大数据基础-原来这个“桶”也能路由分片

192 阅读7分钟

01 前言

我们先回忆在上文《大数据基础-3行代码实现最基本的路由分片(附代码)》的主要内容,我们介绍了哈希分片的第一类常见套路,hash取模法(即基础轮询),并在一定程度上进行延伸,引出加权轮询的概念。最后我们对基础轮训和加权轮询的优缺点进行考量,其虽然逻辑清晰、复杂度低,但是灵活性较差。当节点发生变化时,会导致映射关系全部打乱,已经分配的数据也需要根据新的映射关系重新分配。

那怎么样能改进基础轮询的痛点问题呢?

本篇我们就带着这个问题一步一步来深入到本篇的主题,哈希分片的第二类方法:虚拟桶。

02 解题—寻找核心问题点

来,我们先不着急于破题,首先分析下基础轮询,有关灵活性缺失的核心矛盾点

  1. 分片层和物理节点层重合。

  2. hash取模的方法对取模数值非常敏感,牵一发而动全身。

03 破题—对症下药

  • 解决层次模糊的问题

在通用的路由分片模型中,存在着Key-Patition映射和Partition-Machine映射,其代表着数据记录、分片、物理节点3个角色存在其中,因此对于基础轮询而言,其分层方式是有悖于通用的路由分片模型。我们需要对其进行层次拆分。如下图所示,将原本强耦合的两层拆分成弱耦合的三层,这样就满足通用模型的定义。

  • 解决hash取模敏感度高
  1. 固定分片数量

    <1>对于hash取模来说,其对取模的数值的大小是依赖性非常高。

    那么是否可以保证取模的数值固定不变呢?

    此时你可能会想:对于分布式服务来说,日常节点的扩容、缩容肯定都是都是非常正常的事情。的确,过去确实是不可以,但是此时我们已经解决了路由分片模型中的分层问题,那么保证数值不变就不是一个难题了。

    我们可以保证分片数量是固定的,那么此时物理节点的增加或者减少就并不影响分片的数量了,无论何时通过将Key 进行hash取模的数值都不会变了,这样就解决了节点变化。

  2. 弱化重分配成本

    当我们将分片进行数量固定后,每当物理节点变化(上线、下线)时,都会通过维护Partition-Machine之间的映射关系,将物理节点的最新变动同步给分片层进行新旧节点间的数据迁移,从而实现各个节点上的分片数量、物理数据相对均衡**。**

    当有节点上线时,老节点上的部分数据会逐渐迁移到新节点上,同时更新路由信息。

    节点下线时,迁移方向相反。这样就解决了hash取模,节点变动后,全量重新分配的问题。

此时我们在基础轮询的基础上,演化出了一个新的路由分片架构,虚拟桶(Virtual Buckets),其路由分片模型图如下所示,若干个Key会存放在一个Bucket中(可理解成分片),每个Bucket又实际存储在物理节点中,一般的为了数据安全,会采用多副本的方式存放在一组节点中。

---------------------小Tips----------------------

这里提前解答大家的一个误区,分布式系统的多副本不一定就是类似HDFS的pipeline数据复制或者Tidb/Etcd的raft复制,MySQL一主多从实际上也是数据复制的另一种表现。只不过在分布式系统中,将数据复制更具像化而已。有关数据复制的内容请继续关注后续文章。

04 理论之上的最佳实践

上面我们借着解决hash取模的痛点问题,引入了虚拟桶的概念,那么现有系统中有哪个系统使用的虚拟桶的方法进行路由分片呢?

我相信Redis大家都不陌生,它是一个内存数据库,借助着将数据存储在内存中的方式,由于数据不需要落盘(暂不考虑Redis的rdb和aof的持久化),大大提高了业务的读写效率。

实际上Redis 3.0后的cluster模式就在为“虚拟桶”的分片路由模型背书。

接下来往外拓展一下,着手分析redis cluster的相关实现,首先redis cluster是符合路由分片的通用模型的,其模型也分为三层结构:即key(数据记录)-slot(分片)-machine(物理节点).在redis cluster中有slot的概念,其slot个数是常量16384。

这时你肯定有个疑问,如何定这个取模值(分片数)呢?

接下来我们我们带着问题探索下Redis是如何来确定这个数值的。(原有画图工具只能画直角图,原谅我用PPT画图的水平吧,我会努力的😭)

实际上这个数值和服务的架构以及实现逻辑是强相关的,redis cluster是采取的去中心化的架构,如上图所示,5个节点通讯使用的是p2p的Gossip协议,这也就意味着,集群内的每个master节点都会携带当前节点和其他节点的数据信息,去定期ping/pong若干个集群内的其他master节点。

  1. 消息体大小线性增长

    如果slot的数量为16384,那么根据redis的消息数据结构来看,其消息头是大小为16384/8个字节的char类型数组,这个bitmap存放当前节点和slot的对应关系(redis数据结构与本篇主题关系不大,暂不详细展开)。此外还包括其他节点的信息,集群节点越多,其消息体越大。

  2. 快速维护全局唯一拓扑

    为了保证集群内节点信息数据的一致性,redis集群内部心跳的时间也非常短,因此节点越多,会把消息体同步所占用的网络带宽不断放大,直到集群无法负担,由于无法实际测试具体多少节点才最大限制,最后查了redis作者的回答,他建议是小于1000节点,实际上据我所知,redis集群上百节点就已经很大了,

  3. 数据结构压缩效率

    此外节点不变,slot越多,那么bitmap的压缩率就越低。

因此。结合了架构、数据结构、核心逻辑多个方面最终才确定的16384这个数值


《结尾趣谈》

小A在接手新服务研究架构时,对一个参数绞尽脑汁都没有好的思路。于是发起求助。

A:为啥这个参数配成2000啊?

B:不知道为啥,过去就这个值。我过去接这个服务时,我也问过,上一个人也不知道为啥。

C:我联系下过去负责这个服务的同学,我有他微信,我问问。

。。。。

C:他说当时是2010年,四舍五入就2000了。

A:WHAT?

抛开尴尬不谈,很多人都在问为啥前人留的坑不好填呢?因为当一个逻辑你绞尽脑汁都想不出来答案时,可能仅仅是前人一个拍脑袋的决定而已。

因此为了不给自己留坑、不给其他人留坑,少拍脑袋决定东西!

原创不易**,觉得有点用的话,就请你为本文点个在看或者无情转发吧。**

你的支持是我写作的动力。

关注公众号,加我好友

大数据之路,携手前行