1.分片架构
分片是数据库中常用的提升存储容量的方式,它基于水平拓展的思想,将大量的数据按一定规则分配到不同的服务器中存储,比如MySQL集群可以借助Sharding-JDBC等外部框架对数据进行分片存储,而MongoDB则提供了原生的分片能力支持。
分片的目的主要有:
- 单机无法存储存储过于庞大的数据集
- 单机写QPS已经到了瓶颈,存在操作卡死现象
MongoDB分片架构组成分为几部分:
- 分片数据节点:真实存储分片数据的节点
- 分片配置节点:存储着分片的规则与策略等原数据
- 分片路由节点:通过分片配置规则对数据查询及写入进行路由
2.分片策略
MongoDB中存在Chunk的概念,类似于Hdfs中的Block数据块。Chunk描述的是一段数据集合。
Chunk所描述的是范围区间,例如,db.users使用了userId作为分片键,那么Chunk就是userId的各个值(或哈希值)的连续区间。集群在操作分片集合时,会根据分片键找到对应的Chunk,并向该Chunk所在的分片发起操作请求。
MongoDB内部基于Chunk作为存储单位,提供了分片键的范围分片及哈希分片两种分片模型:
- 范围分片:以分片键的范围来划分文档到具体的chunk中
- 哈希分片:以分片键的哈希值来划分文档到具体的chunk中
范围分片的缺点在于,如果分片键有明显递增(或者递减)趋势,则新插入的文档会分布到同一个chunk,此时写压力会集中到一个节点,从而导致单点的性能瓶颈。一些常见的导致递增的Key有:
- 时间值
- ObjectId,自动生成的_id由时间、计数器组成
- UUID,包含系统时间、时钟序列
- 自增整数序列
所以在使用范围分片时,分片键最好避开上述这类含有递增趋势的Key,而是选择一些随机生成ID、年龄或者或者身份证号等,避免瞬时的单点写入导致压力骤升。
哈希分片会先事先根据分片键计算出一个新的哈希值(64位整数),再根据哈希值按照范围分片的策略进行chunk的切分。哈希分片与范围分片是互补的,由于哈希算法保证了随机性,所以文档可以更加离散地分布到多个chunk上,这避免了集中写问题。然而,在执行一些范围查询时,哈希分片并不是高效的。因为所有的范围查询都必然导致对所有chunk进行检索,如果集群有10个分片,那么mongos将需要对10个分片分发查询请求。
哈希分片与范围分片的另一个区别是,哈希分片只能选择单个字段,而范围分片允许采用组合式的多字段作为分片键。
在选择分片键时,需要根据业务的需求及范围分片、哈希分片的不同特点进行权衡:
- 分片键的基数(cardinality),取值基数越大越有利于扩展
- 分片键的取值分布应该尽可能均匀
- 业务读写模式,尽可能分散写压力,而读操作尽可能来自一个或少量的分片
- 分片键应该能适应大部分的业务操作
3.读写策略
mongos分片路由节点在启动后,其内部会维护一份路由表缓存并通过心跳机制与分片配置中心保持同步
- 业务请求进入后,由mongos开始接管
- mongos检索本地路由表,根据请求中的分片键信息找到相应的chunk
- mongos向目标分片发起操作,并返回最终结果
mongos接管了所有的数据读写请求,充分扮演着代理者的角色。而对于客户端而言,分片模式下的数据操作处理并没有发生什么变化,mongos已经屏蔽了所有的差异。
分片模式会影响索引的唯一性。由于没有手段保证多个分片上的数据唯一,所以唯一性索引必须与分片键使用相同的字段,或者以分片键作为前缀。如下面的选择可以避免冲突。(1)唯一性索引为:{a:1},分片键采用a字段。(2)唯一性索引为:{a:1,b:1},分片键采用a字段。
对于分片读写来说,最好能够做到:
- 写时分散压力,能够均匀地对多个分片进行写入
- 读时单点聚合,只需要对一个或者少个分片进行读取,避免频繁的数据连接
4.数据迁移
为了保证分片集群的水平扩展能力,业务数据应当尽可能地保持均匀分布,包含以下两个方面
- 数据应均匀地分布于不同的chunk上
- 每个分片上的chunk数量尽可能是相近的
但是实际上由于分片键分布不均匀,有可能出现某些分片或Chunk包含了大量数据,而某些分片或Chunk则只分配到了少量数据,这样就不利于集群整体的负载均衡,会导致数据倾斜,从而造成某个分片过载,可能导致磁盘容量不足写入数据失败。
对于分片数据倾斜问题,可通过手动预设分片+指定chunk数量的方式,但是这种方式只能在哈希分片模型使用,第二种是MongoDB自带的自动Chunk分裂负载均衡调整。Chunk均衡器会在后台对各分片的chunk进行监控,一旦发现了不均衡状态就会自动进行chunk的搬迁以达到均衡。
在默认情况下,一个chunk的大小为64MB,该参数由配置的chunksize参数指定。如果持续地向该chunk写入数据,并导致数据量超过了chunk大小,则MongoDB会自动进行分裂,将该chunk切分为两个相同大小的chunk。
当分片中的Chunk数量差达到阈值时,则会对其进行迁移,维持各分片Chunk数量均衡,迁移阈值如下:
数据均衡会影响性能,在分片间进行数据块的迁移是一个“繁重”的工作,很容易带来磁盘I/O使用率飙升,或业务时延陡增等一些问题。所以可以设置在夜间MongoDB集群压力较小时,对数据进行迁移。
以下操作启用了自动均衡器,同时在每天的凌晨2点到4点进行Chunk数据迁移操作: