MongoDB Sharding 机制分析

MongoDB Sharding 机制分析

MongoDB 是一种流行的非关系型数据库。做为一种文档型数据库,除了有无 schema 的灵活的数据结构,支持复杂、丰富的查询功能外,MongoDB 还自带了至关强大的 sharding 功能。mysql

要说 MongoDB 的 sharding,首先说说什么是 sharding。所谓 sharding 就是将数据水平切分到不一样的物理节点。这里着重点有两个, 一个是水平切分,另外一个是物理节点。通常咱们说数据库的分库分表有两种类型。一种是水平划分,好比按用户 id 取模,按余数划分用户的数据,如博客文章等;另外一种是垂直划分,好比把用户信息放一个节点,把文章放另外一个节点,甚至能够把文章标题基本信息放一个节点,正文放另外一个节点。sharding 指的是前一种。而物理节点,主要是和如 mysql 等提供的表分区区分。表分区虽然也对数据进行了划分,可是这些分区仍然是在同一个物理节点上。sql

那么,为何要使用 sharding 呢?sharding 解决了什么问题,带来了什么好处呢?很多人都经历过本身的网站、应用由小到大,用户愈来愈多,访问量愈来愈大,数据量也愈来愈大。这固然是好事。可是,之前一个服务器就能够抗下的数据库如今不行了。开始还能够作作优化,再加个缓存。可是再后来,不管如何都不是一个服务器能承受了。数据量也会很快超过服务器的硬盘容量。这时候,就不得不进行拆分,作 sharding 了。这里,sharding 能够利用上更多的硬件资源来解决了单机性能极限的问题。此外,将数据进行水平切分后,还会减少每一个索引的体积。因为通常数据库的索引都是 B树 结构,索引体积减少后,索引深度也会随之减少,索引查找的速度也会随之提升。对于一些比较费时的统计查询,还能够由此把计算量分摊到多个机器上同时运算,经过分布式来提升速度。mongodb

虽然 sharding 有不少好处,可是传统数据库作 sharding 会遇到不少麻烦事。首先是扩容和初始化的问题。好比,原来按用户 id 模 5,分了 5 个节点,后来随着数据增加,这 5 个也不够用了,须要再增长。这时候若是改为 10 个,则至少要挪动一半数据。若是不是整倍数,好比扩展到 7 个节点,那绝大部分数据都会被挪一遍。对于已经作了 sharding 的大规模数据库来讲,这是一件至关可怕的事情。并且在数据迁移期间,一般都没法继续提供服务,这将形成很长时间的服务中断。对从一个未作 sharding 的数据库开始建立也是一样。若是使用虚拟节点,好比将数据划分红 1000 个虚拟节点,而后经过映射关系来找到对应的物理节点,能够有所改善。但仍然没法避免迁移过程当中的服务中断。另外一个麻烦事是数据路由。数据拆分之后,应用程序就须要去定位数据位于哪一个节点。还须要将涉及多个节点的查询的结果合并起来。这个工做若是没有使用数据库中间件的话,就须要花很多功夫本身实现,即便使用了中间件,也很难作到透明。因为关系型数据库功能的复杂性,不少功能在 sharding 上将没法正常使用。好比 join 、事务等。所以会形成应用层的大量修改测试工做。数据库

sharding 会有这许多麻烦事,那么 MongoDB 的 sharding 又如何呢?后端

MongoDB 的 sharding 的特点就是自动化。具体体现为能够动态扩容、自动平衡数据、以及透明的使用接口。能够从一个普通的 replica set,或者单个实例平滑升级,能够动态增长删除节点,响应数据快速增加。能够自动在节点间平衡数据量,避免负载集中在少数节点,而在这期间不影响数据库读写访问。对客户端,可使用彻底相同的驱动,大部分功能可用,基本不须要更改任何代码。缓存

MongoDB 的 sharding 有如此强大的功能,它的实现机制是怎样的呢?下图就是 MongoDB sharding 的结构图。服务器

从图中能够看出,MongoDB sharding 主要分为 3 大部分。shard 节点、config 节点和 config 节点。对客户端来讲,直接访问的是 图中绿色的 mongos 节点。背后的 config 节点和 shard 节点是客户端不能直接访问的。mongos 的主要做用是数据路由。从元数据中定位数据位置,合并查询结果。另外,mongos 节点还负责数据迁移和数据自动平衡,并做为 sharding 集群的管理节点。它对外的接口就和普通的 mongod 同样。所以,可使用标准 mongodb 客户端和驱动进行访问。mongos 节点是无状态的,自己不保存任何数据和元数据,所以能够任意水平扩展,这样任意一个节点发生故障均可以很容易的进行故障转移,不会形成严重影响。数据结构

其中蓝色的 shard 节点就是实际存放数据的数据节点。每一个 shard 节点能够是单个 mongod 实例,也可使一个 replica set 。一般在使用 sharding 的时候,都会同时使用 replica set 来实现高可用,以避免集群内有单个节点出故障的时候影响服务,形成数据丢失。同时,能够进一步经过读写分离来分担负载。对于每一个开启 sharding 的 db 来讲,都会有一个 默认 shard 。初始时,第一个 chunk 就会在那里创建。新数据也就会先插入到那个 shard 节点中去。app

