面试官常问的“一致性哈希”,都在这-18-张图里!

文章讨论了公司随着人员增长带来的IM使用问题,如何通过引入一致性哈希算法解决消息分配和扩展问题,以及面临的挑战,如客户使用和时区差异导致的停机影响。作者提供了Java开发的学习资源作为背景和解决方案的补充。
摘要由CSDN通过智能技术生成

随着公司的发展,公司的人数也急剧上升,公司内部的 IM 使用人数也跟着多了起来,新问题又随之出现了。

最主要的问题是,人们收到聊天信息的速度变慢了。原因也很简单,收取聊天信息的集群机器不够用了。解决办法可以简单直接点,再加台机器就好了。

不过,由于收消息的集群中新加入了一台机器,这时候,我们还需要额外多作一些事情:

  1. 我们需要为新加入的这台机器上的应用额外再多增加一个队列 chat02。
    image

  2. 我们还需要修改下我们的分配消息的规则,把原来的 hash(id) mod 2 修改为 hash(id) mod 3。
    image

  3. 重新启动发送消息的项目,以便修改的规则生效。

  4. 把收消息的应用部署到新机器上。
    image

到这时,一切还都在可控范围。开发人员只需要在需要的时候,新增加个队列,然后把我们的分配规则小小的修改下即可。

但是,他们不知道的是,暴风雨就要来了。

3. 新的问题来了,也许这就是人生吧

由于公司内部很多人在使用这个 IM 工具。有些时候,为了方便,公司的客户还有一些合作方也用起了这个 IM。这让事情变得复杂了起来。起初,开发人员还是像往常一样,每当人们抱怨说收消息过慢的时候,他们就会加一台机器。

最糟糕的是,公司的客户也会抱怨,他们发现 IM 有时候彻底不可用。这可不是小事情。公司内部人员的问题还可以内部沟通解决。但是公司客户的问题,大意不得,因为这关系到公司产品的名誉。

那么,这到底是怎么一回事呢?

原来,根本原因还在于每次修改完配置规则后的重启服务。每次修改完配置规则,就需要规划好一个恰当的停机时间,去重新对项目做个上线。

但是,这种方法在公司的客户也使用这个 IM 后就行不通了。因为公司的客户有不少是在国外的。也就是说,不管白天还是深夜,很可能总是有人在使用这个 IM。

这就迫使开发人员们,在增加机器时,还需要去和多方协调沟通出一个上线时间,然后发布公告,再去上线。这种反复沟通,再上线,再反复沟通,再上线直接把开发人员们折腾了个半死。

往往沟通完,上线时间直接被放到了半个月以后。而在这半个月里,开发人员还要承受无数内部 IM 使用人的口水。费心竭力的沟通,声嘶力竭的解释,缺眠少觉的上线,这一切的一切推动着开发人员们必须对眼前这套技术方案作出改变了。

4. 思路转起来,队列环起来

新的技术方案的需求本质就是:

无论是分配消息规则变化还是集群机器添加都不能停机停服务

对于这种情况,一个很好的解决方案就是如果我们对项目配置文件进行动态的定时检测,当发现变动时,刷新配置规则即可。
image

一切看上去很美好,采用了动态的定时检测后,每当我们需要新增集群中的机器时,我们只需要如下三个步骤了:

  1. 增加一个队列
  2. 修改分配消息的规则
  3. 部署新的机器

客户毫无感知,开发人员们也不需要和用户们协调沟通出专门的上线安排。可是,这个方案也存在一些问题:

  1. 随着我们的系统部署越来越多,我们需要手工修改规则的系统也越来越多。
  2. 如果消费机器宕机了,我们需要删除队列,同时还需要去删除修改分配消息的规则,等到机器恢复了,我们还要再把分配消息的规则改回去。

这个分配消息的规则真讨厌啊,每次有变动,就要去关心这个分配消息的规则。有没有什么办法能把这个分配变得更自动化一些呢?

如果我们假设在 MQ 中有 100 个收发聊天信息的队列(100:这是对我们的IM不可能达到的一个数字),我们只需要在配置规则中配置成:

hash(id) mod 100

然后,我们的发送消息的应用启动后,去动态的探测出真实的所有收发聊天信息的队列信息。

当我们通过哈希算出的编号发现没有真实对应的队列存在时,就根据一定的规则,去找到一个真实存在的队列,这个队列,就是我们要发消息的队列。

如果我们做到这样,那么以后,每次队列有变化,无论增多还是减少,我们都不需要再去考虑分配规则的事情了,只需要移除有问题的队列或者增加有对应消费者的队列即可。

这个思想,就是一致性哈希的思想。

具体怎么做呢?

第一步,我们假设有个 100 个收发聊天信息的队列,并且这些队列处于一个环上。
image

第二步,我们获取到真实的收发聊天信息的队列数量,假设有 5 个。
image

第三步,我们把真实的队列映射到我们第一步假设的环中。
image

