分片集群为 MongoDB 的分布式版本,相较副本集,分片集群数据被均衡的分布在不同分片中, 不仅大幅提升了整个集群的数据容量上限,也将读写的压力分散到不同分片,以解决副本集性能瓶颈的难题,但分片集群的架构更加复杂,本文重点介绍使用腾讯云 MongoDB 分片集群时的注意事项。
分片集群组件
一个 MongoDB 分片集群由如下三个组件构成,缺一不可:
shard:每个分片是整体数据的一部分子集,每个分片都部署为副本集。
mongos:充当查询路由器,提供客户端应用程序和分片集群之间的接口。
config servers:配置服务器存储集群的元数据和配置,包括权限认证相关。
分片集群 sharding 方式及性能影响
MongoDB 分片集群提供三种 Sharding(数据分布)方式,分别为基于范围、基于 Hash、基于 zone/tag。不同的 Sharding 方式使用不同的业务,也会对性能产生不同的影响。
基于范围
优势:分片键范围查询性能较好,读性能较好。
劣势:数据分布可能不均匀,存在热点。
基于 Hash
优势:数据分布均匀,写性能较好,适用于日志、物联网等高并发场景。
劣势:范围查询效率较低。
基于 zone/tag
若数据具备一些天然的区分,如基于地域、时间等标签,数据可以基于标签来做区分。
优势:数据分布较为合理。
分片键的选择
分片键是文档中的某一个字段,用来进行路由查询。
选择合适的片键对 sharding 效率影响很大,主要基于如下四个因素:
取值基数
取值基数建议尽可能大,如果用小基数的片键,因为备选值有限,那么块的总数量就有限,随着数据增多,块的大小会越来越大,导致水平扩展时移动块会非常困难。
例如:选择年龄做一个基数,范围最多只有100个,随着数据量增多,同一个值分布过多时,导致 chunck 的增长超出 chuncksize 的范围,引起 jumbo chunk,从而无法迁移,导致数据分布不均匀,性能瓶颈。
取值分布
取值分布建议尽量均匀,分布不均匀的片键会造成某些块的数据量非常大,同样有上面数据分布不均匀,性能瓶颈的问题。
查询带分片
查询时建议带上分片,使用分片键进行条件查询时,mongos 可以直接定位到具体分片,否则 mongos 需要将查询分发到所有分片,再等待响应返回。
避免单调递增或递减
单调递增的 sharding key,数据文件挪动小,但写入会集中,导致最后一篇的数据量持续增大,不断发生迁移,递减同理。
综上,在选择片键时要考虑以上4个条件,尽可能满足更多的条件,才能降低 MoveChuncks 对性能的影响,从而获得最优的性能体验。
修改分片键值
MongoDB 4.2 之前的版本,文档的分片键字段值不可变。
从 MongoDB 4.2 版本开始,除非分片键字段是不可变的
_id
字段,否则您可以更新文档的分片键值。 若要更新,请使用以下方式来更新文档的分片键值:命令 | 方法 |
update with multi: false | db.collection.update() with multi: false |
- | 如果分片键修改导致将文档移动到另一个分片,则在批量操作中不能指定多个分片键修改;即批量大小为 1 。 如果分片键修改不会导致将文档移动到另一个分片,则可以在批量操作中指定多个分片键修改。 |
修改分片键时需要注意:
必须在事务中或以可重试写入方式在 mongos 上运行,不要直接在分片上执行操作。
您必须在查询过滤器的完整分片键上包含相等条件。 例如,如果一个分片集合内使用
{country:1,userid:1}
作为分片键,要想更新文档的分片键,则必须在查询过滤器中包含 country:,userid:
,也可以根据需要在查询中包括其他字段。分片集群 balance 介绍及相关参数
在一个分片集群内部,MongoDB 会把数据分为 chunks,后台进程 balancer 负责 chunk 的迁移,从而均衡各个 shard server 的负载,每个 chunk 包含一部分数据,chunk 的产生和迁移会导致 balance 的产生。
说明:
系统初始仅1个 chunk,chunk size 默认值64MB。
chunck 迁移时会造成集群的读写性能下降,因此需要通过适当配置 balance 活动窗口来避免 balance 对业务高峰期的影响,也可以通过命令来关闭 balance。
查看 mongo 集群是否开启了 balance
mongos> sh.getBalancerState()true
也可通过执行 sh.status() 查看 balance 状态。
查看是否正在有数据的迁移
mongos> sh.isBalancerRunning()false
设置 balance 窗口
修改 balance 窗口的时间:
db.settings.update({ _id: "balancer" },{ $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } } },{ upsert: true })
删除 balance 窗口:
use configdb.settings.update({ _id : "balancer" }, { $unset : { activeWindow : true } })
关闭 balance
默认 balance 的运行可以在任何时间,迁移只需要迁移的 chunk,如需关闭 balance,可执行下列命令:
sh.stopBalancer()sh.getBalancerState()
停止 balance 后,查看是否有迁移进程正在执行,可执行下列命令:
use configwhile( sh.isBalancerRunning() ) {print("waiting...");sleep(1000);}
打开 balance
如您需要准备重新打开 balance,可执行下列命令:
sh.setBalancerState(true)
当驱动版本不支持 sh.startBalancer() 时,可执行下列命令来重新打开 balance:
use configdb.settings.update( { _id: "balancer" }, { $set : { stopped: false } } , { upsert: true } )
集合的 balance
关闭某个集合的 balance:
sh.disableBalancing("students.grades")
打开某个集合的 balance:
sh.enableBalancing("students.grades")
查看某个集合是否开启了 balance:
db.getSiblingDB("config").collections.findOne({_id : "students.grades"}).noBalance