MongoDB分片集群

分片集群架构

分片简介

分片是指在将数据进行水平切分之后,将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的"水平分表"。不同的是,mongodb本身就自带了分片管理的能力,对于开发者来说可以做到开箱即用。

mongodb副本集实现了数据的多副本复制以及高可用,但是一个副本集能承载的容量和负载是有限的。当遇到下面的场景时,就需要考虑使用分片了。

  • 存储容量需求超出单机的磁盘容量。
  • 活跃的数据集超出单机内存容量,导致很多请求都要从磁盘读取数据,影响性能。
  • 写IOPS超出单个mongodb节点的写服务能力。

分片集群架构

在分片模式下,存储这些不同的切片数据的节点被称为分片节点,一个分片集群内则包含了多个分片节点。当然,除了分片节点,集群中还需要一些配置节点、路由节点,以保证分片机制的正常运行。下图是一个典型的分片集群架构。

架构说明

  • 数据分片(数据节点):分片用于存储真正的数据,并提供最终的数据读写访问。分片仅仅是一个逻辑的概念,它可以是一个单独的mongod实例,也可以是一个副本集。图中每一个副本集都是一个数据分片。
  • 配置服务器(配置节点):配置服务器包含多个节点,并组成一个副本集结构,配置副本集中保存了整个分片集群中的元数据,其中包含各个集合的分片策略,以及分片的路由表等。
  • 查询路由(路由节点):mongos时分片集群的访问入口,其本身并不持久化数据。mongos启动后,会从配置服务器中加载元数据。之后mongos开始提供访问服务,并将用户的请求正确路由到对应的分片。在分片集群中可以部署多个mongos以分担客户端请求的压力。

分片策略

通过分片概念,可以将一个非常大的集合分散存储到不同的分片上。

什么是chunk

chunk的意思是数据块,一个chunk代表了集合中的"一段数据",例如,用户集合在切分成多个chunk之后如下图所示:

chunk所描述的是范围区间,例如,db.users使用了userId作为分片键,那么chunk就是userId的各个值(或哈希值)的连续区间。集群在操作分片集合时,会根据分片键找到对应的chunk,并向该chunk所在的分片发起操作请求,而chunk的分布在一定程度上会影响数据的读写路径,这由以下两点决定:

  • chunk的切分方式,决定如何找到数据所在的chunk。
  • chunk的分布状态,决定如何找到chunk所在的分片。

分片算法

chunk切分是根据分片策略进行实施的,分片策略的内容包括分片键和分片算法。当前,mongodb支持两种分片算法。

  1. 范围分片
    如下图所示,假设集合根据x字段来分片,x的完整取值范围为[minKey,maxKey](x为整数,这里的minKey、maxKey为整型的最小值和最大值),其将整个取值范围划分为多个chunk,例如:
    • chunk1包含x的取值在[minKey,-75]的所有文档。
    • chunk2包含x取值在[-75,25]之间的所有文档,以此类推。

范围分片能很好的满足范围查询的需求,比如想查询x的值在[-30,10]之间的所有文档,这是mongos直接将请求定位到chunk2所在的分片服务器,就能查询出所有符合条件的文档。范围分片的缺点在于,如果Shard Key有明显递增(或递减)趋势,则新插入的文档会分布到同一个chunk,此时写压力会集中到一个节点,从而导致单点的性能瓶颈。一些常见的导致递增的key如下。

  • 时间值。
  • ObjectId,自动生成的_id由事件、计数器组成。
  • UUID,包含系统时间、时钟序列。
  • 自增整数序列。
  1. 哈希分片

    哈希分片会先事先根据分片键计算出一个新的哈希值(64位整数),再根据哈希值按照范围分片的策略进行chunk的切分。哈希分片与范围分片是互补的,由于哈希算法保证了随机性,所以文档可以更加离散地分布到多个chunk上,这避免了集中写问题。然而,在执行一些范围查询时,哈希分片并不是高效的。因为所有的范围查询都必然导致对所有chunk进行检索,如果集群有10个分片,那么mongos将需要对10个分片分发查询请求。
    哈希分片与范围分片的另一个区别是,哈希分片只能选择单个字段,而范围分片允许采用组合式的多字段作为分片键。

分片键的选择

