DDIA第六章: Partitioning
tags:
ddia
读书笔记
在分布式数据存储的系统中,Partition(分片)是绕不开的一个技术点,原因在于随着数据规模的增加,总有一天需要一台机器装不下所有的数据的时候,所以需要将数据进行切分并且存放到各个机器上去;对于整个系统来说,分片也会极大的提升系统本身的scalability; 如果所有的数据都存储在一台机器上,那么就表示通过增加机器无法为系统提供扩展性;
当然这个技术已经在不上的数据存储系统中应用;当然不同的系统所叫的名字各不相同;
Mongodb: shard, 这个算是物理意义上的分片,比这个更加小的话叫做chunk
Hbase: region
bigtable: tablet
Cassandra: vnode
等等..
1.分片有哪种方式并且各自的优缺点是什么?
1.1 按照range进行切分
通常数据都会包含有一个primary-key,当然也可以不用primary-key但是一定会有一个key用来做为切分的依据;而所谓的range进行切分,通常是按照是字典排序的顺序进行切分,每个region会包含[min,max),在同一个region的数据按照切分依赖的key是有序的;有点类似于字典,如果按照word的首字母进行切分,那么第一个region就是A开头,这里面包含了所有的A开头的word,并且是有序排列;
这种切分的好处是:
range scan会非常快,正常的切分是按照primary-key,那么如果对primary-key进行range-scan就会非常快,因为这些key是存储在一起,并且有序…类似于你查字典以A开头的word,那就是第一个区域遍历即可;
range查询性能:range查询会集中在某一些partition上,而不会在全部的partition上;这其实很重要,因为会限制你查询性能的上限(后面解释);
缺点:
倾斜问题:不管是read还是write,甚至于存储的空间都会倾斜的很厉害的;而且极端容易出发hotspot的问题,导致整个服务集群出现不可用;
1.2 按照hash分片
range分片的问题很明显,而且处理起来并不是很简单的,尤其是hot spot的问题,出现了除了限流就只能限流,可以加入平时的rebalance的机制,但是rebalance本质上都是一种比较重的操作的;而hash分片就是应对这种倾斜问题所提出的;
依照上面提到的,range 分片是按照某一个用户指定的key来进行分片,那么hash分片的唯一的区别在于依据变成了hash(key)计算之后的数值进行分片;因为有hash函数的存在,即使只是普通的md5,都能很好的达到所谓的随机性,这样可以把原本相关的数据分散到不同的partition上的;这样就极大极大概率减少了所谓的倾斜问题;
优点
相关数据会被分散到各个分区,减少了数据倾斜和热点问题的产生
单点query也是非常快速的;
缺点
range-scan:对于hash分片的数据,其数据相关性已经被hash函数所打破,所以相关的数据不会在同一个分区,而且你也不知道和没发去跟踪所在的partition;所以针对的range scan或者模糊查询都是需要被发送到所有的partition上;这会导致一个短板效应,latency会依赖于最慢的partition返回的时间的;
性能:每一个range-scan都要往全部分区上查询,那么整个集群的range-scan性能就是单节点的range-scan的性能,所以上限很明显;
是否可以兼顾呢?通常的方法是组合索引,本质上依然还是range分片,但是用户主动将相对随机的字段加在最前面,从而达到随机的效果;这个需要用户去识别,尤其是第一个字段是否足够的随机;还有就是这种情况的话,想要有比较好的range-scan的查询的话,只能按照组合索引的顺序去查询,比如组合索引(a,b,c),那么你有效率的scan的方式是提供a or a,b or a,b,c这种模式,一定要有顺序,因为索引第一部分是a字段,你不能只使用b来进行查询,因为这样不能明确找到具体的partition,那就全量搜索。
1.3 关于负载倾斜和热点问题
上面讲到了hash 分片可以解决绝大多数的热点问题,但是这也只是绝大多数而已;依然还有会一些corner case让你不得不面对的;其中比如单key的多量访问,比如就对某一个key进行过量的并发访问最终导致对应的分区出现过载,最终导致服务短暂不可用的;对于这种情况的话,解决的方式其实有几种:
服务保护;即当系统出现过载的时候,限流熔断机制的引入可以帮助系统可用性的提高的;当然这其实就是所谓的系统极限,对外用户来说体验可能不算太好;
可以适当引入所谓的cache,但是cache也会面临单机的硬件瓶颈;比如就是网卡打满了什么的,这种时候依然还是不能解决本质上的问题;
系统本身能自动去检测数据倾斜或访问热点的时候,通过对于key+random num来进行分散,这样调整整体数据的倾斜性;但是这种方式带来的问题是有几个:
需要维护这种meta-信息,尤其是需要记住哪些key做了类似的操作,而且也查询也变得更加麻烦,需要同时向所有的randum key进行查询
反应的速度太慢,需要检测并且加入所谓的均衡机制;对于read倾斜来说,不要到要何年马月才能有效果;对写倾斜可能效果会比较好;
当然现在很多系统目前还没有所谓的这种机制,起码没有自动检测和实施的机制,估计未来会出现的;
关于这段我也是深有感触,之前维护过规模比较大的mongodb集群的;不同区域的mongodb集群选择了不同的分片模式的:
整体使用下来range分片模式在维护过程中整体的需要关心的事情还是太多的,尤其是负载均衡和热点问题的,动不动就因为热点请求导致服务抖动严重的,而且mongodb集群的负载也很不均衡,这个里面是由于mongodb按照的是chunk的个数进行均衡,但是不考虑真实数据的均衡程度,所以时不时就需要进行调整,当然hash也有这个问题,但是整个的存储均衡好不少的;所以后期新开集群我们默认使用的hash分片,因为当时的mongodb整体的自动化做的不好,虽然我们在外围一套工具进行均衡和迁移,但是依然是很麻烦的操作;
当然hash也遇到了一些问题,老板观察到整个模糊查询的性能已经达到了上限,希望后面能提到更加好的,这样可以做更多的事情;但是模糊查询或者range-scan在hash分片中本身就是需要发送到所有的partition进行查询的,单机瓶颈就是集群瓶颈,所以如果要提高可能需要思考其他的方案;
关于极端情况的hot spot问题,我们当时也遇到了,就一个用户疯狂的查一个key的value,并且这个value有点大,就导致了单partition的网卡被打满了,整个集群就开抖动;对于这种问题,后期我们基本是限流熔断的方式来进行,没有从本质上去解决这个问题;
DDIA很多章节讲的真的很不错的,缓缓道来,并且该讲的都讲到了;
2. 第二索引分片的问题
第二索引主要是提升用户的使用方式的,可以用其他的字段进行索引,增加了业务上的便利性的;那么对于第二索引数据存储也是一个问题的,因为不可能存储在同一个节点上,也涉及到了所谓的分片的问题;常见的方案有两种:
2.1 按照文档进行分片
这种方法本质上就是数据在那个节点,就在那个节点进行索引存储的;别名叫做:
a local index
这种方法应该是比较常见的方式的,这样每一个分片的索引是独立的,只对当下的数据进行负责的;对于这种分片的优缺点分析:
优点:
- 整个写入逻辑比较简单的,而且写入数据和index是在同一个节点做的,更加方便做所为的事务
缺点:
- 对于模糊查询可能需要查询所有的分片;因为正常情况下,路由层是没办法对除了主键以外的条件进行有效的效率上的过滤的;那么就会遇到hash分片的问题;
2.2 按照Term进行分片
这边的term,书上的解释看上去更加是类似于具体的二级索引构建的key,也就是按照真实的索引key来进行分片;可以这么去思考:索引的分区依赖于具体索引组成的key; 这个索引方式的别名:
a global index
这个时候数据和索引其实是本分离在不同的节点上的;
优点:每次查询可以根据查询的条件优先过滤,找到部分的分区,然后快速找到所有相关的数据;其实本质上也是range分片的过程;通过索引找到所有的id然后再去根据id的list查询所有的数据;
缺点:很明显的复杂度变高了,而且数据和索引的原子性就很难把握了;如果index写入成功,数据还在写入的过程中,查询发现索引有但是数据层没有,这种不一致性就很难把握;再加上故障处理等等,会有不少的用户体验;
这两个索引我在之前的工作环境中都遇到过;
1. mongodb的索引是使用第一种方式的;模糊查询的话,尤其是没有涉及到分区key的模糊查询的话,那必然就会触发全分区的查询的;但是如果在查询的时候加入所谓的分区key的部分的话,在range分片下还是有一定的优化的;
2. 一个朋友的公司在内部做influxdb的分布式架构,influxdb的索引数据和时序数据本身就是用了第二种方式的,不是很确定关于不一致部分是如何处理的;我猜可能是先写数据再async同步index吧;这样只会触发所谓的index延迟,而不会出现index有数据没有这种情况的;
3. rebalance
和开头讲的一样,分片的好处是为了提升集群的可扩展性的;当新节点上线之后,需要去别的节点拉取一些分片到自己的节点,从而开始分担集群的服务;如果系统发现一些节点的负载过于沉重,需要系统将当前节点的部分分区迁移到其他的节点上,用来减轻当前节点的压力的;而这种迁移的过程就是所谓的rebalance
3.1 Rebalance的策略
不要使用
hash(key) mod N这种方式;固定分区数量
在集群初始化的时候已经确定了分区的个数,后面的运行过程的时候分区的个数是不会变换的;但是分区到node之间的关系可以是人为指定或者自动自动执行的;
动态分区的方式
所谓动态指的是系统会设置每一个分区的max value,可能是数据的条数又或者是存储大小等等,也就是说系统会根据运行情况,记录每一个分区的大小,如果达到了所谓的max value就进行split,又或者当两个相邻的分区都很小的情况下进行merge;这样系统的分区的个数是在变化的;
rebalance迁移的最小维度是分片,每一个节点上存储了多个分片;当系统觉得当前节点有太多的分片或者压力过大的时候,就会启动将当前的节点的部分分区迁移到其他的节点上的;这个过程就是rebalance的过程的;
3.2 rebalance 自动还是人工
理论上应该是需要自动的,但是rebalance是一个很重的操作的,有的时候会影响到正常的读写请求的,所以需要去trade-off;按照目前的技术发展自动化一定是趋势的;
mongodb的rebalance机制比较单一,老版本是按照chunk的个数做均衡,新版本加入了按照数据存储余量来进行均衡;但是这种单一的均衡策略并不能解决读写不平衡的问题的;尤其是range分片的情况下;
所以我们在维护mongodb集群的过程中,加入了外围的工具,加各种策略去判断哪些节点压力比较大的,然后通过mongodb自带的迁移分区的command来迁移;关于这块我觉得mongodb现在做的并不是很好的;
关于为什么rebalance是一个比较重的op呢?因为实际上分片本质上是一个逻辑上的概念,它并不是真的类似于物理隔离在一个节点上的;当然这几年已经有不少数据库的公司开始思考做成物理隔离;这样迁移的过程整体的波动会小很多的;因为在节点维度多个分片数据是融合在一起的,所以你在迁移的时候需要先去找到这些数据,然后进行迁移,并且迁移完毕之后再删除的;整个过程,当分片比较大,或者分片数比较多的时候,其实会消耗比较多的当前节点的资源,而且迁移本身涉及到了网络带宽和本地的磁盘io、cpu的占用,不控制好的确会影响到了当下的在线业务;
不同的数据库系统都会给予一些配置来进行控制,比如每次只能同时启动一个分片的迁移的,这样的方式来保证影响范围的;
说一下物理隔离的好处,如果是真实的物理隔离,那么进行迁移的时候只需要消耗少许的io和网络即可,不会因为查询数据对整个节点的内存什么的进行很大的影响的;而且删除也只是物理层面的直接delete,这个就很好,之前听TIDB目前就这么做,每一个region一个rocksdb,当然他们的说大概一块nvme的话放2000多个rocksdb问题不大的;如果真的是这样,其实好处真的不少;分布式系统的迁移问题真的很麻烦的,涉及到到的状态很多的,并且又要控制影响,快了不好,慢了就赶不及;
4. 请求路由
既然数据被分散在多个节点上,那么client的请求如何路由到正确的节点上呢?这个问题更加普通的叫法是: service discovery,并不只是限制在数据库领域;对于这个问题目前业界的几个解决方法是:

