返回 登录
0

关于MongoDB Sharding,你应该知道的

MongoDB Sharded Cluster原理

如果你还不了解 MongoDB Sharded Cluster,可以先看文档认识一下:

图片描述

什么时候考虑用Sharded Cluster?

当你考虑使用Sharded Cluster时,通常是要解决如下2个问题:

  1. 存储容量受单机限制,即磁盘资源遭遇瓶颈。
  2. 读写能力受单机限制(读能力也可以在复制集里加secondary节点来扩展),可能是CPU、内存或者网卡等资源遭遇瓶颈,导致读写能力无法扩展。

如果你没有遇到上述问题,使用MongoDB复制集就足够了,管理维护上比Sharded Cluster要简单很多。

如何确定shard、mongos数量?

当你决定要使用Sharded Cluster时,问题来了,应该部署多少个shard、多少个mongos?这个问题首富已经指点过我们,『先定一个小目标,比如先部署上1000个shard』,然后根据需求逐步扩展』。

回到正题,shard、mongos的数量归根结底是由应用需求决定,如果你使用sharding只是解决『海量数据存储』的问题,访问并不多,那么很简单,假设你单个shard能存储M,需要的存储总量是N。

numberOfShards = N / M / 0.75    (假设容量水位线为75%)
numberOfMongos = 2+ (因为对访问要求不高,至少部署2个 mongos 做高可用即可)

如果你使用sharding是解决高并发写入(或读取)数据的问题,总的数据量其实很小,这时你部署的shard、mongos要能满足读写性能需求,而容量上则不是考量的重点。假设单个shard最大qps为M,单个mongos最大qps为Ms,需要总的qps为Q。(注:mongos、mongod的服务能力,需要用户根据访问特性来实测得出)

numberOfShards = Q / M  / 0.75    (假设负载水位线为75%)
numberOfMongos = Q / Ms / 0.75 

如果sharding要解决上述2个问题,则按需求更高的指标来预估;以上估算是基于sharded cluster里数据及请求都均匀分布的理想情况,但实际情况下,分布可能并不均衡,这里引入一个『不均衡系数D』的概念(个人YY的,非通用概念),意思是系统里『数据(或请求)分布最多的shard是平均值的D倍』,实际需要的shard、mongos数量,在上述预估上再乘上『不均衡系数D』。

而为了让系统的负载分布尽量均匀,就需要合理的选择shard key。

如何选择shard key ?

MongoDB Sharded cluster 支持2种分片方式,各有优劣:

  • 范围分片,通常能很好的支持基于shard key的范围查询
  • Hash分片,通常能将写入均衡分布到各个shard

上述2种分片策略都不能解决的问题包括:

  1. shard key取值范围太小(low cardinality),比如将数据中心作为shard key,而数据中心通常不会很多,分片的效果肯定不好。
  2. shard key某个值的文档特别多,这样导致单个chunk特别大(及jumbo chunk),会影响chunk迁移及负载均衡。
  3. 根据非shard key进行查询、更新操作都会变成scatter-gather查询,影响效率。

好的shard key应该拥有如下特性:

  • key分布足够离散(sufficient cardinality)
  • 写请求均匀分布(evenly distributed write)
  • 尽量避免scatter-gather查询(targeted read)

