后端在分布式缓存中的一致性哈希

最直接的办法就是"哈希取模"。比方说你有三台缓存机器,算一下 key 的哈希值,然后除以 3 取余数,余数是 0 放第一台,是 1 放第二台,以此类推。简单粗暴,在机器数量固定的时候挺好使。可一旦要扩容或者缩容,麻烦就大了。比如三台机器加一台变成四台,那取模的底数就从 3 变成了 4,会导致大部分 key 的存放位置都发生变化。这意味着几乎所有的缓存数据都需要重新迁移,这简直就是一场"缓存雪崩"的预演,短时间内数据库很可能被洪水般的请求打垮。

那有没有更优雅的方案呢?有,这就是今天要聊的"一致性哈希"。它的核心目标就一个:在缓存机器数量变动时,尽可能少地迁移数据,保持系统稳定。

一致性哈希的基本思想挺巧妙的。它不用实际的机器数量来取模,而是引入一个"哈希环"的概念。你可以想象一个圆环,范围从 0 到 2^32-1。首先,对每台缓存服务器的节点(比如用IP地址或者唯一标识)进行哈希计算,得到一个值,这个值就对应环上的一个点。这样,几个节点就把这个圆环划分成了几段。

当我们要存储一个数据对象时,同样对 key 进行哈希,也得到环上的一个点。然后,从这个点开始,顺时针沿着圆环寻找,碰到的第一个节点,就把数据存在那个节点上。

这么做的精妙之处在于扩容和缩容。假设现在环上有 Node A, B, C 三个节点。现在要新增一台节点 Node D。我们同样把 D 哈希后放到环上。这时,受影响的数据仅仅是新节点 D 逆时针方向到前一个节点之间的这一小部分数据。比如 D 落在 A 和 B 之间,那么原本从 A 到 B 顺时针方向(也就是即将归属于 D 的这部分)的数据需要从 B 迁移到 D。而环上其他大部分数据,它们的归属都没有发生变化。这就完美地避免了大量数据迁移。

缩容也是同理。比如节点 B 宕机了,那么原本属于 B 的数据会顺延到下一个节点 C 上。只有这一部分数据需要重新定位,其他数据不受影响。

听起来很完美是吧?但现实往往没那么简单。基础的一致性哈希算法有两个明显的缺陷。

第一个是"数据倾斜"。如果节点数量很少,它们哈希后在环上的分布可能很不均匀,导致某两个节点之间的弧段特别长。那么大部分 key 都会落到这个弧段,最终存到同一个节点上,造成该节点压力巨大,而其他节点却很空闲。这就失去了分布式的意义。

第二个是"节点负载不均衡"。即使节点分布均匀,在节点数量少的情况下,每个节点在环上负责的弧段长度也可能差异很大。这同样会导致某些节点存储的数据多,负载高。

那怎么解决呢?答案是"虚拟节点"。

虚拟节点的思想是:不再把物理节点直接映射到哈希环上,而是为每个物理节点计算出一组哈希值,也就是生成多个"虚拟节点"放在环上。比如,对于物理节点 A,我们不是用"A"来计算一个哈希值,而是用"A1"、"A2" ... "A1000"这样的字符串计算出一千个哈希值,对应环上的一千个点。对节点 B、C 也做同样的操作。

这样,环上就被大量的虚拟节点占据了。当需要一个 key 定位节点时,还是找到它顺时针方向第一个虚拟节点,然后这个虚拟节点再归属于某个物理节点。

这么做的好处立竿见影。首先,虚拟节点数量远大于物理节点,通过大量分散的虚拟节点,可以更好地"填充"整个哈希环,使得每个物理节点负责的弧段长度更加接近,从而解决了数据倾斜和负载不均衡的问题。其次,当需要扩容时,新加入的物理节点也会对应一大批虚拟节点,这些虚拟节点会"插入"到环上的各个位置,从原有的多个物理节点那里各自"瓜分"一小部分数据,而不是从一个节点那里承接所有压力。这使得数据迁移和负载变得更加平稳。

在实际项目中,比如使用 Twemproxy 或者 Redis Cluster 这类中间件时,背后都用到了类似一致性哈希加上虚拟节点的机制。虚拟节点的数量通常可以配置,一般设置得比较大,比如几百甚至上千,以达到理想的均衡效果。

当然,一致性哈希也不是银弹。它虽然极大地减少了数据迁移量,但并没有做到零迁移。在节点变动时,那部分需要迁移的数据如果没处理好,依然可能引发问题。这就需要配合缓存失效、预热、或者一致性协议等更多手段来保证系统的最终稳定。

总而言之,从简单的哈希取模到一致性哈希,再到引入虚拟节点进行优化,这一系列演进体现的正是分布式系统设计中一个朴素的追求:在拥抱变化(节点动态增减)的同时,最大限度地保持稳定(减少数据扰动)。理解了这一点,你就能更好地驾驭分布式缓存,让它真正成为你系统里可靠的性能加速器。

相关推荐
oMcLin21 小时前
如何在Debian 11上通过配置MySQL 8.0的分布式架构,提升跨区域数据同步的效率与延迟?
分布式·mysql·debian
洛阳纸贵21 小时前
Redis
数据库·redis·缓存
一条咸鱼_SaltyFish21 小时前
[Day15] 若依框架二次开发改造记录:定制化之旅 contract-security-ruoyi
java·大数据·经验分享·分布式·微服务·架构·ai编程
梭七y1 天前
【力扣hot100题】(133)LRU缓存
leetcode·缓存·哈希算法
IT 行者1 天前
Spring Security 7 OAuth2 授权码分布式存储之Redis存储方案
redis·分布式·spring
潇凝子潇1 天前
kafka之监控告警
分布式·kafka
xiaolyuh1231 天前
Caffeine 缓存详解
缓存
五阿哥永琪1 天前
Spring Data Redis 实战避坑指南:从配置到缓存预热的全链路最佳实践
redis·spring·缓存
Light601 天前
从“报告”到“能力”——构建智能化、可审计的数据治理闭环——领码 SPARK 数据质量平台白皮书
大数据·分布式·spark
maozexijr1 天前
RabbitMQ Exchange Headers类型存在的意义?
分布式·rabbitmq