在选择分片键时,需要根据业务的需求以及范围分片、哈希分片的不同特点进行权衡。一般来说,在设计分片键时需要考虑的因素包括:

  • 分片键的基数,取值基数越大越有利于发展。
  • 分片键的取值分布应该尽可能均匀。
  • 业务读写模式,尽可能分散写压力,而读操作尽可能来自一个或少量的分片。
  • 分片键应该能适应大部分的业务操作。

读写分发模式

数据分发流程

在了解chunk以及分片策略的相关概念之后,我们再来看看分片集群中正常的业务操作流程,如下图所示:

流程说明:

  • mongos在启动后,其内部会维护一份路由表缓存并通过心跳机制与Config Server保持同步。
  • 业务请求进入后,由mongos开始接管。
  • mongos检索本地路由表,根据请求中的分片键信息找到相应的chunk,进一步确定所在的分片。
  • mongos向目标分片发起操作,并返回最终结果。

可以看到,mongos接管了所有的数据读写请求,扮演着代理者的角色。而对于客户端而言,分片模式下的数据操作处理并没有发生什么变化,mongos已经屏蔽了所有的差异。所以,如果对现有的集群开启分片,则几乎不需要修改任何代码就可以保证功能的连续性。

下面列举了在分片场景下各种请求的处理逻辑:

  • 查询请求:查询请求不包含shard key(或部分前缀),必须将查询分发到所有的分片,然后合并查询结果返回给客户端;查询请求包含shard key(或部分前缀),可根据shard key计算出需要查询到的chunk,向对应的分片发送查询请求。
  • 插入请求:写操作必须包含shard key,mongos根据shard key算出文档应该存储到哪个chunk,然后将写请求发送到chunk所在的分片。
  • 更新/删除请求:如果对单个文档执行更新或删除操作,则查询条件必须包含shard key或_id。如果包含shard key,则直接路由到指定的chunk;如果只包含_id,则需要将请求发送至所有的分片。如果对多个文档执行更新或删除操作,则会将请求发送至多个或者全部分片。
  • 其他命令请求:对于除插入/删除/更新/查询外的其他命令的请求处理方式各不相同,有各自的处理逻辑。如果listDatabases命令,会向每个分片以及Config Server转发listDatabases请求,然后将结果进行合并。需要注意的是,在mongodb4.2版本之前,分片键的字段值是不允许修改的,所有对分片键值的修改都将导致报错。

避免广播操作

一些真实的情况或许并不乐观。由于分片集群下的读写模式增加了复杂度,而这种复杂度仍然要求业务上能充分理解它的工作模式。一种常见的情况是,当读写请求中无法为mongos提供足够的"提示信息"时,mongos将不得不向所有分片发送该请求。这种对所有分片的广播操作会导致集群的扩展能力大打折扣,同时也降低可用性。一般认为,某个分片故障只会影响少部分业务当前提必须是合理利用了分片键的作用。一旦遇到广播查询,分片故障产生的影响可能比想象的严重得多。

考虑优化两个方面,以降低影响

  • 重新审视分片键的合理性,至少保证关键业务不依赖广播操作。
  • 进行拆分,建立索引表。例如在使用手机号查询用户信息时,先通过手机号查询索引表获得用户ID,再通过用户ID查询用户表。

保证索引唯一性

分片模式会影响索引的唯一性。由于没有手段保证多个分片上的数据唯一,所以唯一性索引必须与分片键使用相同的字段,或者以分片键作为前缀。

如下面的选择可以避免冲突:

  • 唯一性索引为:{a:1},分片键采用a字段
  • 唯一性索引为:{a:1,b:1},分片键采用a字段

数据均衡

均衡的方式

一种理想的情况是,所有加入的分片都发挥了相当的作用,包括提供更大的存储容量,以及读写访问性能。因此,为了保证分片集群的水平扩展能力,业务数据应当尽可能地保持均匀分布。这里地均匀性包含以下两个方面。

  1. 所有地数据应均匀地分布在不同地chunk上。
  2. 每个分片上地chunk数量尽可能相近。