举个例子,某物联网应用使用MongoDB Sharded Cluster存储『海量设备』的『工作日志』,假设设备数量在百万级别,设备每10s向MongoDB汇报一次日志数据,日志包含deviceId,timestamp信息,应用最常见的查询请求是『查询某个设备某个时间内的日志信息』。(读者可以自行预估下,这个量级,无论从写入还是数据量上看,都应该使用Sharding,以便能水平扩张)。

  • 方案1:时间戳作为shard key,范围分片

    • Bad
    • 新的写入都是连续的时间戳,都会请求到同一个shard,写分布不均
    • 根据deviceId的查询会分散到所有shard上查询,效率低
  • 方案2:时间戳作为shard key,hash分片

    • Bad
    • 写入能均分到多个shard
    • 根据deviceId的查询会分散到所有shard上查询,效率低
  • 方案3:deviceId作为shardKey,hash分片(如果id没有明显的规则,范围分片也一样)

    • Bad
    • 写入能均分到多个shard
    • 同一个deviceId 对应的数据无法进一步细分,只能分散到同一个chunk,会造成jumbo chunk
    • 根据deviceId的查询只请求到单个shard,不足的时,请求路由到单个shard后,根据时间戳的范围查询需要全表扫描并排序
  • 方案4:(deviceId, 时间戳)组合起来作为shardKey,范围分片(Better)

    • Good
    • 写入能均分到多个shard
    • 同一个deviceId 的数据能根据时间戳进一步分散到多个chunk
    • 根据deviceId查询时间范围的数据,能直接利用(deviceId, 时间戳)复合索引来完成。

关于jumbo chunk及chunk size

jumbo chunk的意思是chunk『太大或者文档太多』且无法分裂。

If MongoDB cannot split a chunk that exceeds the specified chunk size or contains a number of documents that exceeds the max, MongoDB labels the chunk as jumbo.

MongoDB默认的chunk size为64MB,如果chunk超过64MB并且不能分裂(比如所有文档的shard key都相同),则会被标记为jumbo chunk,balancer不会迁移这样的chunk,从而可能导致负载不均衡,应尽量避免。

一旦出现了jumbo chunk,如果对负载均衡要求不高,不去关注也没啥影响,并不会影响到数据的读写访问。如果一定要处理,可以尝试如下方法

  1. 对jumbo chunk进行split,一旦split成功,mongos会自动清除jumbo标记。
  2. 对于不可再分的chunk,如果该chunk已不再是jumbo chunk,可以尝试手动清除chunk的jumbo标记(注意先备份下config数据库,以免误操作导致config库损坏)。
  3. 最后的办法,调大chunk size,当chunk大小不再超过chunk size时,jumbo标记最终会被清理,但这个是治标不治本的方法,随着数据的写入仍然会再出现jumbo chunk,根本的解决办法还是合理的规划shard key。

关于chunk size如何设置的问题,绝大部分情况下,请直接使用默认chunk size,以下场景可能需要调整chunk size(取值在1-1024之间)。

  • 迁移时IO负载太大,可以尝试设置更小的chunk size
  • 测试时,为了方便验证效果,设置较小的chunk size
  • 初始chunk size设置不合适,导致出现大量jumbo chunk,影响负载均衡,此时可以尝试调大chunk size
  • 将『未分片的集合』转换为『分片集合』,如果集合容量太大,可能需要(数据量达到T 级别才有可能遇到)调大chunk size才能转换成功。参考Sharding Existing Collection Data Size

Tag aware sharding

Tag aware sharding是Sharded Cluster很有用的一个特性,允许用户自定义一些chunk的分布规则。Tag aware sharding原理如下:

  1. sh.addShardTag()给shard设置标签A
  2. sh.addTagRange()给集合的某个chunk范围设置标签A,最终MongoDB会保证设置标签A的chunk范围(或该范围的超集)分布设置了标签A的shard上。

Tag aware sharding可应用在如下场景:

  • 将部署在不同机房的shard设置『机房标签』,将不同chunk范围的数据分布到指定的机房
  • 将服务能力不通的shard设置『服务等级标签』,将更多的chunk分散到服务能力更强的shard上去。

使用Tag aware sharding需要注意是,chunk分配到对应标签的shard上『不是立即完成,而是在不断insert、update后触发 split、moveChunk后逐步完成的,并且需要保证balancer是开启的』。所以你可能会观察到,在设置了tag range后一段时间后,写入仍然没有分布到tag相同的shard上去。

关于负载均衡