图中紫色的 config 节点存储了元数据,包括数据的位置,即哪些数据位于哪些节点,以及集群配置信息。config 节点也是普通的 mongod 。如图所示,一组 config 节点由 3 个组成。这 3 个 config 节点并不是是一个 replica set。它们的数据同步是由 mongos 执行两阶段提交来保证的。这样是为了不复制延迟形成的元数据不一样步。config 节点必定程度上实现了高可用。在一个或两个节点发生故障时,config 集群会变成只读。但此时,整个 sharding 集群仍然能够正常读写数据。只是没法进行数据迁移和自动均衡而已。分布式

config 节点里存放的元数据都有些啥呢?连上 mongos 后,

use config; show collections

结果是

settings
shards
databases
collections
chunks
mongos
changelog

还能够进一步查看这些东西的数据。这个 config 库就是后端 config 节点上的数据的映射,提供了一个方便的读取元数据的入口。这些 collection 里面都是什么呢? settings 里是 sharding 的配置信息,好比数据块大小,是否开启自动平衡。shards 里存放的是后端 shard 节点的信息,包括 ip,端口等。databases 里存放的是数据库的信息,是否开启 sharding,默认 shard 等。collections 中则是哪些 collection 启用了 sharding,已经用了什么 shard key。chunks 里是数据的位置,已经每一个 chunk 的范围等。mongos 里是关于 mongos 的信息,changelog 是一个 capped collection,保存了最近的 10m 元数据变化记录。

mongodb sharding 的搭建也很容易。简单的几步就能完成。

先启动若干 shard 节点

mongod --shardsvr

启动 3 个 config 节点

mongod --configsvr

启动 mongos

mongos --configdb=192.168.1.100, 192.168.1.101, 192.168.1.102

这里,–shardsvr 参数只起到修改默认端口为 27018 做用,–configsvr 则修改默认端口为 27019 以及默认路径为 /data/configdb。此外并无什么直接做用。实际使用时,也能够本身指定端口和数据路径。此外,这两个参数的另外一个做用就是对进程进行标记,这样在 ps aux 的进程列表里,就很容易肯定进程的身份。–configdb 参数就是 config 节点的地址。若是更改了默认端口,则须要在这里加上。

而后咱们把数据节点加入集群:在 mongos 上运行

use admin
sh.addShard(’ [hostname]:[port]’)

若是使用的事 replicaSet,则是

use admin
sh.addShard(’replicaSetName/,,’)

接着就是启用 sharding 了。

sh.enableSharding(dbname)
sh.shardCollection(fullName, key, unique)

这样就能够了。仍是很简单的吧。若是 collection 里有数据,则会自动进行数据平衡。

以前说过,mongodb 的 sharding 把数据分红了数据块(chunk)来进行管理。如今来看看 chunk 到底是怎么回事。在 mongodb sharding 中,chunk 是数据迁移的基本单位。每一个节点中的数据都被划分红若干个 chunk 。一个 chunk 本质上是 shard key 的一个连续区间。chunk 其实是一个逻辑划分而非物理划分。sharding 的后端就是普通的 mongod 或者 replica set,并不会由于是 sharding 就对数据作特殊处理。一个 chunk 并非实际存储的一个页或者一个文件之类,而是仅仅在 config 节点中的元数据中体现。mongodb 的sharding 策略实际上就是一个 range 模式。

如图,第一个 chunk 的范围就是 uid 从 -∞ 到 12000 范围内的数据。第二个就是 12000 到 58000 。以此类推。对于一个刚配置为 sharding 的 collection ,最开始只有一个 chunk,范围是从 -∞ 到 +∞。

随着数据的增加,其中的数据大小超过了配置的 chunk size,默认是 64M 则这个 chunk 就会分裂成两个。由于 chunk 是逻辑单元,因此分裂操做只涉及到元数据的操做。数据的增加会让 chunk 分裂得愈来愈多。这时候,各个 shard 上的 chunk 数量就会不平衡。这时候,mongos 中的一个组件 balancer 就会执行自动平衡。把 chunk 从 chunk 数量最多的 shard 节点挪动到数量最少的节点。

最后,各个 shard 节点上的 chunk 数量就会趋于平衡。固然,balance 不必定会使数据彻底平均,由于移动数据自己有必定成本,同时为了不极端状况下早晨数据来回迁移,只有在两个 shard 的 chunk 数量之差达到必定阈值时才会进行。默认阈值是 8 个。也就是说,默认状况下,只有当两个节点的数据量差别达到 64M * 8 == 256M 的时候才会进行。这样就不用对刚建好的 sharding ,插入了很多数据,为何仍是都在一个节点里感到奇怪了。那只是由于数据还不够多到须要迁移而已。

