Cluster集群可以基于一致性哈希算法么?
作者:程序员马丁
在 线博客:https://nageoffer.com
Ragent AI —— 从 0 到 1 纯手工打造企业级 Agentic RAG,拒绝 Demo 玩具!AI 时代,助你拿个offer。
回答话术
首先,由于数据要分片存储在不同的服务器节点上,因此当用户发起请求时,我们会需要一个哈希算法保证对某个 Key 的请求总是路由到指定的服务器节点。传统的哈希算法基于长度(也就是节点数量)进行取模,因此当扩容和缩容时会需要对大量的数据进行迁移。而一致性哈希算法则用于解决这个问题。
在一致性哈希中,规定了一个固定大小的哈希环,环上均匀分布有 2^32 个节点,服务器节点也分布在环上。当用户发起请求时,将会根据 Key 的哈希值计算出落点,若 Key 的落点没 有对应的服务器节点,则顺时针寻找最近的服务器节点。当扩容时,仅需要对新节点与上一节点之间的数据进行迁移,而缩容亦同理。
一致性哈希的优点在于避免了传统哈希算法中扩容和缩容时的大规模数据迁移,但是它也有缺点,那就是当节点在环上分布不均匀就可能导致数据倾斜。因此,当节点掉线或缩容时,可能因为将全部数据压到下一节点而导致服务雪崩。为了解决这个问题,我们需要允许一个节点在环上映射出多个虚拟节点,这样就能尽可能的使请求均匀的打到不同的服务器上。
Redis 集群采用的哈希槽方案同样基于固定数量(2^14)的哈希槽实现,不过相对于一致性哈希,每个节点拥有的哈希槽是可以直接分配的:
- 我们可以直接根据服务器的性能而为它们分配不同数量的哈希槽位,一致性哈希虽然也可以通过改变某个服务器对应虚拟节点的数量做到类似的效果,不过显然没有这么精准。
- 当扩容和缩容的时候,我们可以手动指定将哪些槽位转移到哪些节点,而一致性哈希则做不到类似的效果。
总的来说,哈希槽在使用上比一致性哈希更加灵活,并且实现上也更加简单。
问题详解
1. 为什么需要哈希算法?
在 Redis 集群中,每个分片中的节点存储的数据都是不一样的。因此当 客户端进行访问的时候,就需要有一个哈希算法,能保证总是将同一个 Key 路由到一个相同的节点上。
要实现这样的效果,最简单方法就是直接根据节点总数取模,比如我们有三个节点,那么就需要对所有的 Key 哈希值基于三取模( hash(key) % 3),最终得到的模数即为要访问的节点下标。
不过这个方案有一个致命的缺点,由于节点数量直接影响计算结果,因此当扩容或者缩容的时候,由于 Key 的哈希结果会发生变化,因此可能会引起大规模的数据迁移。
2. 什么是一致性哈希?
2.1. 概述
一致性哈希算法与传统哈希算法一样,也基于长度取模,但是它的长度是一个固定值 2^32。
它假设 2^32 个节点均匀分布在一个哈希环中,然后:
- 确认节点的位置:在开始前,对所有的存储节点进行哈希,确认存储节点落在环中的哪一个位置。
- 确认 Key 的位置:当使用时,对 Key 进行哈希,确认 Key 落在环中的哪一个位置。
- 二次寻址:当 Key 在哈希环上的落点并无直接对应的存储节点时,它再按顺时针寻找最近一个存储节点。
简而言之,这个做法相当于让每个节点都负责哈希环上的一小段区间,它的扩容或者缩容的时候,实际上就是对区间的重新划分。
2.2. 扩容和缩容
基于上面的示例,假如我们在 C 和 A 之间新增一个 D 节点,那么我们仅需要将 C 到 A 之间的数据从原本的 A 迁移到 D 即可:
而缩容则同理,比如现在我们再将节点 A 下线,那么原本的 D 到 A 之间的数据将从原本的 A 迁移到 B:
可见,在这种情况下,扩容和缩容实际上只会影响到一个节点,当进行数据迁移时,最多也只需要迁移一个区间段的数据,相比起传统的哈希算法,它尽可能的降低的迁移的数据量。
2.3. 数据倾斜与虚拟节点
不过,一致性哈希的机制决定了它天生就带有一个缺点:由于哈希算法的随机性,当节点的位置分散的不够均匀时,就会导致部分节点间的间隔过大,此时部分节点将会被迫承担更多的数据,进而造成整个集群的数据倾斜:
比如在上图中,由于节点分布不均匀,就导致 A 节点承担了过多的数据。
糟糕的是,结合它的扩缩容机制,我们不难意识到,当一个节点下线时,实际上上一节点的数据和请求都会直接压到它的下一个节点,如果下一个节点没顶住,就会再压到下下个节点……挂掉的节点越多,下一个节点挂掉的可能性就越大,如此一来,就会造成服务雪崩。
解决这个问题的核心,在于要让节点分布的尽可能的均匀,解决方案也很简单,既然节点数量少会导致区间段划分不均匀,那么我多加几个节点不就行了?
比如,在上文的例子中,我们原本只有 A、B、C 三个真实节点,但是我们可以为每个真实节点都额外创建一个虚拟节点,这样整个哈希环上就有了 6 个节点,A,A',B,B',C,C',其中 A',B',C' 都为虚拟节点,当请求到它们的时候,将会从该虚拟节点转发到对应的真实节点上:
当节点 A 下线以后,它所负责的两部分数据,一部分将会迁移到节点 B,而另一部分将会迁移到节点 C:
你部署的节点越多,并且虚拟节点设置的越多,那么节点就会分布的更均匀,数据倾斜的可能性就越小,而当扩缩容进行数据迁移时,服务雪崩的风险也就越小。
在日常中,我们最经常接触到的一致性哈希算法的落地方案,应该是 Dubbo 的一致性哈希负载均衡策略,有兴趣的可以去了解一下。