NewSQL数据库大对象块存储原理与应用
一、前言
企业内容管理(EnterpriseContent Management,ECM)系统是一种管理非结构化内容的系统,传统代表为EMC Documentum或IBM Filenet等ECM解决方案。随着大数据技术的越发普及,越来越多的客户开始尝试把存放在传统ECM系统中的文件、图片、影像等内容向开放分布式平台迁移。一般来说,用户可以选择的方案根据场景与数据类型来看可以分为几类,包括HDFS方案、对象存储方案、NAS方案、以及分布式数据库方案等。
其中,HDFS方案主要面向数据归档,对大量打成大包的文件直接存放,一般不提供在线读写功能,主要的目的是替代磁带。
而NAS方案则类似HDFS,使用独立第三方传统数据库作为元数据管理系统,同时使用外接NAS设备存放中小型文件。一般来说,NAS作为文件系统可以支持较多数量的小文件,但是当小文件数量达到亿级时同样会产生管理、访问性能与扩展性等一系列问题。
对象存储则以S3等接口为通用标准,设备提供商可以在底层使用K/V存储或块存储等不同存储机制,同时提供类似对象访问、版本管理等一系列功能特性。
最后,分布式数据库方案则使用分布式数据库中的大对象机制,将元数据与大对象统一存放在数据库中,在支持批次管理、版本管理、流程管理等元数据管理特性时不需要借助额外第三方数据库进行支持。
二、功能概述
SequoiaDB(巨杉数据库)是一款新一代分布式文档类数据库,同时支持事务与标准SQL的结构化数据访问方式。在同类开源分布式数据库中,SequoiaDB是唯一一款原生集成行存储与块存储双引擎的数据库。除了JSON存储引擎以外,为了提高非结构化文件的读写性能,SequoiaDB核心引擎提供了分布式块存储模式,可以将非结构化大文件按照固定大小的数据块进行切分并存放于不同分区。当用户需要管理海量的小文件(例如照片、音视频、文档、图片等)时,SequoiaDB的双存储引擎特性能够帮助用户快速搭建一个高性能、高可用的内容管理与影像平台系统。使用SequoiaDB搭建的影像平台系统架构相对简单,元数据与内容数据均可使用SequoiaDB服务器的本地磁盘存放,不再需要额外购买昂贵的外部存储设备,节省企业的开发和运维成本。
SequoiaDB的块存储字段类型叫做LOB(Large OBject,大对象),其核心机制是将内容文件打散成多个数据块,每个数据块被分别发送到不同分区独立存放。与其他解决方案相比,由于不存在独立中控元数据节点,SequoiaDB提供的LOB存储机制理论上可以存放近乎无限数量的对象文件,并且不会由于元数据堆积而造成性能下降。同时,由于数据块被散列分布到所有数据节点,整个系统的吞吐量随集群磁盘数量的增加近乎线性提升。最后,SequoiaDB提供原生的内容管理接口,通过REST访问方式支持批次管理、版本管理、流程管理等一系列基本CM特性。
从使用方式上看,SequoiaDB的LOB机制可以使用原生API的访问形式,对底层LOB对象进行读写访问;同时,用户也可以通过高阶CM API Java接口,Java驱动会将请求封装成RESTful形式,通过发送接收HTTP报文进行对象和批次级别读写更新操作。
三、架构
SequoiaDB的LOB存储结构分为元数据文件(lobm)与数据文件(lobd)。其中,元数据文件存储整个LOB数据文件的元数据模型,包括每个页的空闲状况、散列桶、以及数据映射表等一系列数据结构。而数据文件则存储用户真实数据,数据头之后所有数据页按照page size进行切分,每个数据页不包含任何元数据信息。
图3:LOB元数据与数据文件结构映射
在建立集合的过程当中,大对象存储必须依附于普通集合存在,一个集合中的大对象仅归属于该集合,不能被另外一个集合管理。
当用户上传一个大对象时,会经历几次散列操作。
首先,协调节点或客户端会生成(或者用户指定)一个全局唯一的描述符,同时将传入的数据按照用户指定的pagesize大小切片,最后针对每一个切片按照(描述符+切片id)进行散列,用于决定该切片存在哪个数据分区中。注意,集合的分区键设定并不作用于大对象。
在每个分区中,当接收到数据分片后会根据(描述符+切片id)进行再一次散列,决定元数据桶的位置。而真实数据则通过查找元数据信息,在数据文件中找到一个最近的空闲页写入,然后将该页的ID写入元数据桶中,代表该桶指向这个数据页。如果散列后数据桶已经被占用,则使用常规散列冲突的解决方式找到下一个空闲桶。
当用户读取大对象时,协调节点按照其(描述符+偏移+长度)计算出需要读取多少个切片,以及每个切片所在的数据分区,最后将数据节点返回的数据按顺序排列返回客户端。
由于SequoiaDB将文件切片存储,一个大文件可能存在有非常多个分片,所以在访问的时候协调节点还需要进行请求合并,尽可能使用最小的报文一次性请求多个连续的数据页,以防止访问一个对象时协调节点需要向数据节点发送成千上万的此类请求,同时对数据节点做到I/O合并,一次性读入尽可能多的连续页面。
四、行业应用案例
企业内容管理平台
随着网络技术的渐渐普及,越来越多的银行开始将传统渠道向互联网与移动端靠拢。随之而来的,是更多监管业务的需要,例如针对远程开户等业务,银行需要开始提供“双录”能力,对用户的音频与视频数据进行存储。传统EMC、IBM提供的企业内容管理系统以小机加高端存储硬件为基础,对于仅存票据证照等相对小量的图片存储还可以勉强满足需要,但是当存储类型扩展到音视频等领域性能并不出色,同时开销还会指数级增加。
SequoiaDB提供的分布式、双引擎以及对象存储的功能,天然为海量的音视频、影像、证照等内容提供了分布式存储的能力。SequoiaDB可以使用高存储密度的PC服务器替代传统的小机加高端存储的配置,能够使用户以1/5的拥有成本,提供更多的存储空间与更高的吞吐能力。
图4:基于SequoiaDB的新一代企业内容管理平台与旧平台的对比
在SequoiaDB内容管理解决方案中,数据库除了提供基本的记录与文件的读写操作外,还提供了内容管理平台的批次管理、版本管理、流程控制等一系列后台管控能力,为与用户中间件对接提供了最大便利。
图5:SequoiaDB内容管理平台架构图
五、操作指南
SequoiaDB提供基于shell的命令行界面,以及C、C++、Java、Python、PHP、Nodejs等驱动访问原生LOB API。同时,SequoiaDB提供访问协议的CM API Java接口。本文将会就命令行、C++、Java以及CM API接口进行详细描述。
5.1 命令行
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
putLob |
oid |
string |
大对象OID |
file_path |
string |
大对象文件的本地路径 |
|
forced |
boolean |
如果大对象OID已经存在则直接覆盖 |
|
deleteLob |
oid |
string |
大对象OID |
getLob |
oid |
string |
大对象OID |
file_path |
string |
大对象文件的本地路径 |
|
forced |
boolean |
如果本地文件已经存在则直接覆盖 |
|
listLobs |
- |
- |
- |
表1:命令行操作指令
样例:
> db.foo.bar.putLob('/opt/sequoiadb/standalone/diaglog/sdbdiag.log')
579f55b7389d2aef0a000000
Takes 0.166125s.
> db.foo.bar.listLobs()
{
"Size": 29342,
"Oid": {
"$oid": "579f55b7389d2aef0a000000"
},
"CreateTime": {
"$timestamp": "2016-08-01-21.59.19.939000"
},
"Available": true
}
Return 1 row(s).
Takes 0.6703s.
> db.foo.bar.getLob('579f55b7389d2aef0a000000', '/opt/sequoiadb/standalone/test.log')
{
"LobSize": 29342,
"CreateTime": {
"$timestamp": "2016-08-01-21.59.19.939000"
}
}
Takes 0.910s.
5.2 C++
sdbclient::sdbCollection类:
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
createLob |
lob |
sdbLob & |
传出对象 |
oid |
bson::OID * |
指定OID,如果不指定则自动生成 |
|
removeLob |
oid |
bson::OID & |
大对象OID |
openLob |
lob |
sdbLob & |
传出对象 |
oid |
bson::OID & |
指定OID |
|
listLobs |
cursor |
sdbCursor & |
传出游标 |
表2:sdbCollection类中LOB相关函数
sdbclient::sdbLob类:
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
close |
- |
- |
- |
read |
len |
UINT32 |
一次读取长度 |
buf |
CHAR * |
缓冲区指针 |
|
read |
UINT32 * |
真实读取长度,传出参数 |
|
write |
buf |
CHAR * |
缓冲区指针 |
len |
UINT32 |
一次写入长度 |
|
seek |
size |
SINT64 |
转移偏移 |
whence |
SDB_LOB_SEEK |
寻址起始方式 |
|
isClosed |
- |
- |
- |
isClosed |
flag |
BOOLEAN & |
本对象是否已被关闭的传出参数 |
getOid |
- |
- |
- |
getOid |
oid |
bson::OID & |
本对象的Oid传出参数 |
getSize |
- |
- |
- |
getSize |
size |
SINT64 * |
本对象大小的传出参数 |
getCreateTime |
- |
- |
- |
getCreateTime |
millis |
UINT64 * |
本对象创建时间的传出参数 |
表3:sdbLob类中的相关函数
样例代码可以参考安装目录下samples/CPP/lob.cpp文件。
5.3 Java
com.sequoiadb.base.DBCollection类:
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
createLob |
id |
ObjectId |
指定创建对象 |
createLob |
- |
- |
- |
openLob |
id |
ObjectId |
指定打开对象 |
removeLob |
id |
ObjectId |
指定删除对象 |
listLobs |
- |
- |
- |
表4:DBCollection类中LOB相关函数
com.sequoiadb.base.DBLob类:
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
getID |
- |
- |
- |
getSize |
- |
- |
- |
getCreateTime |
- |
- |
- |
write |
b |
byte[] |
写入字节数组 |
read |
b |
byte[] |
读取字节数组 |
seek |
size |
long |
转移偏移 |
seekType |
int |
寻址起始方式 |
|
close |
- |
- |
- |
表5:DBLob类中的相关函数
样例代码可以参考安装目录下samples/Java/com/sequoiadb/samples/Lob.java文件。
5.4 CM API
名称 |
参数 |
类型 |
说明 |
---|---|---|---|
createBatch |
itemTypeBean |
ItemTypeBean |
批次信息属性封装对象 |
userId |
String |
用户ID |
|
deleteBatch |
batchId |
String |
批次ID |
userId |
String |
用户ID |
|
updateBatch |
iteamTypeBean |
ItemTypeBean |
批次信息属性封装对象 |
userId |
String |
用户ID |
|
queryItemByBatchID |
batchId |
String |
批次ID |
version |
int |
版本号 |
|
queryItemByType |
batchId |
String |
批次ID |
type |
String |
类型 |
|
queryBatch |
map |
Map |
批次匹配条件 |
queryItem |
map |
Map |
文档匹配条件 |
putContent |
path |
String |
上传文档 |
getContent |
oid |
String |
对象OID |
path |
String |
下载文档存储路径 |
表6:CM API中的相关函数
六、性能指标
6.1 系统配置
本文测试使用3台物理机作为服务器与1台物理机作为客户端。客户端使用C程序与服务端直连,使用LOB API进行读写访问操作。
服务端
CPU:Intel® Xeon® CPU E5-2420 0 @1.90GHZ(6core *2) (一台物理机)
CPU:Intel® Xeon® CPU E5-2620 V2@ 2.10GHZ (6core *2) (二台物理机)
MEMORY:48
DISK: 2T/6块
客户端
CPU:Intel® Xeon® CPU E5-2420 0 @1.90GHZ(6core *2) (一台物理机)
MEMORY:48
DISK: 2T/6块
集群部署方式为6分区3副本,三台机器构成高可用集群,网络为千兆网,协调节点与编目节点分别部署在3台服务器上。数据节点分布见表3,其中红色部分代表该分区的主节点,黑色为从节点。
部署 角色 |
分区1 |
分区2 |
分区3 |
分区4 |
分区5 |
分区6 |
---|---|---|---|---|---|---|
192.168.3.78 |
disk1 |
disk2 |
disk3 |
disk4 |
disk5 |
disk6 |
192.168.3.79 |
disk1 |
disk2 |
disk3 |
disk4 |
disk5 |
disk6 |
192.168.3.72 |
disk1 |
disk2 |
disk3 |
disk4 |
disk5 |
disk6 |
表7:数据节点分布
6.2 写操作测试
文件系统的配置分别使用两种方式:打开DIO以及用普通文件系统缓存方式。
表8:DIO模式
表10:文件系统模式
可以看到,打开DIO与普通文件系统缓存相比,性能确实存在一定下降。在三台服务器的情况下,尺寸较小的文件在DIO打开的情况下显示出与普通文件系统缓存更大的差异。当文件尺寸平均达到1-2MB左右后,使用DIO与普通文件系统的差异几乎可以忽略不计。图1显示了启用与关闭DIO的情况下,在800线程并发中整个集群的吞吐量(MB/s)。
图6:写操作吞吐量对比
6.3 读操作测试
不同于写操作,SequoiaDBLOB机制在读操作中受DIO的影响较小。
表11:DIO模式
表12:文件系统模式
在文件读取的过程当中,因为绝大部分读取都是顺序I/O,因此是否打开文件系统缓存基本对性能不构成影响。从性能读数可以看出,SequoiaDB LOB读取时每次读取的缓存大小对于读取性能基本上不构成太大的影响。
测试中吞吐量上限基本达到客户端千兆网瓶颈,因此通过增加网络带宽依然有可以提升的空间。
图7:读操作吞吐量对比
七、结论
SequoiaDB的大对象机制主要为用户存储海量中小型文件所设计。通过配置pagesize大小,SequoiaDB在存储100KB到100MB区间内的文件性能与磁盘开销比例最优,因此针对各个企业的票据、扫描件、合同件、照片、小视频、音频等文件最为适用。
总体来看,使用SequoiaDB替代传统ECM,为企业存储海量中小型文件不单能够大大降低企业的总体拥有成本,还能够大幅度提升数据访问层面的吞吐量,并从开发、运维、管理等各个层面大幅度降低使用难度,帮助企业更快地在企业内容管理系统上落地。
- 嗤!给你来点fiyocms漏洞喷雾
- 厚土Go学习笔记 | 15. defer语句延迟函数的执行
- Nodejs学习笔记(九)--- 与Redis的交互(mranney/node_redis)入门
- Nodejs学习笔记(十)--- 与MongoDB的交互(mongodb/node-mongodb-native)、MongoDB入门
- Golang泛型编程初体验
- 工具| 手把手教你制作信息收集器之端口扫描
- 厚土Go学习笔记 | 14. switch 的条件写的有点灵活,不过风格还是go的一贯风格
- Nodejs学习笔记(十四)— Mongoose介绍和入门
- 厚土Go学习笔记 | 13. 用循环和函数 实现Sqrt(x)
- 代码审计| 这是一款适合练手的漏洞
- 工具| NSE漏洞审计和渗透脚本的demo
- Windows Server 2008 R2 配置Exchange 2010邮件服务器并使用EWS发送邮件
- 厚土Go学习笔记 | 12. if 语句
- 厚土Go学习笔记 | 11. for循环 go语言只有for循环
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS7下升级GLIBC2.31
- 网络扫描利器Fing之Linux版本使用教程
- dotnet 通过依赖注入的 Scoped 给工作流注入相同的上下文信息
- 微信文章爬虫
- vue 随记(6):构建的艺术
- Centos7发布SpringBoot项目并后台运行
- k8s部署zookeeper集群
- dotnet 使用 SemaphoreSlim 可能的内存泄露
- WPF 绑定继承的样式提示 只能根据带有基类型 IFrameworkInputElement 的目标类型的 Style 样式
- 29.opengl高级光照-视差贴图
- SpringBoot集成Mybatis开启下划线格式的数据自动转换成小驼峰格式
- WPF 列表右键菜单比较符合 MVVM 的命令绑定方法
- Group Sample:一个简单有效的目标检测涨点Trick
- PyTorch版CenterNet数据加载解析
- WPF 使用 HandyControl 给 ListView 添加漂亮的表头效果