1.各模式适用场景介绍
由于SequoiaDB对比其他的NoSQL有更多的方式将数据分布到多台服务器上,所以下面笔者为阅读者一一介绍每种分布式方式适合于哪种场景。
1.1Hash 方式分布数据
在Hash分布方式中,由于是对集合中某个字段的Hash值进行数据均匀,所以用户未来在使用Hash分布时,ShardingKey 一定要选择集合中字段值比较随机,并且Hash值比较均匀的字段,这样才能够保证集合中的数据被均匀的分布在各个数据分区组上。
Hash分布式方式主要适合数据量比较大,并且集合中包含一个Hash值比较随机的字段。如果集合中并没有Hash值比较随机的字段,但是集合的数据量又非常的巨大,用户可以考虑使用SequoiaDB中的主键-“_id”作为ShardingKey。
1.2Range 方式分布数据
Range 分布方式,主要适用用于集合数据量大,并且集合包含某个具有比较明确的数值范围的字段,例如时间字段或者是业务类型字段,来帮助用户做集合的范围切分。
1.3Partition 方式分布数据
Partition 分布方式,和Range 分布方式的适用场景非常类似,都是要求集合数据量大,并且集合包含某个具有比较明确的数值范围的字段。
Partition分布方式,实际上并不能够做到数据自动均匀分布到多个数据分区组,而是需要用户在建立子表时,人工显式指定此子表是被分配到哪个数据分区组上,然后再通过主表的attach 命令将子表按照某个字段的范围值挂载到主表上。
而Partition 分布方式与Range 分布方式最大的不同点,在于对数据的删除上。
在Range 分布方式中,如果用户需要对集合中某个过旧的时间范围数据进行删除,用户需要调用remove 命令,真实地从数据库磁盘中删除这部分数据,这个耗时会比较久。
而在Partition 分布方式中,用户同样希望删除某个过旧的时间范围数据,用户只需要调用dettach命令,将符合时间范围的子表从主表中卸载下来,即可完成数据从集合中清除的目的,dettach命令的效率非常高,基本是秒级完成。然后用户可以对过旧的子表执行truncate 命令,回收磁盘空间。
注意,集合truncate 命令的执行效率比集合remove 命令的执行效率要高出几个量级。
1.4多维分区
SequoiaDB的多维分区方式,它很好地结合了Hash分布方式和Partition 分布方式的优点,能够让集合中的数据以更小的颗粒度分布到数据库多个数据分区组上。
多维分区分布方式,主要适合的场景是集合数据量特别巨大,集合中同时包含两个关键的ShardingKey,一般为time和id两个字段,time字段给主子表使用,id字段给Hash 分布使用,并且用户在使用在集合过程中,还可能会定期对部分数据进行空间回收。
在真实的客户环境中,多维分区主要使用的场景为:银行的历史数据流水表,业务系统历史日志表等。
2.操作指南
作者在为下面各种数据分布方式做操作前,需要先在数据库中一些准备操作。
连接到数据库中
var db = new Sdb("localhost", 11810);
查看当前数据库有多少数据分区组
var cursor = db.listReplicaGroups();while (cursor.next()){var obj = cursor.current().toObj();if (obj["GroupName"] != "SYSCoord" && obj["GroupName"] != "SYSCatalogGroup") {println (obj["GroupName"]);}} cursor.close ();
返回的结果为
group1
group2
group3
group4
为了做Hash 切分方便,用户可以先给数据库建立Domain
db.createDomain ("domain1", ["group1", "group2"], {AutoSplit:true});
db.createDomain ("domain2", ["group3", "group4"], {AutoSplit:true});
2.1Hash 方式分布数据
建立一个集合空间,名为testcs_domain1,并且指定此集合空间是建立在domain1 上的
db.createCS("testcs_domain1", {Domain:"domain1"});
在testcs_domain1 集合空间上建立一个集合,名为testcl_hash,设置ShardingType = hash,ShardingKey = id
var CS = db.getCS("testcs_domain1");
CS.createCL("testcl_hash", {ShardingType:"hash", ShardingKey:{id:1}});
检查testcl_hash 集合在数据库中的分布情况
db.snapshot(8, {Name:"testcs_domain1.testcl_hash"});
{
"AutoSplit": true,
"CataInfo": [
{
"ID": 0,
"GroupID": 1001,
"GroupName": "group2",
"LowBound": {
"": 0
},
"UpBound": {
"": 2048
}
},
{
"ID": 1,
"GroupID": 1000,
"GroupName": "group1",
"LowBound": {
"": 2048
},
"UpBound": {
"": 4096
}
}
],
"EnsureShardingIndex": true,
"InternalV": 3,
"Name": "testcs_domain1.testcl_hash",
"Partition": 4096,
"ShardingKey": {
"id": 1
},
"ShardingType": "hash",
"Version": 2,
"_id": {
"$oid": "57c73fe8ed44740501b46dee"
}
}
用户可以从snapshot 的输出中看到,testcs_domain1.testcl_hash 被切分到group1 和group2 上,并且Hash桶是均匀分配的。
2.2Range 方式分布数据
用户在做Range 切分时,Domain 的功能就不能帮上忙了。为了让本文档的阅读者更加清晰地了解Range 切分,作者在数据库中新建一个集合空间,名为testcs_range
db.createCS("testcs_range");
建立一个名为testcl_range 的集合,ShardingKey = time, ShardingType = range,并且指定此集合在初始化时建立在group1 数据分区组上。
var CS = db.getCS("testcs_range");
CS.createCL("testcl_range", {ShardingType:"range", ShardingKey:{time:1}, Group:"group1"});
对testcl_range 做范围切分
CS.testcl_range.split ("group1", "group2", {time:"20160401"}, {time:"20160701"});
CS.testcl_range.split ("group1", "group3", {time:"20160701"}, {time:"20161001"});
CS.testcl_range.split ("group1", "group4", {time:"20161001"}, {time:"20170101"});
查看testcs_range.testcl_range 的切分情况
db.snapshot (8, {Name:"testcs_range.testcl_range"});
{
"CataInfo": [
{
"ID": 0,
"GroupID": 1000,
"GroupName": "group1",
"LowBound": {
"time": {
"$minKey": 1
}
},
"UpBound": {
"time": "20160401"
}
},
{
"ID": 1,
"GroupID": 1001,
"GroupName": "group2",
"LowBound": {
"time": "20160401"
},
"UpBound": {
"time": "20160701"
}
},
{
"ID": 3,
"GroupID": 1002,
"GroupName": "group3",
"LowBound": {
"time": "20160701"
},
"UpBound": {
"time": "20161001"
}
},
{
"ID": 4,
"GroupID": 1003,
"GroupName": "group4",
"LowBound": {
"time": "20161001"
},
"UpBound": {
"time": "20170101"
}
},
{
"ID": 2,
"GroupID": 1000,
"GroupName": "group1",
"LowBound": {
"time": "20170101"
},
"UpBound": {
"time": {
"$maxKey": 1
}
}
}
],
"EnsureShardingIndex": true,
"Name": "testcs_range.testcl_range",
"ShardingKey": {
"time": 1
},
"ShardingType": "range",
"Version": 4,
"_id": {
"$oid": "57c754b6ed44740501b46e0e"
}
}
testcs_range.testcl_range 集合数据切分情况如下
group2 负责 time 范围为 "20160401" ~ "20160701"
group3 负责 time 范围为 "20160701" ~ "20161001"
group4 负责 time 范围为 "20161001" ~ "20170101"
group1 负责 time 范围为除 group2、group3、group4 范围外的所有数据
用户可以写入两条记录去验证一下
CS.testcl_range.insert({name:"test", id:1, time:"20160425"});
CS.testcl_range.insert({name:"haha", id:2, time:"20160101"});
用户可以通过快照查看每个数据分区组的记录数
db.snapshot(4, {Name:"testcs_range.testcl_range"}, {"Details.Group.TotalRecords":null,"Details.GroupName":null});
{
"Details": [
{
"GroupName": "group1",
"Group": [
{
"TotalRecords": 1
},
{
"TotalRecords": 1
},
{
"TotalRecords": 1
}
]
},
{
"GroupName": "group2",
"Group": [
{
"TotalRecords": 1
},
{
"TotalRecords": 1
},
{
"TotalRecords": 1
}
]
},
{
"GroupName": "group3",
"Group": [
{
"TotalRecords": 0
},
{
"TotalRecords": 0
},
{
"TotalRecords": 0
}
]
},
{
"GroupName": "group4",
"Group": [
{
"TotalRecords": 0
},
{
"TotalRecords": 0
},
{
"TotalRecords": 0
}
]
}
]
}
由于本集群是一个数据分区组,包含3个数据副本,所以在快照信息中,一个group 会显示3条记录。
并且用户从快照信息中,group1 包含1 条记录,group2 包含1 条记录,符合作者对testcs_range.testcl_range 的切分情况。
2.3Parttion 方式分布数据
为了验证使用主子表来做数据切分,作者在数据库中新建一个名为 testcs_mainsub 的集合空间
db.createCS("testcs_mainsub");
var CS = db.getCS("testcs_mainsub");
在 testcs_mainsub 集合空间上建立一个主表,ShardingType = range, ShardingKey = time, isMainCL = true
CS.createCL("testcl_main", {ShardingType:"range", ShardingKey:{time:1}, IsMainCL:true});
建立子表,并且在建立子表时,显式指定每个子表被分配到哪个数据分区组上。
CS.createCL("testcl_sub1", {Group:"group1"});
CS.createCL("testcl_sub2", {Group:"group2"});
CS.createCL("testcl_sub3", {Group:"group3"});
CS.createCL("testcl_sub4", {Group:"group4"});
将各个子表挂载到主表上
CS.testcl_main.attachCL ("testcs_mainsub.testcl_sub1", {LowBound:{time:"20160101"}, UpBound:{time:"20160401"}});
CS.testcl_main.attachCL ("testcs_mainsub.testcl_sub2", {LowBound:{time:"20160401"}, UpBound:{time:"20160701"}});
CS.testcl_main.attachCL ("testcs_mainsub.testcl_sub3", {LowBound:{time:"20160701"}, UpBound:{time:"20161001"}});
CS.testcl_main.attachCL ("testcs_mainsub.testcl_sub4", {LowBound:{time:"20161001"}, UpBound:{time:"20170101"}});
用户可以通过快照了解主表数据的切分情况
db.snapshot (8, {Name:"testcs_mainsub.testcl_main"});
{
"CataInfo": [
{
"ID": 1,
"SubCLName": "testcs_mainsub.testcl_sub1",
"LowBound": {
"time": "20160101"
},
"UpBound": {
"time": "20160401"
}
},
{
"ID": 2,
"SubCLName": "testcs_mainsub.testcl_sub2",
"LowBound": {
"time": "20160401"
},
"UpBound": {
"time": "20160701"
}
},
{
"ID": 3,
"SubCLName": "testcs_mainsub.testcl_sub3",
"LowBound": {
"time": "20160701"
},
"UpBound": {
"time": "20161001"
}
},
{
"ID": 4,
"SubCLName": "testcs_mainsub.testcl_sub4",
"LowBound": {
"time": "20161001"
},
"UpBound": {
"time": "20170101"
}
}
],
"EnsureShardingIndex": true,
"IsMainCL": true,
"Name": "testcs_mainsub.testcl_main",
"ShardingKey": {
"time": 1
},
"ShardingType": "range",
"Version": 5,
"_id": {
"$oid": "57c75d35ed44740501b46e28"
}
}
目前 testcs_mainsub.testcl_main 的数据切分情况如下
testcl_sub1 属于 group1,它负责的数据范围为 20160101 ~ 20160401
testcl_sub2 属于 group2,它负责的数据范围为 20160401 ~ 20160701
testcl_sub3 属于 group3,它负责的数据范围为 20160701 ~ 20161001
testcl_sub4 属于 group4,它负责的数据范围为 20161001 ~ 20170101
用户可以写入两条记录来验证一下数据的分布情况
CS.testcl_main.insert ({name:"test", id:1, time:"20160123"});
CS.testcl_main.insert ({name:"haha", id:2, time:"20161207"});
用户可以查看一下每个子表的数据情况
CS.testcl_sub1.count();
CS.testcl_sub2.count();
CS.testcl_sub3.count();
CS.testcl_sub4.count();
2.4多维分区
为了验证SequoiaDB 的多维分区功能,并且为了给阅读者更好了解在多维分区和Domain功能的结合,作者在数据库中新建两个集合空间,分别为 testcs_doublepartition_domain1 和 testcs_doublepartition_domain2
db.createCS ("testcs_doublepartition_domain1", {Domain:"domain1"});
var CS1 = db.getCS ("testcs_doublepartition_domain1");
db.createCS ("testcs_doublepartition_domain2", {Domain:"domain2"});
var CS2 = db.getCS ("testcs_doublepartition_domain2");
刚才作者已经在数据库多维分区的技术原理也介绍了,SequoiaDB的多维分区,实际上就是在一个集合中,同时运用主子表和Hash分区两个数据分区功能,从而使得集合能够以更小的颗粒度做数据均衡。
在 testcs_mainsub 集合空间上建立一个主表,ShardingType = range, ShardingKey = time, isMainCL = true
var CS = db.getCS("testcs_mainsub");
CS.createCL("testcl_main_doublepartition", {ShardingType:"range", ShardingKey:{time:1}, IsMainCL:true});
建立子表,并且在建立子表时,注意选择集合空间,并且显式指定ShardingType = hash 和 ShardingKey = id。
CS1.createCL("testcl_sub1", {ShardingType:"hash", ShardingKey:{id:1}});
CS1.createCL("testcl_sub2", {ShardingType:"hash", ShardingKey:{id:1}});
CS2.createCL("testcl_sub3", {ShardingType:"hash", ShardingKey:{id:1}});
CS2.createCL("testcl_sub4", {ShardingType:"hash", ShardingKey:{id:1}});
将各个子表挂载到主表上
CS.testcl_main_doublepartition.attachCL ("testcs_doublepartition_domain1.testcl_sub1", {LowBound:{time:"20160101"}, UpBound:{time:"20160401"}});
CS.testcl_main_doublepartition.attachCL ("testcs_doublepartition_domain1.testcl_sub2", {LowBound:{time:"20160401"}, UpBound:{time:"20160701"}});
CS.testcl_main_doublepartition.attachCL ("testcs_doublepartition_domain2.testcl_sub3", {LowBound:{time:"20160701"}, UpBound:{time:"20161001"}});
CS.testcl_main_doublepartition.attachCL ("testcs_doublepartition_domain2.testcl_sub4", {LowBound:{time:"20161001"}, UpBound:{time:"20170101"}});
用户可以通过快照信息核对数据的切分情况
db.snapshot (8, {Name:"testcs_mainsub.testcl_main_doublepartition"});
{
"CataInfo": [
{
"ID": 1,
"SubCLName": "testcs_doublepartition_domain1.testcl_sub1",
"LowBound": {
"time": "20160101"
},
"UpBound": {
"time": "20160401"
}
},
{
"ID": 2,
"SubCLName": "testcs_doublepartition_domain1.testcl_sub2",
"LowBound": {
"time": "20160401"
},
"UpBound": {
"time": "20160701"
}
},
{
"ID": 3,
"SubCLName": "testcs_doublepartition_domain2.testcl_sub3",
"LowBound": {
"time": "20160701"
},
"UpBound": {
"time": "20161001"
}
},
{
"ID": 4,
"SubCLName": "testcs_doublepartition_domain2.testcl_sub4",
"LowBound": {
"time": "20161001"
},
"UpBound": {
"time": "20170101"
}
}
],
"EnsureShardingIndex": true,
"IsMainCL": true,
"Name": "testcs_mainsub.testcl_main_doublepartition",
"ShardingKey": {
"time": 1
},
"ShardingType": "range",
"Version": 5,
"_id": {
"$oid": "57c80046ed44740501b46e43"
}
}
testcs_doublepartition_domain1.testcl_sub1 属于 group1,它负责的数据范围为 20160101 ~ 20160401
testcs_doublepartition_domain1.testcl_sub2 属于 group2,它负责的数据范围为 20160401 ~ 20160701
testcs_doublepartition_domain2.testcl_sub3 属于 group3,它负责的数据范围为 20160701 ~ 20161001
testcs_doublepartition_domain2.testcl_sub4 属于 group4,它负责的数据范围为 20161001 ~ 20170101
用户也可以使用快照来确认一下各个子表是否被正确切分,例如查看集合 testcs_doublepartition_domain1.testcl_sub1 是否被分布到 domain1 上
db.snapshot (8, {Name:"testcs_doublepartition_domain1.testcl_sub1"});
{
"AutoSplit": true,
"CataInfo": [
{
"ID": 0,
"GroupID": 1000,
"GroupName": "group1",
"LowBound": {
"": 0
},
"UpBound": {
"": 2048
}
},
{
"ID": 1,
"GroupID": 1001,
"GroupName": "group2",
"LowBound": {
"": 2048
},
"UpBound": {
"": 4096
}
}
],
"EnsureShardingIndex": true,
"InternalV": 3,
"MainCLName": "testcs_mainsub.testcl_main_doublepartition",
"Name": "testcs_doublepartition_domain1.testcl_sub1",
"Partition": 4096,
"ShardingKey": {
"id": 1
},
"ShardingType": "hash",
"Version": 3,
"_id": {
"$oid": "57c8004eed44740501b46e46"
}
}
用户从快照信息中了解到,testcs_doublepartition_domain1.testcl_sub1 集合被切分到group1 和 group2 上,满足domain1 的数据分区组列表。