MongoDB 分片技术原理

607 阅读5分钟

本文会用很精简的内容将MongoDB分片的基本概念和原理说清楚,但不会过于陷入细节。可能大部分同学在工作或者学习中还没有接触过MongoDB,但不妨碍大家学习借鉴,市面上很多的存储组件也大致使用类似的思想来处理分片。

分片基本概念

什么是分片?

通过多台机器存储数据,并以此达到存储大量数据,提高吞吐的目的。

什么时候适合使用分片?

当系统增长到以下两种情形时,可以考虑使用分片:

  1. 单台服务的硬盘容量或者内存容量无法满足需求
  2. 单台服务器的吞吐达不到要求

通常有两种方法来解决系统增长:垂直扩容水平扩容

垂直扩容:增加单个服务器的性能,比如更强大的cpu,更多的内存或者更多的存储空间。当前的技术水平,使用垂直扩容带来的性能提升是有上限的

水平扩容:将数据和负载分摊到多台“廉价”服务器上,以此获得比单台“昂贵”服务器更高的性能。代价是增加了部署和维护的复杂度

MongoDB通过分片支持水平扩容。

分片集群架构

MongoDB集群架构由以下几个组件组成:

分片(shard)服务器:每个分片包含整个数据集的一部分,所有分片的数据构成了全集数据。每个分片可以以副本集(replica set)方式部署。

配置(config)服务器:存储了分片集群的配置和元数据,包含了集群数据集到各个分片的映射关系。可以以副本集方式部署。

路由(mongos)服务器:起到路由的作用,从配置服务器加载并缓存集群信息。当客户端发起查询请求时,决定应该做定向查询,还是做分散-聚合查询。所以,在一个分片集群中,客户端必须连接mongos服务器进行读写操作,不能直接连到某个分片进行读写。

MongoDB的分片是collection级别的,不熟悉mongoDB的同学可以把collection理解为mysql的表。比如下图,collection1是分片集合,collection2不是。那么collection1会被存储在shardA和shardB上,而collection2只会被存储在主分片上,这里是shardA。

分片原理

要对collection进行分片,首先要定好分片键(shard key)。分片键和索引键很像,其实也可以认为分片键就是索引键,因为MongoDB要求分片键必须有索引或者至少是一个复合索引的前缀。

MongoDB会将分片集合按照分片键的范围将整个数据集划分成不重叠的小数据块(chunk),每个chunk都是集合的一个数据子集。每个chunk有以下几个特点:

  1. chunk中的数据分片键是连续的
  2. 每个chunk通过一个左闭右开的区间标识
  3. chunk有固定的大小,默认是64MB

chunk1:{x: minKey} -->> {x: -75}

chunk2: {x: -75} -->> {x: 25}

chunk3: {x: 25} -->> {x: 175}

chunk4: {x: 175} -->> {x: maxKey}

将整个数据集合拆分成多个固定大小的chunk后,MongoDB均衡器动态的将这些chunk均匀的划分到不同的分片上。当然这里也会涉及到chunk分裂这种细节问题,但整体的思想就是通过分配chunk达到分片的目的。

分片键

我们已经知道MongoDB的分片是基于分片键来做的,分片键的选取会影响到整个分片集合的效率和性能,并且分片键选择后就很难修改(可以加后缀)。那么分片键的选取就非常关键,至少需要考察以下几点:

基数:分片键的基数决定了chunk的数据量,因为相同分片键的数据只能存在同一个chunk内。比如选取的分片键基数是4的话,最多就只能有4个chunk。打个比方用日志等级作为分片键就不是很合适,因为只会有Error,Info,Debug,Warn等几个等级,chunk的数量很有可能少于shard的数量。可以考虑使用复合分片键。

频率:如果某个分片键的出现频率过高,会导致某个chunk变为热点,并且数据量急剧增大,出现超大块(jumbo chunk)。可以考虑使用复合分片键。

单调性:如果分片键是单调递增或者单调递减的,那么在使用范围分片策略(ranged sharding)时,所有新增的文档都会被插入到包含maxKey或者minKey的chunk,形成热点。可以考虑使用哈希分片策略(hashed sharding)。

分片类型

MongoDB支持两种分片策略,哈希分片(hashed sharding)范围分片(ranged sharding)。假设现在有一个user表,username作为分片键,形如user1,user2...,我们看下在这两种分片策略下,chunk会长什么样。

哈希分片

将分片键通过hash函数计算出hash值后,使用这个计算出的hash值作为真实的分片键。相近的值不一定会被分到同一个chunk,所以在范围查询时,通常需要广播,但对于单调的分片键有打散的作用。

{ "username" : { "$minKey" : 1 } } -->> { "username" : NumberLong("-6148914691236517204") } on : shard1 Timestamp(1, 0)

 { "username" : NumberLong("-6148914691236517204") } -->> { "username" : NumberLong("-3074457345618258602") } on : shard1 Timestamp(1, 1)

 { "username" : NumberLong("-3074457345618258602") } -->> { "username" : NumberLong(0) } on : shard2 Timestamp(1, 2)

 ...

范围分片

范围分片直接使用分片键作为划分数据的依据,具有相邻分片键的文档大概率会在同一个chunk上的特点。这种分片策略对于范围查询比较友好。

{ "username" : { "$minKey" : 1 } } -->> { "username" : "user4685" } on : shard3 Timestamp(11, 0)

{ "username" : "user4685" } -->> { "username" : "user8371" } on : shard2 Timestamp(11, 1)

{ "username" : "user8371" } -->> { "username" : { "$maxKey" : 1 } } on : shard1 Timestamp(8, 1)

均衡器

均衡器是一个后台程序,负责监控每个分片上的chunk数量。当分片间的chunk数不均衡时,将开启自动迁移程序,将chunk从更多的分片向更少的分片迁移。

chunk迁移会有带宽和负载的开销,均衡器做了一些操作来减轻这方面的影响:

  1. 限制同一时间只能有一个分片进行迁移
  2. 只有当chunk数最多的分片和chunk数最少的分片二者之间的gap达到阈值才进行迁移,如下:
chunk数量迁移阈值
<202
20-794
>=808

参考

mongoDB Documentation

MongoDB权威指南