MongoDB Sharding

MongoDB分片(Sharding)是一种扩展数据库存储和处理能力的技术,通过将数据分布在多个服务器上实现水平扩展。分片关键在于选择合适的分片键,确保数据均衡分布。优势包括提高读写效率、存储容量和高可用性。考虑因素包括数据量、性能问题以及不可逆性。分片策略包括范围分片和哈希分片,根据数据分布和查询需求选择。负载均衡由balancer处理,确保chunk在shards间均匀分布。在适当的时候,如存储或性能需求超出单机限制时,考虑使用分片集群。确定shard和mongos数量需考虑数据量和性能需求,同时尽量避免jumbo chunk,选择合适的shard key以保持负载均衡。
摘要由CSDN通过智能技术生成
介绍
Sharding Key
  • Sharding key必须是在对应的collection当中所有的文档都存在的field,比如我要sharding我的user collection,我就可以找nick_name这种所有documents中都存在的field来做为sharding key,当然还要考虑其它因素,但是这个是一个必要条件。
  • sharding key可以一个或者多个fields组成,还以user document为例,除了可以使用nick_name还可以加一个created time一起作为sharding key。当然具体使用什么field要根据系统的实际情况来综合考虑。
  • sharding只能有一个,而且设定后不可更改
  • 针对已经有数据的collections做sharding时,原collections必须有以sharding key开头的索引;如果是空的collection,则MongoDB会自动创建对应的索引
Sharding的优势
读写效率

由于我们可以将相应的写操作分布在不同的shard上,可以更加均匀分担写压力,因此写的性能一般情况下会得到提高。
读取操作时,如果query中包含shard key对应的field,或者以这个field为prefix的compound index key

存储容量

由于是水平分割,因此几乎可以无限扩容,不需要担心某个collection过大而无法单机存储的问题

高可用

从MongoDB3.2开始,config server已经支持replicaset模式,这样的话sharding节点和config节点均可以通过replicaset来保证高可用。

关于Sharding的考虑

由于sharding的不可逆性,所以最好是先不使用sharding,在业务运行一段时间后,根据业务的情况来做决定,这主要需要考虑如下因素:

  • 某个collection的数据是否非常大,达到未来可能一台机器的存储容量都无法解决该collection的存储。
  • 是否有些collection存在性能问题,希望通过sharding来解决
  • 由于具有不可逆性,我们可以只针对需要做sharding的collection做sharding,而其它collection不需要sharding。不做sharding的collection会存储于primary shard之上(所以可见这个shard应该有更大的容量)
  • 针对需要分片的collection,需要确定满足以下几个条件:
  1. 选取的key可以使得collection的数据能均衡地分布在不同的shard上
  2. 写请求的时候也能均匀分布,比如时间戳作为key时,虽然也能将key均匀分布到不同的shard上,但是同一时间段,写请求就会持续在一个shard上,这样就不是均匀写了
  3. 我们针对该collection的查询主要是使用哪个field来作为查询条件,那么这个field最好是作为shard key
  4. 作为sharding key的field需要有索引,或者是作为compound索引的prefix。
  5. 尽量避免会导致jumbo chunk的key,比如通过age来作为key,同一个age的数据均会放到同一个chunk,这就可能导致chunk不断变大,当超过chunk size时,就会变成jumbo chunk
Sharding策略

Sharding有两种策略:一种是ranged sharding; 另一种是hashed sharding。

  • 如果选择的key,其值域非常接近,尤其是单调递增或者递减(increase or decrease monotonically, 也可以翻译成单向递增递减),就会导致所有的新增操作都会集中在最大的range的shard上(如果是递增);或者集中在最小range的那个shard上(如果是递减)。这就导致无法均匀地进行写操作(因为所有的写操作都集中在一个shard甚至一个chunk上)。而那个最大或最小range的shard将成为系统的瓶颈。具体参考shard-key-monotonic。在这种情况下,我们应当选择hashed sharding.
  • hashed sharding。MongoDB通过hash算法(MD5)来计算key的hash值,然后将数据存储于对应范围的chunk上。由于算法的原因,即使非常"近"的key通过hash之后的值可能会有很大的差异,从而可以更加均匀的分布在不同的chunk之上。hashed sharding的缺点在于当我们的业务有很多范围查询时,(比如uid 5~20),此时由于可能分布在很多不同的chunk上,所以就必须进行广播时操作,即要求所有的shard都进行查询,看是否有在这个范围内的数据,如果有就会交给mongos,mongos再负责将这些数据组合成一个完整的答案给客户端。这种情况下,势必会影响到查询性能。所以具体使用什么样的策略,需要综合考虑很多因素。
Splitting and Migration
Splitting

splitting就是当chunk中存储的数据量超过设定的chunk size时(默认64M),将其分裂成两个chunk,具体参考chunk split
在这里插入图片描述

Migration

数据迁移,是将某一个shard中的chunk数据迁移到另一个shard上。迁移有两种方式,一种是手动迁移,一种自动迁移。大部分时候,MongoDB会通过balancer进行自动迁移,以保持chunk在shard上能够均匀分布。手动迁移仅用于某些特定场景如bulk insert。

关于balancer

balancer是一个后台进程,它会自动判断含有最多chunk的shard和最少chunk的shard之间的chunk数量差达到了设定的threshold,如果达到了,就会自动进行数据迁移。
在这里插入图片描述

作者:geekpy
链接:https://www.jianshu.com/p/a10c812cfb64
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么时候考虑用 Sharded cluster?

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

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

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

如何确定 shard、mongos 数量?

当你决定要使用 Sharded cluste 时,问题来了,应该部署多少个 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 为 N。 (注: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. 方案1: 时间戳作为 shard key,范围分片
  • Bad
  • 新的写入都是连续的时间戳,都会请求到同一个 shard,写分布不均
  • 根据 deviceId 的查询会分散到所有 shard 上查询,效率低
  1. 方案2: 时间戳作为 shard key,hash 分片
  • Bad
  • 写入能均分到多个 shard
  • 根据 deviceId 的查询会分散到所有 shard 上查询,效率低
  1. 方案3:deviceId 作为 shardKey,hash分片(如果 id 没有明显的规则,范围分片也一样)
  • Bad
  • 写入能均分到多个 shard
  • 同一个 deviceId 对应的数据无法进一步细分,只能分散到同一个 chunk,会造成 jumbo chunk
  • 根据 deviceId的查询只请求到单个 shard,不足的时,请求路由到单个 shard 后,根据时间戳的范围查询需要全表扫描并排序
  1. 方案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。

作者:张友东
链接:https://mongoing.com/archives/3397
来源:MongoDB中文社区
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值