允许client可以连接所有节点,这个节点会作为中转节点,把请求转到正确的节点上去的;
有一个单独组件叫做
routing tier,路由层,client直接访问的是路由层,由路由层去请求到正确的node;这就表示路由层需要感觉到底层分区部署的变化的,这样它才能路由请求到正确的node上去;要如何做呢?通常系统会依赖类似于zookeeper的组件,通过监听zookeeper的节点变化来感知底层分区的变化,这种架构由kafka,hbase等等,mongodb的话,路由层是mongos,configserver就是作为zookeeper的角色存在;当然和zk的监听不一样的是,mongodb还有一套触发机制,让mongos主动去拉configsrv来进行更新;由另外一种方式是cassandra类似的系统使用的,节点之间使用gossip的系列就传递集群的节点的信息,原因是cassandra这类系统数据存储在什么节点是通过计算获得的,而并不是存储起来的,但是关键计算的信息是集群的node信息,所以节点变化是需要传递到所有的节点的;但是gossip这种协议最终收敛,但是时间上不确定的,所以比较复杂…好处你可以感知到的就是meta信息需要保存的很少;
client直接连接到正确的节点上,如果连接不到正确的节点,错误节点会和它说你应该去什么节点,然后再直接连接过去的…redis的集群模式就是这样的;
5. 总结
这章节主要讲了
为什么要分片
分片的2种方式
第二索引的分片方式
rebalance的方式和面对的问题
请求路由的几种方式
基本上包含了分区所有的信息, 后面会配合副本机制来来保证整体的高可用和高可扩展性;在分片这个阶段,最为重要的是如何找到合适业务方式的分区方式和如何高效的去rebalance,目前不同数据库系统的rebalance的大致机制是类似的,但是实现上会有一些差别,但是这样也会让效率差别很大;