MongoDB Sharded Cluster的自动负载均衡目前是由mongos的后台线程来做的,并且每个集合同一时刻只能有一个迁移任务,负载均衡主要根据集合在各个shard上chunk的数量来决定的,相差超过一定阈值(跟chunk总数量相关)就会触发chunk迁移。

负载均衡默认是开启的,为了避免chunk迁移影响到线上业务,可以通过设置迁移执行窗口,比如只允许凌晨2:00-6:00期间进行迁移。

use config
db.settings.update(
   { _id: "balancer" },
   { $set: { activeWindow : { start : "02:00", stop : "06:00" } } },
   { upsert: true }
)

另外,在进行sharding备份时(通过mongos或者单独备config server和所有shard),需要停止负载均衡,以免备份出来的数据出现状态不一致问题。

sh.stopBalancer()

moveChunk归档设置

使用3.0及以前版本的Sharded Cluster可能会遇到一个问题,停止写入数据后,数据目录里的磁盘空间占用还会一直增加。

上述行为是由sharding.archiveMovedChunks配置项决定的,该配置项在3.0及以前的版本默认为true,即在move chunk时,源shard会将迁移的chunk数据归档一份在数据目录里,当出现问题时,可用于恢复。也就是说,chunk发生迁移时,源节点上的空间并没有释放出来,而目标节点又占用了新的空间。

在3.2版本,该配置项默认值也被设置为false,默认不会对moveChunk的数据在源shard上归档。

recoverShardingState设置

使用MongoDB Sharded Cluster时,还可能遇到一个问题,就是启动shard后,shard不能正常服务,『Primary上调用ismaster时,结果却为true,也无法正常执行其他命令』,其状态类似如下:

mongo-9003:PRIMARY> db.isMaster()
{
    "hosts" : [
        "host1:9003",
        "host2:9003",
        "host3:9003"
    ],
    "setName" : "mongo-9003",
    "setVersion" : 9,
    "ismaster" : false,  // primary 的 ismaster 为 false???
    "secondary" : true,
    "primary" : "host1:9003",
    "me" : "host1:9003",
    "electionId" : ObjectId("57c7e62d218e9216c70aa3cf"),
    "maxBsonObjectSize" : 16777216,
    "maxMessageSizeBytes" : 48000000,
    "maxWriteBatchSize" : 1000,
    "localTime" : ISODate("2016-09-01T12:29:27.113Z"),
    "maxWireVersion" : 4,
    "minWireVersion" : 0,
    "ok" : 1
}

查看其错误日志,会发现shard一直无法连接上config server,上述行为是由sharding.recoverShardingState选项决定,默认为true,也就是说,shard启动时,其会连接config server进行sharding状态的一些初始化,而如果config server连不上,初始化工作就一直无法完成,导致 shard 状态不正常。

有同学在将Sharded Cluster所有节点都迁移到新的主机上时遇到了上述问题,因为config server的信息发生变化了,而shard启动时还会连接之前的config server,通过在启动命令行加上--setParameter recoverShardingState=false来启动shard就能恢复正常了。

上述默认设计的确有些不合理,config server的异常不应该去影响shard,而且最终的问题的表象也很不明确,在3.4大版本里,MongoDB也会对这块进行修改,去掉这个参数,默认不会有recoverShardingState的逻辑,具体参考SERVER-24465

要关注的问题好多,hold不住怎么破?

阿里云已经推出了MongoDB云数据库服务,帮助广大开发者解决MongoDB运维管理的所有问题,让开发者专注于业务开发。MongoDB云数据库目前已支持三节点高可用复制集,Sharded Cluster的功能正在紧锣密鼓的研发中,敬请关注。

使用MongoDB Sharding遇到问题欢迎到云栖社区或MongoDB中文社区一块交流探讨。

参考资料

作者:张友东,花名林青,阿里云数据库组技术专家,主要关注分布式存储、NoSQL数据库等技术领域,目前主要参与MongoDB云数据库的研发,致力于让开发者用上最好的MongoDB云服务。
来源:转载自作者个人博客yunotes

评论