MongoDB分片

Posted by AJW on March 23, 2017

MongoDB分片(sharding)是指将数据拆分,分散在不同机器上的过程,是一种横向扩展。它可以使得我们不需要配置强大的服务器就可以存储更多的数据,处理更大的负载。分片是Mongo最复杂的配置方式,这里记录一些分片的知识点和注意问题。

分片集群的组件

一个分片集群包含三种组件:

  • 分片服务器(shard)
  • 路由服务器(mongos)
  • 配置服务器(config servers)

分片服务器

分片服务器存储了整个分片数据集的一个子集。通常一个分片服务器应该是一个复制集,以来保证数据的冗余。在一个单独的分片上执行查询操作只会返回数据的一个子集,只有连接到路由服务器上,才能对整个集群进行读写操作。

配置服务器

配置服务器存储了整个分片集群的元数据和配置信息,包括集群中有哪些分片、分片的是哪些集合、数据块的分布等。同时,配置服务器也存储了用于用户验证的信息,并且管理分布式锁。

从mongo3.4开始,配置服务器必须采用副本集的形式,而且必须使用WireTiger引擎,同时有以下限制:

  • 不能有仲裁节点
  • 不能有延时节点
  • 必须建有索引(也就是说buildIndexes选项必须设置为true)

在配分片集群时,要先设置分片服务器,才能添加路由服务器和分片服务器。

路由服务器

路由服务器将读写操作路由到分片上,它提供了应用到分片集群的唯一接口。用户不应该直接连接到分片服务器上。路由服务器通过缓存配置服务器上的元数据来得知各个分片上有哪些数据,而它自身不存储数据。
通常会把mongos服务器和应用程序运行在同一台服务器上。

所以,mongo的分片是一种自动分片,意思是说,应用层对分片是全然不知的,也不知道数据到底在哪个分片上,用户使用分片集群时和使用单个mongo服务是完全相同的。应用程序只需要连接到路由服务器就可以了,路由服务器会将用户请求路由到相应的分片上去。

自动分片

Mongo对集群数据的追踪

mongos通过块(chunk)来追踪文档的位置。每个块由给定片键特定范围内的文档组成。一个块只能存在于一个分片上,所以mongodb用一个比较小的表就能够维护块跟分片的映射。通过查看mongos的config.chunk集合可以查看目前的块信息。

块范围

新的分片集群块范围是负无穷到正无穷,它包含了所有的数据,随着数据量的增大,块会自动分成更小的块,每个块的范围是一个左闭右开的区间。

拆分块

mongos会记录每个块中插入了多少数据,一旦达到了某个阈值(应该就是chunksize),就会检查是否需要对块进行拆分。如果块确实需要拆分,mongos就会在配置服务器上更新这个块的元信息。注意,块的拆分只需要更改块的元数据即可,不需要对数据进行移动

分片拆分点是由分片自己决定的。当mongos向分片询问是否需要被拆分时,分片会对块大小进行粗略的计算,如果发现块正在不断变大,它就会计算出合适的拆分点,然后将这些信息发送给mongos。当然,分片可能找不到任何可用的拆分点,因为合法拆分块的方法有限。例如,具有相同片键的文档必须保存在相同的块中,如果一个块中的文档片键值都相同,那么该块就无法拆分。

均衡器

均衡器(balancer)负责数据的迁移。它会周期性地检查分片间是否存在不均衡,如果存在,则会开始块的迁移,直到各个分片上的块数量达到均衡状态。从3.4版本开始,均衡器运行在配置服务器的主节点上。具体的块迁移操作过程如下:

  1. 当均衡器发现有分片达到了“移动阈值(migration threshold)”,就会发送moveChunk命令到该分片(后面称其为“源分片”)。
  2. 源分片接到命令后,就会通知目标shard开始均衡过程。在块移动的过程中,所有的读写请求还是会被路由到源分片上。
  3. 目标分片建立源分片上的数据索引,然后开始读取源分片上的数据。
  4. 当数据接收完毕后,源分片就会停止在其上的写操作,然后目标分片会与源分片进行同步,来把移动过程中发生的读写操作也被复制过来。
  5. 当两者完全同步之后,源分片会连接到配置服务器更新分片的元数据。
  6. 在元数据更新完毕,并且块上没有开放的游标(open cursors,意思应该就是没有其他连接?)时,源分片会删除它上边的被移动的数据。默认情况下删除操作会被加入到队列,异步删除。

由于均衡的过程还是比较耗费资源的,所以mongo对均衡的过程做了一些限制:

  • 一个分片只能同时参与到一个块移动的过程,如果一个分片上有多个块想要被移动,那么只能一个一个地操作。但是,mongo是支持多个移动操作同时并发进行的,所以,对于一个有着n个分片的分片集群,最多一次可以执行n/2(向下取整)个块移动操作。
  • 只有在块数量最多的分片和数量最少的分片的差值达到移动阈值时,才会触发均衡操作。当任意两个分片上块的数量差小于2时,均衡过程结束。移动阈值的具体大小如下:

migration-threshold

实验发现这个均衡的过程可能很慢,特别是在建立了一个新的collection并插入数据时。

片键

上文中已经提到了片键,可以看到它是数据分块的依据。片键选择不同,数据的分发方式也会有很大不同,最终会影响整个集群的性能。并且,片键在选定后就不能再对其进行改变(包括片键本身和片键字段的数据),所以选择好片键对整个集群十分重要。

片键的索引

片键上必须有索引,或者是单索引,或者片键是复合索引的前缀键。片键上的索引可以是唯一索引,但是除了片键_id字段,其他字段就不能有唯一索引了。

片键的类型

常见的片键有两种类型:

  • 升序片键
  • 散列片键(随机分发的片键)

升序片键最典型的就是_id字段或者像日期这样的字段,采用这种片键会造成一个问题,新插入的数据都会被分配到最后一个数据块中,造成最后一个数据块不断增大,然后就需要不断拆分、均衡,这会对性能造成一定影响。

散列片键可以使数据随机分发,使数据分布更加均衡。散列片键需要先创建散列索引,然后对集合分片。如果对一个不存在的collection建立散列片键的话,mongo会立即在每一个分片上建立2个空的数据块。当插入文档时,写请求就会均匀的分发到不同的分片上。但是散列片键的问题在于不支持范围查询。

为什么感觉我们的数据既要按照日期来分片,又要范围查询。。。

选择片键时尽量要使得片键的的值有较多的变化,也就是有较高的“势”,否则,由于相同片键的文档只能存在于同一个数据块中,会造成数据块的数量很少。