MongoDB 4.x分片、集群1、分片集群架构1.1、分片简介1.2、分片集群架构2、分片策略2.1、什么是chunk2.2、分片算法2.2.1、范围分片range sharding2.2.2、哈希分片hash sharding2.3、分片键的选择3、读写分发模式3.1、数据分发流程3.2、避免广播操作3.3、保证索引唯一性4、数据均衡4.1、均衡的方式4.2、chunk分裂4.3、自动均衡4.3.1、迁移阈值4.3.2、迁移速度4.4、数据均衡带来的问题5、使用mtools搭建集群5.1、mtools介绍5.2、准备工作5.3、安装mtools5.4、创建分片集群5.5、停止、启动6、使用分片集群6.1、向分片集合写入数据6.2、查询数据的分布7、使用标签7.1、分片标签7.2、使用场景1、分片集群架构1.1、分片简介分片shard是指在将数据进行水平切分之后将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”​。不同的点在于MongoDB本身就自带了分片管理的能力对于开发者来说可以做到开箱即用。我们知道MongoDB副本集实现了数据的多副本复制及高可用但是一个副本集能承载的容量和负载是有限的。在你遇到下面的场景时就需要考虑使用分片了。存储容量需求超出单机的磁盘容量。活跃的数据集超出单机内存容量导致很多请求都要从磁盘读取数据影响性能。写IOPS超出单个MongoDB节点的写服务能力。1.2、分片集群架构在分片模式下存储这些不同的切片数据的节点被称为分片节点一个分片集群shard cluster内则包含了多个分片节点。当然除了分片节点集群中还需要一些配置节点、路由节点以保证分片机制的正常运作。下图所示是一个典型的分片集群架构。数据分片分片用于存储真正的数据并提供最终的数据读写访问。分片仅仅是一个逻辑的概念它可以是一个单独的mongod实例也可以是一个副本集。图6-1中的Shard0、Shard1都是一个副本集分片。在生产环境中也一般会使用副本集的方式这是为了防止数据节点出现单点故障。配置服务器Config Server​配置服务器包含多个节点并组成一个副本集结构对应于图6-1中的ConfigReplSet。配置副本集中保存了整个分片集群中的元数据其中包含各个集合的分片策略以及分片的路由表等。查询路由mongos​mongos是分片集群的访问入口其本身并不持久化数据。mongos启动后会从配置服务器中加载元数据。之后mongos开始提供访问服务并将用户的请求正确路由到对应的分片。在分片集群中可以部署多个mongos以分担客户端请求的压力。2、分片策略通过分片功能可以将一个非常大的集合分散存储到不同的分片上如图所示。假设这个集合大小是1TB那么拆分到4个分片上之后每个分片存储256GB的数据。这个当然是最理想化的场景实质上很难做到如此绝对的平衡。一个集合在拆分后如何存储、读写与该集合的分片策略设定是息息相关的。在了解分片策略之前我们先来介绍一下chunk。2.1、什么是chunkchunk的意思是数据块一个chunk代表了集合中的“一段数据”​例如用户集合db.users在切分成多个chunk之后如图所示。chunk所描述的是范围区间例如db.users使用了userId作为分片键那么chunk就是userId的各个值或哈希值的连续区间。集群在操作分片集合时会根据分片键找到对应的chunk并向该chunk所在的分片发起操作请求而chunk的分布在一定程度上会影响数据的读写路径这由以下两点决定chunk的切分方式决定如何找到数据所在的chunk。chunk的分布状态决定如何找到chunk所在的分片。2.2、分片算法chunk切分是根据分片策略进行实施的分片策略的内容包括分片键和分片算法。当前MongoDB支持两种分片算法下面具体介绍。2.2.1、范围分片range sharding如图所示假设集合根据x字段来分片x的完整取值范围为[minKey, maxKey]​x为整数这里的minKey、maxKey为整型的最小值和最大值​其将整个取值范围划分为多个chunk例如chunk1包含x的取值在[minKey-75的所有文档。chunk2包含x取值在[-7525之间的所有文档依此类推。范围分片能很好地满足范围查询的需求比如想查询x的值在[-3010]之间的所有文档这时mongos直接将请求定位到chunk2所在的分片服务器就能查询出所有符合条件的文档。范围分片的缺点在于如果Shard Key有明显递增或者递减趋势则新插入的文档会分布到同一个chunk此时写压力会集中到一个节点从而导致单点的性能瓶颈。一些常见的导致递增的Key如下。时间值。ObjectId自动生成的_id由时间、计数器组成。UUID包含系统时间、时钟序列。自增整数序列。2.2.2、哈希分片hash sharding哈希分片会先事先根据分片键计算出一个新的哈希值64位整数​再根据哈希值按照范围分片的策略进行chunk的切分。哈希分片与范围分片是互补的由于哈希算法保证了随机性所以文档可以更加离散地分布到多个chunk上这避免了集中写问题。然而在执行一些范围查询时哈希分片并不是高效的。因为所有的范围查询都必然导致对所有chunk进行检索如果集群有10个分片那么mongos将需要对10个分片分发查询请求。哈希分片与范围分片的另一个区别是哈希分片只能选择单个字段而范围分片允许采用组合式的多字段作为分片键。2.3、分片键的选择在选择分片键时需要根据业务的需求及范围分片、哈希分片的不同特点进行权衡。一般来说在设计分片键时需要考虑的因素包括分片键的基数cardinality​取值基数越大越有利于扩展。分片键的取值分布应该尽可能均匀。业务读写模式尽可能分散写压力而读操作尽可能来自一个或少量的分片。分片键应该能适应大部分的业务操作。3、读写分发模式3.1、数据分发流程在了解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命令会向每个分片及ConfigServer转发listDatabases请求然后将结果进行合并。需要注意的是在MongoDB 4.2版本之前分片键的字段值是不允许修改的所有对分片键值的修改都将导致报错。3.2、避免广播操作一些真实的情况或许并不乐观。由于分片集群下的读写模式增加了复杂度而这种复杂度仍然要求业务上能充分理解它的工作模式。一种常见的情况是当读写请求中无法为mongos提供足够的“提示信息”时mongos将不得不向所有分片发送该请求如图所示。有不少的业务场景会碰到这样的问题尤其是在分片键无法满足多变的业务查询需求时这种对所有分片的广播操作会导致集群的扩展能力大打折扣同时也降低了可用性。一般认为某个分片故障只会影响少部分业务但前提必须是合理利用了分片键的作用。一旦遇到广播查询分片故障产生的影响可能比想象的要严重得多如图所示。如何降低影响呢建议可以考虑优化两个方面。重新审视分片键的合理性至少保证关键业务不依赖广播操作。进行拆分建立索引表。例如在使用手机号查询用户信息时先通过手机号查询索引表获得用户ID再通过用户ID查询用户表。3.3、保证索引唯一性分片模式会影响索引的唯一性。由于没有手段保证多个分片上的数据唯一所以唯一性索引必须与分片键使用相同的字段或者以分片键作为前缀。如下面的选择可以避免冲突。唯一性索引为{a1}分片键采用a字段。唯一性索引为{a1b1}分片键采用a字段。4、数据均衡4.1、均衡的方式一种理想的情况是所有加入的分片都发挥了相当的作用包括提供更大的存储容量以及读写访问性能。因此为了保证分片集群的水平扩展能力业务数据应当尽可能地保持均匀分布。这里的均匀性包含以下两个方面。所有的数据应均匀地分布于不同的chunk上。每个分片上的chunk数量尽可能是相近的。其中第1点由业务场景和分片策略来决定而关于第2点我们有以下两种选择。1手动均衡。一种做法是可以在初始化集合时预分配一定数量的chunk仅适用于哈希分片​比如给10个分片分配1000个chunk那么每个分片拥有100个chunk。另一种做法则是可以通过splitAt、moveChunk命令进行手动切分、迁移。2自动均衡。开启MongoDB集群的自动均衡功能。均衡器会在后台对各分片的chunk进行监控一旦发现了不均衡状态就会自动进行chunk的搬迁以达到均衡。其中chunk不均衡通常来自于两方面的因素一方面在没有人工干预的情况下chunk会持续增长并产生分裂split​而不断分裂的结果就会出现数量上的不均衡另一方面在动态增加分片服务器时也会出现不均衡的情况。自动均衡是开箱即用的可以极大简化集群的管理工作。4.2、chunk分裂在默认情况下一个chunk的大小为64MB该参数由配置的chunksize参数指定。如果持续地向该chunk写入数据并导致数据量超过了chunk大小则MongoDB会自动进行分裂将该chunk切分为两个相同大小的chunk如图所示。务必记住chunk分裂是基于分片键进行的如果分片键的基数太小则可能因为无法分裂而会出现jumbo chunk超大块的问题。例如对db.users使用gender性别作为分片键由于同一种性别的用户数可能达到数千万分裂程序并不知道如何对分片键gender的一个单值进行切分因此最终导致在一个chunk上集中存储了大量的user记录总大小超过64MB​。jumbo chunk对水平扩展有负面作用该情况不利于数据的均衡业务上应尽可能避免。当然如果能接受这种情况则另当别论了。一些写入压力过大的情况可能会导致chunk多次失败split​最终当chunk中的文档数大于1.3×avgObjectSize时会导致无法迁移。此外在一些老版本中如果chunk中的文档数超过250000个也会导致无法迁移。4.3、自动均衡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完成迁移后发送通知此时ConfigServer开始更新元数据库将chunk的位置更新为目标shard1。在更新完元数据库后并确保没有关联cursor的情况下shard0会删除被迁移的chunk副本。Config Server通知mongos服务器更新路由表。此时新的业务请求将被路由到shard1。4.3.1、迁移阈值均衡器对于数据的“不均衡状态”判定是根据两个分片上的chunk个数差异来进行的其阈值见表。4.3.2、迁移速度数据均衡的整个过程并不是很快影响MongoDB均衡速度的几个选项如下。_secondaryThrottle用于调整迁移数据写到目标分片的安全级别。如果没有设定则会使用w2选项即至少一个备节点确认写入迁移数据后才算成功。从MongoDB 3.4版本开始_secondaryThrottle被默认设定为false, chunk迁移不再等待备节点写入确认。_waitForDelete在chunk迁移完成后源分片会将不再使用的chunk删除。如果_waitForDelete是true那么均衡器需要等待chunk同步删除后才进行下一次迁移。该选项默认为false这意味着对于旧chunk的清理是异步进行的。并行迁移数量在早期版本的实现中均衡器在同一时刻只能有一个chunk迁移任务。从MongoDB 3.4版本开始允许n个分片的集群同时执行n/2个并发任务。随着版本的迭代MongoDB迁移的能力也在逐步提升。从MongoDB 4.0版本开始支持在迁移数据的过程中并发地读取源端和写入目标端迁移的整体性能提升了约40%。这样也使得新加入的分片能更快地分担集群的访问读写压力。4.4、数据均衡带来的问题数据均衡会影响性能在分片间进行数据块的迁移是一个“繁重”的工作很容易带来磁盘I/O使用率飙升或业务时延陡增等一些问题。因此建议尽可能提升磁盘能力如使用SSD。除此之外我们还可以将数据均衡的窗口对齐到业务的低峰期以降低影响。登录mongos在config数据库上更新配置代码如下在上述操作中启用了自动均衡器同时在每天的凌晨2点到4点运行数据均衡操作。对分片集合中执行count命令可能会产生不准确的结果mongos在处理count命令时会分别向各个分片发送请求并累加最终的结果。如果分片上正在执行数据迁移则可能导致重复的计算。替代办法是使用collection.countDocument方法该方法会执行聚合操作进行实时扫描可以避免元数据读取的问题但需要更长时间。在执行数据库备份的期间不能进行数据均衡操作否则会产生不一致的备份数据。在备份操作之前可以通过如下命令确认均衡器的状态。sh.getBalancerState查看均衡器是否开启。sh.isBalancerRunning查看均衡器是否正在运行。sh.getBalancerWindow查看当前均衡的窗口设定。5、使用mtools搭建集群本次我们将安装一个本地化的MongoDB分片集群进行测试。由于分片架构的搭建工作相对烦琐为了简化笔者选用mtools工具来快速构建集群。5.1、mtools介绍mtools是一套基于Python实现的MongoDB工具集其包括MongoDB日志分析、报表生成及简易的数据库安装等功能。它由MongoDB原生的工程师单独发起并做开源维护目前已经有大量的使用者。mtools所包含的一些常用组件如下。mlaunch支持快速搭建本地测试环境可以是单机、副本集、分片集群。mlogfilter日志过滤组件支持按时间检索慢查询、全表扫描操作支持通过多个属性进行信息过滤支持输出为JSON格式。mplotqueries支持将日志分析结果转换为图表形式依赖tkinterPython图形模块和matplotlib模块。mlogvis支持将日志分析结果转换为一个独立的HTML页面实现与mplotqueries同样的功能。5.2、准备工作安装MongoDB将MongoDB可执行程序包下载到本地。mtools需要调用MongoDB的二进制程序来启动数据库因此需保证Path路径中包含{MONGODB_HOME}/bin这个目录。-安装Python需选用Python 3.6、3.7或3.8版本可以从Python官网中下载。为了检查当前Python的版本可以执行version命令结果如下python-V5.3、安装mtoolsPython 3.4及以上版本都自带pip工具可以利用它来安装mtools。安装依赖模块代码如下安装mtools代码如下5.4、创建分片集群准备分片集群使用的工作目录代码如下执行mlaunch init初始化集群代码如下选项说明--sharded 2启用分片集群模式分片数为2。--replicaset--nodes 3采用3节点的副本集架构即每个分片为一致的副本集模式。--config 3--csrs配置服务器采用3节点的副本集架构模式csrs是指Config Server as aReplica Set。--mongos 3启动3个mongos实例进程。--port 27050集群将以27050作为起始端口集群中的各个实例基于该端口向上递增。--noauth不启用鉴权。如果执行成功那么片刻后可以看到如下输出至此已经完成了分片集群的初始化及启动。mlaunch list命令可以对当前集群的实例状态进行检查代码如下此时可以看到各个实例的运行状态包括进程号以及监听的端口等。检查分片实例连接mongos查看分片实例的情况代码如下5.5、停止、启动如果希望停止集群则可以使用mlaunch stop命令代码如下再次启动集群可以使用mlaunch start命令代码如下使用mtools搭建测试集群是相当方便的相比手工搭建的方式可缩减大量的时间。6、使用分片集群基于前面已经创建好的分片集群下面我们来进行一些测试。为了使集合支持分片需要先开启database的分片功能代码如下执行shardCollection命令对集合执行分片初始化代码如下data.book集合将bookId作为分片键并采用了哈希分片策略除此以外​“numInitialChunks4”表示将初始化4个chunk。numInitialChunks必须和哈希分片策略配合使用。而且这个选项只能用于空的集合如果已经存在数据则会返回错误。6.1、向分片集合写入数据向data.book集合写入一批数据代码如下稍等片刻脚本将写入10万条记录可执行count操作进行检查代码如下6.2、查询数据的分布执行getShardDistribution命令代码如下输出如下可以看到10万个文档总共占26.87MB的空间而且两个分片的数据量分布相当。这是因为我们在一开始执行了chunk的预分配而且分片键的取值是相对离散的。7、使用标签7.1、分片标签在大多数情况下应该将数据的分布交给MongoDB的均衡器自行处理。这是显而易见的因为人工进行数据块的迁移moveChunk是一项非常烦琐而且容易出错的工作。而无论是选择哈希分片还是范围分片都无法决定chunk所在的位置。换句话说分片策略只影响数据所在的chunk而chunk所在的分片则是由均衡器来调整的这具有非常大的随机性。那么是否存在干预的手段呢答案是有的。MongoDB允许通过为分片添加标签tag的方式来控制数据分发。一个标签可以关联到多个分片区间TagRange​。如此达到的结果是均衡器会优先考虑chunk是否正处于某个分片区间上被完全包含​如果是则会将chunk迁移到分片区间所关联的分片否则按一般情况处理。7.2、使用场景分片标签适用于一些特定的场景。例如集群中可能同时存在OLTP和OLAP处理一些系统日志的重要性相对较低而且主要以少量的统计分析为主。为了便于单独扩展我们可能希望将日志与实时类的业务数据分开此时就可以使用标签。为了让分片拥有指定的标签需执行addShardTag命令代码如下实时计算的集合应该属于oltp标签声明TagRange的代码如下而离线计算的集合则属于olap标签代码如下如此安排之后main.devices集合将被均衡地分发到shard01、shard02、shard03分片上而other.systemLogs集合将被单独分发到shard04分片上。对于没有做任何声明的集合则会被分发到任意一个分片上。务必注意的是标签特性需要借助自动均衡器balancer的功能数据分发不是立即生效的而是由均衡器在后台进行数据块的腾挪后所达到的效果。