其中,第1点由业务场景和分片策略决定,而第2点,有两种选择。

  1. 手动均衡
    一种做法是,可以在初始化集合时预先分配一定数量地chunk(仅适合哈希分片),比如给10个分片分配1000个chunk,那么每个分片拥有100个chunk。另一种做法是,可以通过splitAt、moveChunk命令进行手动切分、迁移。
  2. 自动均衡
    开启mongodb集群地自动均衡功能。均衡器会在后台对各分片的chunk进行监控,一旦发现了不均衡状态就会自动进行chunk的搬迁以达到均衡。其中,chunk不均衡通常来自两个方面的因素:一方面,在没有人工干预的情况下,chunk会持续增长并产生分裂,而不断分裂的结果就会出现数量上的不均衡;另一方面,在动态增加分片服务器时,也会出现不均衡的情况。自动均衡是开箱即用的,可以简化集群的管理工作。

chunk分裂

在默认情况下,一个chunk的大小为64mb,该参数由配置的chunksize参数指定。如果持续地向该chunk写入数据,并导致数据量超过了chunk的大小,则mongodb会自动进行分裂,将该chunk切分为两个相同大小的chunk。务必记住,chunk分裂是基于分片键进行的,如果分片键的基数太小,则可能因为无法分裂而出现jumbo chunk(超大块)的问题。例如,对db.users使用gender作为分片键,由于同一种性别的用户数可能达到数千万,分裂程序不知道如何对分片键的一个单值进行切分,因此最终导致在一个chunk上集中存储了大量的user记录(总大小超过64mb)。

jumbo chunk对水平扩展有负面作用,该情况不利于数据的均衡,业务上应尽可能避免。当然,如果能接受这种情况,则另当别论。

一些写入压力过大的情况会导致chunk多次分裂失败,最终当chunk中的文档数大于1.3*avgObjectSize时会导致无法迁移。

自动均衡

mongodb的数据均衡器运行于primary config server(配置服务器的主节点)上,而该节点也同时会控制chunk数据的搬迁流程。

流程说明:

  • 分片shard0在持续的业务写入压力下,产生了chunk分裂。
  • 分片服务器通知Config Server进行元数据更新。
  • Config Server的自动均衡器对chunk分布进行检查,发现shard0和shard1的chunk数差异达到了阈值,向shard0下发moveChunk命令以执行chunk迁移。
  • shard0执行指令,将指定数据块复制到shard1。该阶段会完成索引、chunk数据的复制,而且在整个过程中业务侧对数据的操作仍然会指向shard0;所以,在第一轮复制完毕之后,目标shard1会向shard0确认是否还存在增量更新的数据,如果存在则继续复制。
  • shard0完成迁移后发送通知,此时Config Server开始更新元数据库,将chunk的位置更新为目标shard1。在更新完元数据库后并确保没有关联cursor的情况下,shard0会删除被迁移的chunk副本。
  • Config Server通知mongos服务器更新路由表。此时,新的业务请求将被路由到shard1。

迁移阈值

均衡器对于数据的"不均衡状态"判定是根据两个分片上的chunk个数差异来进行的

chunk个数 迁移阈值
少于20 2
20~79 4
80及以上 8

迁移速度

数据均衡的整个过程并不是很快,影响MongoDB均衡速度的几个选项如下:

  • _secondaryThrottle:用于调整迁移数据写到目标分片的安全级别。如果没有设定,则会使用w:2选项,即至少一个备节点确认写入迁移数据后才算成功。从MongoDB 3.4版本开始,_secondaryThrottle被默认设定为false, chunk迁移不再等待备节点写入确认。
  • _waitForDelete:在chunk迁移完成后,源分片会将不再使用的chunk删除。如果_waitForDelete是true,那么均衡器需要等待chunk同步删除后才进行下一次迁移。该选项默认为false,这意味着对于旧chunk的清理是异步进行的。
  • 并行迁移数量:在早期版本的实现中,均衡器在同一时刻只能有一个chunk迁移任务。从MongoDB 3.4版本开始,允许n个分片的集群同时执行n/2个并发任务。

随着版本的迭代,mongodb的迁移能力也在逐步提升。从mongodb4.0版本开始,支持在迁移数据的过程中并发读取源端和写入目标端,迁移的整体性能提升了约40%

数据均衡带来的问题

数据均衡会影响性能,在分片间进行数据块的迁移是一个"繁重"的工作,很容易带来磁盘I/O使用率飙升,或业务时延陡增等一些问题。因此,建议尽可能提升磁盘能力,如使用SSD。除此之外,我们还可以将数据均衡的窗口对齐到业务的低峰期以降低影响。

登录mongos,在config数据库上更新配置,代码如下:

javascript 复制代码
use config
 
sh.setBalancerState(true)
 