第四步,我们通过分配规则 hash(id) mod 100 计算出对应的队列编号。

如果 hash(id) 的结果为 2000,那么算出的队列编号 m = 0。这时候,我们一查,发现对应编号 0 的 chat00 队列确实存在,那么就直接发送消息到 chat00 中。
image

如果我们的 hash(id) 的结果为 1999,那么算出的队列编号 m = 99。此时,我们去查队列映射关系,发现 99 编号并没有对应的真实队列。这时候怎么办?很简单,我们顺时针继续往下找,找到谁了呢?0 对应的 chat00 队列,这是真实存在的,这时候,我们就将消息发送到 chat00 队列中。
image

上面四步就是一个基本的一致性哈希算法了。

那么,这套一致性哈希算法满足我们不想总是更新消息分配规则的需求吗?让我们验证一下:

  1. 假设我们需要在消费信息端集群增加一台机器
    我们如果要增加一台机器,那么同时我们也需要在 MQ 中增加一个队列。这时候,我们的分配规则是 hash(id) mod 100,增加了队列后,真实的队列数假设为 6。此时,如果 hash(id) mod 100 的结果小于 6,那么分配的规则和没有增加机器的时候规则一样,以前分配到哪个队列,现在还是分配到哪个队列。但是对于结果等于 6 的情况,则发生了变化。信息会被自动分配给 chat05。当分配给 chat05 后,新的消费者就会自动开始进入正常工作了,我们不需要做任何人工干预,也不需要考虑分配规则的变化。

增加机器以前:
image

增加机器之后:
image

  1. 假设消费信息端集群一台机器宕机了
    模拟宕机,此时我们会去减少一个队列。减少后的真实队列数量为 5,则正好和增加队列相反,m = 5 时,那么行为不会有任何变化,以前分到哪个队列,还是分到哪个队列。如果 m = 6,由于已经不存在真实的队列了,就会做顺时针查找,结果找到 chat00,以前会分到 chat05 的就会被分到 chat00。而此时,chat00 由于正好有消费者,所以,系统的用户是毫无感知的,我们也专心修复我们机器即可。当机器恢复后,就会和新增机器一样,计算结果为 6 的信息会被重新分配回 chat05。

目前,我们可以看到,当我们引入一致性哈希后,我们不管新增机器还是集群机器宕机,我只需要跟随着机器的状态,做一个操作即可:增加或者减少 MQ 中的队列。一切简单化了。

那么,这个方案是否依然还有问题呢?

5. 失衡的圆环,压垮骆驼的可能只是一根稻草

假设我们目前有 5 个队列存在,我们的分配规则是 m = hash(id) mod 100。那么,此时,问题就出来了。

如果 m 的值大于 5,由于没有对应的真实队列存在,系统就会顺时针顺着我们构造出来的哈希环找,最终会找到 chat00 这个队列上。

然后,你会发现,只要是 m 值大于 5 的 id 对应用户发的信息,最终都会落入到 chat00 队列中。
image

在极端情况下,如果大量的信息涌入到 chat00 队列里,由于对应 chat00 的消费者处理不过来,很可能会导致这个消费者的崩溃。

然后,去除队列后,根据规则,又会有大量的信息涌入到 chat00 后续的队列 chat01 里,这些信息又会导致 chat01 对应应用的崩溃,最终引发整个集群的崩溃,这就是雪崩效应。

我们需要一种更巧妙的办法来解决这个问题。

6. 从实变虚,也许我们应该更敢想一些

经过上面的论述,我们发现,我们在分配队列时,之所以失衡,是因为我们的队列在圆环上的分配失衡。

我们所有的真实队列都是按照顺时针依次排布在圆环上的。在上面的场景里,我们只有 5 个队列。此时,我们假设会有 100 个队列。那么,m = hash(id) mod 100 这个公式里:

m 大于 5 的概率为 95%

由于我们的 5 个队列是按照编号顺序依次排列的。那就说明所有 m 大于 5 的信息就都会映射到一个不存在的队列上,最终,根据规则,顺时针滑到了 0 对应的 chat00 队列中。

如果,我们可以让真实存在的队列均匀分布到环上,那么,这种严重失衡的现象还会再出现吗?
image

image

从上面的图我们可以看出,如果我们能让真实的队列均匀的在圆环上分布,那么这种严重失衡的现象就会得到极大的缓解。

那么如何让这些队列能均匀的分布在这个圆环中呢?还记得我们在苦恼分配信息规则的不断修改时,我们大胆的假设了一个我们的 IM 系统永远也不可能达到的队列数字吗?

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

读者福利

由于篇幅过长,就不展示所有面试题了,感兴趣的小伙伴

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」

更多笔记分享

35K成功入职:蚂蚁金服面试Java后端经历!「含面试题+答案」
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
f-1712529927818)]

更多笔记分享

[外链图片转存中…(img-6NBHqzzD-1712529927819)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值