在数据迁移的过程当中,仍然能够进行数据读写,并不会所以而影响可用性。那么 mongodb 是怎么作到的呢?在数据迁移过程当中,数据读写操做首先在源数据节点中进行。待迁移完毕后,再将这期间的更新操做同步到新节点中去。最后再更新 config 节点,标记数据已经在新的地方,完成迁移。只有在最后同步迁移期间的操做的时候,须要锁定数据更新。这样就讲锁定时间尽量缩小,大大下降数据迁移对服务的影响。

mongodb 的 sharding 和传统 sharding 的最大区别就在于引入了元数据。看似增长了复杂度,并增长了一些额外的存储,可是由此带来的灵活性倒是显而易见的。传统的 sharding 本质上是对数据的静态映射,全部那些数据迁移的困难都是由此而来。而引入元数据之后,就变静态映射为动态映射。数据迁移就再也不是难事了。从而从根本上解决了问题。另外一方面,用元数据实现 chunk 则下降了实现难度,后端节点仍然可使用原有的技术。同时,由于不须要对后端数据进行变更,也使部署迁移变得更容易,只须要另外加上 mongos 节点和 config 节点便可。

再说说数据路由功能。mongos 的最主要功能就是做为数据路由,找到数据的位置,合并查询结果。来看看它是如何处理的。若是查询的条件是 shard key ,那么 mongos 就能从元数据直接定位到 chunk 的位置,从目标节点找到数据。

若是查询条件是 shard key 的范围,因为 chunk 是按 shard key 的范围来划分的,因此 mongos 也能够找到数据对应 chunk 的位置,并把各个节点返回的数据合并。

若是查询的条件不是任何一个索引,原来的全 collection 遍历仍然不可避免。可是会分发到全部节点进行。因此,仍是能够起到分担负载的做用。

若是查询的条件是一个索引,但不是 shard key,查询也会被分发到全部节点,不过在每一个节点上索引仍然有效。

若是是按查询 shard key 进行排序,一样因为 chunk 是一个 shard key 的范围,则会依次查询各 chunk 所在节点,而无需返回全部数据再排序。若是不是按 shard key 排序,则会在每一个节点上执行排序操做,而后由 mongos 进行归并排序。因为是对已排序结果的归并排序,因此在 mongos 上不会有多少压力,查询结果的游标也会变成在每一个节点上的游标。并不须要把全部数据都吐出来。

从上面能够看到,对 sharding 集群来讲,shard key 的选择是相当重要的。shard key 其实就至关于数据库的聚簇索引,因此选择聚簇索引的原则和选择 shard key 的原则是差很少的。一样, shard key 一旦设定就没法再更改,因此,选择的时候就要谨慎。shard key 的选择主要就这么几点。

首先,shard key 的值要是固定的,不会被更改的。由于一旦这个值被更改,就有可能会从一个节点被挪动到另外一个节点,从而带来很大的开销。

第二,shard key 要有足够的区分度。一样由于 chunk 是一个 shard key 的范围,因此 shard key 相同的值只能位于同一个 chunk 。若是 shard key 相同的值很大,导致一个 chunk 的大小超过了 chunk size,也没法对 chunk 进行分裂,数据均衡。同时,和通常的数据库索引同样,更好的区分度也能提升查询性能。

第三,shard key 还要有必定的随机性而不是单向增加。单向增加的 shard key 会致使新插入的数据都位于一个 chunk 中,在在某一个 shard 节点中产生集中的写压力。因此,最好避免直接使用 _id ,时间戳 这种单向增加的值做为 shard key。

mongodb 的 sharding 有不少优点,可是也一样有其局限性。

首先,mongodb 只提供了 range 模式的 sharding。这种模式虽然能够对按 shard key进行 range 查询、排序进行优化,可是也会形成使用单向增加的值时,写入集中的结果。

第二,启用了 sharding 以后,就没法保证除 shard key 觉得其余的索引的惟一性。即便设为 unique,也只是保证在每一个节点中惟一。有一个办法是,把索引设为 {<shard_key>:1, <unique_key>:1} 。可是这样并不必定知足业务逻辑需求。

第三,启用 sharding 后,没法直接使用 group() 。可是能够用 map reduce 功能做为替代。

第四,虽然数据迁移操做对读写影响很小,可是这个过程须要先把数据从磁盘中换入内存才能进行,因此可能会破坏热数据缓存。此外,数据迁移也仍是会增大 io 压力,因此能够考虑平时关闭自动平衡,在凌晨压力小的时候再进行。

最后,config 节点的元数据同步对时钟准确性要求比较高,一旦各 config 时钟偏差大了,就会出现没法上锁,从而没法更改,致使数据集中。所以 ntp 时钟同步时必不可少的。

在这里再说一下 sharding 集群的备份问题。因为后端数据节点仍然是普通的 mongod 或 replica set,因此备份其实和原先差很少。只是须要注意的是,备份前须要中止自动平衡,保证备份期间 sharding 的元数据不会变更,而后备份 shard 节点和 config 节点数据便可。

相关文章
相关标签/搜索