db.settings.update(
 
    {_id:"balancer"},
 
    {$set:{activeWindow:{start:"02:00",stop:"04:00"}}},
 
    {upsert:true}
 
)

在上述操作中启用了自动均衡器,同时在每天的凌晨2点到4点运行数据均衡操作

对分片集合中执行count命令可能会产生不准确的结果,mongos在处理count命令时会分别向各个分片发送请求,并累加最终的结果。如果分片上正在执行数据迁移,则可能导致重复的计算。替代办法是使用db.collection.countDocuments({})方法,该方法会执行聚合操作进行实时扫描,可以避免元数据读取的问题,但需要更长时间。

在执行数据库备份的期间,不能进行数据均衡操作,否则会产生不一致的备份数据。在备份操作之前,可以通过如下命令确认均衡器的状态:

  • sh.getBalancerState():查看均衡器是否开启。
  • sh.isBalancerRunning():查看均衡器是否正在运行。
  • sh.getBalancerWindow():查看当前均衡的窗口设定。

搭建mongodb分片集群

各位读者可以参考下面这篇博客:
猿创征文|MongoDB数据库 分片集群搭建部署实战
MongoDB分片集群(sharded cluster)搭建过程

分片标签

在大多数情况下,应该将数据的分布交给mongodb的均衡器自行处理。这是显而易见的,因为人工进行数据块的迁移时一项非常繁琐且容易出错的工作。

而无论时选择哈希分片还是范围分片,都无法决定chunk所在的位置。换句话说,分片策略只影响数据所在的chunk,而chunk所在的分片则是由均衡器来调整的,这具有非常大的随机性。那么,是否存在干预的手段呢?mongodb允许通过为分片添加标签(tag)的方式来控制数据分发。一个标签可以关联到多个分片区间。如此达到的结果是,均衡器会优先考虑chunk是否正处于某个分片区间上(被完全包含),如果是则会将chunk迁移到分片区间所关联的分片,否则按一般情况处理。

分片标签适用于一些特定的场景。例如,集群中可能同时存在OLTP和OLAP处理,一些系统日志的重要性相对较低,而且主要以少量的统计分析为主。为了便于单独扩展,我们可能希望将日志与实时类的业务数据分开,此时就可以使用标签。

为了让分片拥有指定的标签,需执行addShardTag命令

javascript 复制代码
sh.addShardTag("shard01","oltp")
 
sh.addShardTag("shard02","oltp")
 
sh.addShardTag("shard03","olap")

实时计算的集合应该属于oltp标签,声明TagRange

javascript 复制代码
sh.addTagRange("main.devices",{shardKey:MinKey},{shardKey:MaxKey},"oltp")

而离线计算的集合,则属于olap标签

javascript 复制代码
sh.addTagRange("other.systemLogs",{shardKey:MinKey},{shardKey:MaxKey},"olap")

main.devices集合将被均衡地分发到shard01、shard02分片上,而other.systemLogs集合将被单独分发到shard03分片上。对于没有做任何声明的集合,则会被分发到任意一个分片上。

注意:标签特性需要借助自动均衡器的功能,数据分发不是立即生效的,而是由均衡器在后台进行数据块的腾挪后所达到的效果。

参考资料

https://blog.csdn.net/Zhuxiaoyu_91/article/details/144232019
猿创征文|MongoDB数据库 分片集群搭建部署实战
MongoDB分片集群(sharded cluster)搭建过程

相关推荐
cookqq25 分钟前
mongodb源码分析session异步接受asyncSourceMessage()客户端流变Message对象
数据库·sql·mongodb·nosql
呼拉拉呼拉37 分钟前
Redis故障转移
数据库·redis·缓存·高可用架构
什么都想学的阿超40 分钟前
【Redis系列 04】Redis高可用架构实战:主从复制与哨兵模式从零到生产
数据库·redis·架构
pp-周子晗(努力赶上课程进度版)1 小时前
【MySQL】视图、用户管理、MySQL使用C\C++连接
数据库·mysql
斯特凡今天也很帅1 小时前
clickhouse常用语句汇总——持续更新中
数据库·sql·clickhouse
超级小忍2 小时前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税3 小时前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
hshpy3 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
月初,3 小时前
MongoDB学习和应用(高效的非关系型数据库)
学习·mongodb·nosql
文牧之4 小时前
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS
运维·数据库·oracle