消息队列基础

Posted by 淦 Blog on March 13, 2025

简介

使用场景

  • 异步处理(可以更快地返回结果,减少等待,自然实现了步骤之间的并发,提升系统总体的性能)
  • 流量控制(隔离网关和后端服务,以达到流量控制和保护后端服务的目的)
  • 服务解耦
  • 作为发布 / 订阅系统实现一个微服务级系统间的观察者模式
  • 连接流计算任务和数据
  • 用于将消息广播给大量接收者

问题和局限性

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

常见的开源的消息队列中间件

RabbitMQ

消息模型

Exchange 位于生产者和队列之间,生产者并不关心将消息发送给哪个队列,而是将消息发送给 Exchange,由 Exchange 上配置的策略来决定将消息投递到哪些队列中。

优点

  • 使用Erlang语言编写的,它最早是为电信行业系统之间的可靠通信设计的,也是少数几个支持 AMQP 协议的消息队列之一。
  • 相当轻量级的消息队列,非常容易部署和使用。
  • 支持非常灵活的路由配置,和其他消息队列不同的是,它在生产者(Producer)和队列(Queue)之间增加了一个 Exchange 模块,可以理解为交换机,可根据配置的路由规则将生产者发出的消息分发到不同的队列中。

缺点

  • 对消息堆积的支持并不好,在它的设计理念里面,消息队列是一个管道,大量的消息积压是一种不正常的情况,应当尽量去避免。当大量消息积压的时候,会导致性能急剧下降。
  • 性能是这几个消息队列中最差的,大概每秒钟可以处理几万到十几万条消息。
  • 使用的编程语言 Erlang 不仅是非常小众的语言,而且学习曲线非常陡峭。做一些扩展和二次开发,建议慎重考虑可持续维护的问题。

RocketMQ

消息模型

每个主题包含多个队列,通过多个队列来实现多实例并行生产和消费。只在队列上保证消息的有序性,主题层面是无法保证消息的严格顺序的。

订阅者的概念是通过消费组(Consumer Group)来体现的。每个消费组都消费主题中一份完整的消息,不同消费组之间消费进度彼此不受影响,即一条消息被 Consumer Group1 消费过,也会再给 Consumer Group2 消费。消费组中包含多个消费者,同一个组内的消费者是竞争消费的关系,每个消费者负责消费组内的一部分消息。如果一条消息被消费者 Consumer1 消费了,那同组的其他消费者就不会再收到这条消息。

在 Topic 的消费过程中,由于消息需要被不同的组进行多次消费,所以消费完的消息并不会立即被删除,这就需要为每个消费组在每个队列上维护一个消费位置(Consumer Offset),这个位置之前的消息都被消费过,之后的消息都没有被消费过,每成功消费一条消息,消费位置就加一。

分布式事务

增加了事务反查的机制来解决事务消息提交失败的问题。

在提交或者回滚事务消息时发生网络异常,RocketMQ 的 Broker 没有收到提交或者回滚的请求,Broker 会定期去 Producer 上反查这个事务对应的本地事务的状态,然后根据反查结果决定提交或者回滚这个事务。

为了支撑这个事务反查机制,业务代码需要实现一个反查本地事务状态的接口,告知 RocketMQ 本地事务是成功还是失败。

优点

  • 有非常活跃的中文社区,使用 Java 语言开发,很容易对 RocketMQ 进行扩展或者二次开发。
  • 对在线业务的响应时延做了很多的优化,大多数情况下可以做到毫秒级的响应。
  • 每秒钟大概能处理几十万条消息。

缺点

  • 作为国产的消息队列,相比国外的比较流行的同类产品,在国际上还没有那么流行,与周边生态系统的集成和兼容程度要略逊一筹。

Kafka

消息模型

和 RocketMQ 是完全一样的,唯一的区别是,在 Kafka 中,队列这个概念的名称不一样,Kafka 中对应的名称是“分区(Partition)”,含义和功能是没有任何区别的。

分布式事务

直接抛出异常,让用户自行处理。可以在业务代码中反复重试提交,直到提交成功,或者删除之前创建的订单进行补偿。

优点

  • 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域,几乎所有的相关开源软件系统都会优先支持 Kafka。
  • 使用 Scala 和 Java 语言开发,设计上大量使用了批量和异步的思想,拥有超高的性能。尤其是异步收发的性能,大约每秒钟可以处理几十万条消息。

缺点

  • Broker使用“先攒一波再一起处理”的设计,同步收发消息的响应时延比较高。
  • 当业务场景中,每秒钟消息数量没有那么多的时候,时延反而会比较高,不太适合在线业务场景。

Pulsar

优点

  • 分布式发布-订阅消息平台,具有非常灵活的消息模型和直观的客户端 API。
  • 分层架构(Broker + BookKeeper),存储计算分离。
  • 相比 Kafka,具有更高的吞吐量和更低的延迟。

缺点

  • 架构设计复杂(如 Broker + BookKeeper 的分层架构、多租户模型等),其高级功能(如分层存储、事务消息)需要较长时间的学习和实践。
  • 社区规模较小,相比 Kafka 的庞大社区和商业支持(如 Confluent),Pulsar 的开发者生态和第三方解决方案仍处于发展阶段。

总结

  • RabbitMQ:对消息队列功能和性能都没有很高的要求,只需要一个开箱即用易于维护的产品。
  • RocketMQ:低延迟和金融级的稳定性,主要场景是处理在线业务,比如在交易系统中用消息队列传递订单。
  • Kafka:处理海量的消息,像收集日志、监控信息或是前端的埋点这类数据,或是应用场景大量使用了大数据、流计算相关的开源产品。
  • Pulsar:云原生架构、低延迟和多场景适配性,尤其在金融、游戏、IoT 和全球化业务中表现突出。

确保消息不丢失

检测消息丢失的方法

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

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

确保消息可靠传递

生产阶段

在编写发送消息代码时,需要注意,正确处理返回值或者捕获异常,就可以保证这个阶段的消息不会丢失。

存储阶段

如果对消息的可靠性要求非常高,可以通过配置 Broker 参数来避免因为宕机丢消息。

消费阶段

不要在收到消息后就立即发送消费确认,而是应该在执行完所有消费业务逻辑之后,再发送消费确认。

消费过程中重复消息的处理

用幂等性解决重复消息问题

在消费端,让消费消息的操作具备幂等性。其任意多次执行所产生的影响均与一次执行的影响相同。从业务逻辑设计上入手,将消费的业务逻辑设计成具备幂等性的操作。

  • 利用数据库的唯一约束实现幂等
  • 为更新的数据设置前置条件
  • 记录并检查操作

消息积压的处理

优化性能来避免消息积压

发送端性能优化

  • 优先检查一下,是不是发消息之前的业务逻辑耗时太多导致的。
  • 增加批量或者是增加并发

消费端性能优化

  • 要保证消费端的消费性能要高于生产端的发送性能,这样的系统才能健康的持续运行。
  • 在扩容 Consumer 的实例数量的同时,必须同步扩容主题中的分区(也叫队列)数量,确保 Consumer 的实例数和分区数量是相等的。