当前位置:首页 > 问答 > 正文

Redis里那个CRC槽位算法到底怎么革新了,聊聊它的技术亮点和作用

在Redis Cluster出现之前,人们要想实现Redis的分布式,也就是把数据分到多个Redis实例上,常用的方法是“一致性哈希”,这个老方法听起来不错,但它有一些挺让人头疼的毛病,当你要增加或减少一个Redis节点时,它确实能避免所有数据都重新洗牌,但依然会有一大部分数据需要搬家,这个数据迁移的量还是不小的,对系统稳定性和网络压力都是考验,更关键的是,一致性哈希容易导致“数据倾斜”,也就是运气不好的话,某个节点可能承担了远超其他节点的数据量和访问压力,成了整个系统的瓶颈,管理起来也挺复杂,你需要很小心地设置虚拟节点来让数据分布得更均匀一点。

Redis的作者Antirez在设计Redis Cluster时,就彻底抛弃了一致性哈希,自己搞了一套全新的“哈希槽”算法,这个革新,可以说是直击痛点的。

它的核心设计特别简单直接:干脆别想那么复杂了,我们预先就把整个数据空间分成固定数量的“槽位”,就16384个(0到16383)。 这个数字是精心选择的,既够大(能支持足够多的节点),又不会让节点间同步槽位信息的开销太大,你可以把这16384个槽位想象成一个大数组,每个数组下标就是一个槽位。

任何一个键(Key)来了,我怎么知道它该属于哪个槽位呢?这里用了一个非常高效的计算方法:对键名计算一次CRC16校验码,然后把这个校验码对16384取个余数,得到的结果就是它的槽位号,这个过程非常快,是O(1)的时间复杂度,决定了就能定位。

那这个槽位又怎么和具体的Redis节点对应起来呢?这就是最精彩的部分了:槽位和节点的映射关系是灵活的、可手动分配的。 在搭建Redis Cluster时,你可以命令这16384个槽位平均(或者按你的意愿)分配给集群里的所有主节点,一个三主节点的集群,我可以让节点A管理0-5460号槽,节点B管理5461-10922号槽,节点C管理10923-16383号槽,客户端会有一份这个“槽位配置映射表”,它知道要找的数据在哪个槽位,进而就知道该连接哪个节点。

Redis里那个CRC槽位算法到底怎么革新了,聊聊它的技术亮点和作用

这套机制的技术亮点和作用非常突出:

第一,它实现了完美的数据分片和控制力,因为槽位是固定的,数据通过CRC16计算直接、确定性地落到一个槽位里,只要槽位分配得均匀,数据自然就是均匀分布的,从根本上解决了一致性哈希可能带来的数据倾斜问题,管理员还能根据每个节点硬件性能的不同,手动分配不同数量的槽位,实现负载的精细调控。

第二,它让集群的扩缩容变得异常平滑和可控,这才是最大的革新点,现在我想增加一个新节点D,我不需要动原有的所有数据,我只需要做一件事:从节点A、B、C的槽位里,各自匀出一部分(比如每人拿出500个槽),分配给新节点D,系统只会迁移这1500个槽位对应的数据,而其他14884个槽位的数据纹丝不动,这极大地减少了数据迁移量,缩容也是同样的道理,这使得集群的弹性伸缩对业务的影响降到最低。

Redis里那个CRC槽位算法到底怎么革新了,聊聊它的技术亮点和作用

第三,简化了客户端的逻辑和集群的管理,客户端不需要实现复杂的一致性哈希逻辑,它只需要从集群获取并缓存一份最新的“槽位-节点”映射表就行了,当它要访问一个key时,先算槽位号,再查表找节点,直接连接操作,非常直接,对于集群管理者来说,槽位是一个清晰的运维单元,迁移、故障转移都可以以槽位为单位进行操作,视野非常清晰。

第四,为高可用打下了坚实基础,在Redis Cluster中,每个主节点都可以有若干个从节点,故障转移不是以整个节点为单位,而是以槽位为单位的,如果主节点A挂了,集群会自动将它负责的那些槽位(比如0-5460)选举一个归属权,由其对应的从节点接管,这个过程对于客户端来说是透明的,客户端可能会收到一个“MOVED”重定向指令,告诉它新的节点地址,然后自动更新本地缓存,之后就能继续正常访问了。

Redis的哈希槽算法,其革新性不在于用了多高深的数学原理,而在于用一种极其简单、务实且中心化的思路(预先分好16384份,由集群管理分配关系),巧妙地解决了分布式缓存中数据分片、负载均衡和弹性伸缩的核心难题,它用确定的规则取代了概率性的分布,用可控的迁移取代了大规模的数据动荡,这才是它真正厉害的地方,也是Redis Cluster能够成为一个成熟、稳定、高性能分布式解决方案的关键基石。

参考来源:Redis官方文档关于Redis Cluster的规范,以及Redis作者Salvatore Sanfilippo (antirez) 在博客和讨论中关于设计选择的阐述。