消息队列 MQ 基础知识整理

1 为什么需要消息队列?

1.1 异步处理

  • 可以更快地返回结果

  • 减少等待

1.2 流量控制

方法一

生产者生产到消息队列中,消费者根据自身消费能力,从消息队列中拉取

这种设计的优点是:能根据下游的处理能力自动调节流量,达到削峰填谷的作用。但这样做同样是有代价的:

  • 增加了系统调用链环节,导致总体的响应时延变长。
  • 上下游系统都要将同步调用改为异步消息,增加了系统的复杂度。

方法二

解决办法使用令牌桶算法

单位时间内只发放固定数量的令牌到令牌桶中,规定服务在处理请求之前必须先从令牌桶中拿出一个令牌,如果令牌桶中没有令牌,则拒绝请求

1.3 服务解耦

所有消费者订阅同一个主题,生产者生产一份数据,订阅该主题的所有消费者都能收到完整数据(发布 - 订阅模型)

1.4 引入新的问题

  • 引入消息队列带来的延迟问题
  • 增加了系统的复杂度
  • 可能产生数据不一致的问题
  • 消息丢失问题

2 如何确保消息不会丢失?

2.1 检测消息丢失的方法

利用消息队列的有序性来验证是否有消息丢失

在 Producer 端,我们给每个发出的消息附加一个连续递增的序号,然后在 Consumer 端来检查这个序号的连续性

2.2 确保消息可靠传递

消息队列 MQ 基础知识整理
使用请求确认机制

1. 生产阶段

Broker 获得消息后再向生产者发送确认消息。你需要捕获消息发送的错误,并重发消息。

2. 存储阶段

将消息写入磁盘后再给 Producer 返回确认响应

集群环境,至少将消息发送到 2 个以上的节点,再给客户端回复发送确认响应

3. 消费阶段
客户端从 Broker 拉取消息后,执行用户的消费业务逻辑,成功后,才会给 Broker 发送消费确认响应

2.3 引入新的问题

无论是 Broker 还是 Consumer 有可能收到重复消息

3 如何处理消费过程中的重复消息?

3.1 传递消息服务质量标准

  • At most once:至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息
  • At least once:至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
  • Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。

常用的绝大部分消息队列提供的服务质量都是 At least once

3.2 幂等解决消息重复问题

幂等原本是数学的概念,指 f(f(x)) = f(x)。拓展到计算机领域,就是指一个方法调用一次和调用 N 次所产生的效果一样

举例:

  1. 将张三账户的余额设置为 100 元,幂等
  2. 将张三账户的余额加一百元,非幂等

从对系统的影响结果来说:At least once + 幂等消费 = Exactly once

3.3 设计幂等操作的方法

张三账户的余额加一百元。如何变成幂等操作呢?

1. 利用数据库唯一性约束实现幂等

账户 ID与账单 ID 联合组成唯一约束,表里最多只能存在一条记录

只要是支持类似INSERT IF NOT EXIST语义的存储类系统都可以用于实现幂等,比如,你可以用 Redis 的 SETNX 命令来替代数据库中的唯一约束

2. 为更新的数据设置前置条件

若张三当前余额为 100 元时,增加 100 元

更加通用的方法是,给你的数据增加一个版本号属性

3. 记录并检查操作

上述两种方法都不能解决时

给每条消息指定一个全局唯一的 ID,消费时,先根据这个 ID 检查这条消息是否有被消费过,如果没有消费过,才更新数据,然后将消费状态置为已消费。

三个操作必须作为一组操作保证原子性,才能真正实现幂等

引入新的问题:分布式事务、分布式锁

4 如何解决消息积压问题?

4.1 消息积压的直接原因

系统中的某个部分出现了性能问题,来不及处理上游发送的消息,才会导致消息积压

一定要保证消费端的消费性能要高于生产端的发送性能,这样的系统才能健康的持续运行

4.2 性能优化

1. 发送端性能优化

发送端都是先执行自己的业务逻辑,最后再发送消息。

你的代码发送消息的性能上不去,你需要优先检查一下,是不是发消息之前的业务逻辑耗时太多导致的。

2. 消费端性能优化

水平扩展消费者

收到消息后,立刻返回确认 -> 会丢消息

4.3 突发积压处理

导致积压突然增加,最粗粒度的原因,只有两种:要么是生产变快了,要么是消费变慢了。

1. 生产变快了

没办法的办法是,将系统降级,通过关闭一些不重要的业务,减少发送方发送的数据量,最低限度让系统还能正常运转,服务一些重要业务。

2. 消费变慢了

优先检查一下日志是否有大量的消费错误

笔记整理于


李玥:消息队列高手课