一文搞懂消息队列

1. 为什么需要消息队列?

消息队列(Message Queue),简称为MQ,是一种跨进程的通信机制,用于上下游传递消息。常见消息队列中间件如:Kafka、ActiveMQ、RabbitMQ、RocketMQ等。

优势:
我们在单体应用里面需要用队列解决的问题,在分布式系统中大多都可以用消息队列来解决。

  • 解耦
  • 异步
  • 削峰

劣势:

  • 系统可用性降低:系统每增加一个组件,必然导致可用性降低。毕竟没有一个组件可以保证100%可用性,因此还需要在消息队列高可用方面花费投入。
  • 系统复杂性增加:在使用消息队列后,会增加很多方面的问题,比如如何保证消息不被重复消费、如何保证消息可靠传输、如何保证数据一致性问题和如何解决海量消息的积压故障。因此,需要考虑的东西更多,系统复杂性增大。

2. 可供选择的消息队列产品

选择消息队列产品的基本标准:

  • 开源
  • 有活跃度
  • 与周边生态系统兼容
  • 消息的可靠传递:确保不丢消息;
  • Cluster:支持集群,确保不会因为某个节点宕机导致服务不可用,当然也不能丢消息;
  • 性能:具备足够好的性能,能满足绝大多数场景的性能要求

2.1 RabbitMQ

RabbitMQ 是使用一种比较小众的编程语言:Erlang 语言编写的,它最早是为电信行业系统之间的可靠通信设计的,也是少数几个支持 AMQP 协议的消息队列之一。

优势:

  • RabbitMQ 是一个相当轻量级的消息队列,非常容易部署和使用

劣势:

  • 对消息堆积的支持并不好,在它的设计理念里面,消息队列是一个管道,大量的消息积压是一种不正常的情况,应当尽量去避免。当大量消息积压的时候,会导致 RabbitMQ 的性能急剧下降。
  • 性能是我们介绍的这几个消息队列中最差的,依据硬件配置的不同,它大概每秒钟可以处理几万到十几万条消息。
  • 使用的编程语言 Erlang,这个编程语言不仅是非常小众的语言,更麻烦的是,这个语言的学习曲线非常陡峭。

2.2 RocketMQ

RocketMQ 是阿里巴巴在 2012 年开源的消息队列产品,后来捐赠给 Apache 软件基金会,2017 正式毕业,成为 Apache 的顶级项目。

优势:

  • RocketMQ 有非常活跃的中文社区
  • RocketMQ 的性能比 RabbitMQ 要高一个数量级,每秒钟大概能处理几十万条消息。

劣势:

  • 在国际上还没有那么流行,与周边生态系统的集成和兼容程度要略逊一筹。

2.3 Kafka

优势:

  • Kafka 与周边生态系统的兼容性是最好的没有之一,尤其在大数据和流计算领域,几乎所有的相关开源软件系统都会优先支持 Kafka。
  • Kafka 的性能,尤其是异步收发的性能,是三者中最好的,但与 RocketMQ 并没有量级上的差异,大约每秒钟可以处理几十万条消息。

劣势:

  • 它的同步收发消息的响应时延比较高,因为当客户端发送一条消息的时候,Kafka 并不会立即发送出去,而是要等一会儿攒一批再发送

2.4 总结

  • 你对消息队列功能和性能都没有很高的要求,只需要一个开箱即用易于维护的产品,建议使用 RabbitMQ。
  • 使用消息队列主要场景是处理在线业务,比如在交易系统中用消息队列传递订单,那 RocketMQ 的低延迟和金融级的稳定性是你需要的。
  • 如果你需要处理海量的消息,像收集日志、监控信息或是前端的埋点这类数据,或是你的应用场景大量使用了大数据、流计算相关的开源产品,那 Kafka 是最适合你的消息队列。

3. 消息队列的基础概念

3.1 队列模型

早期的消息队列,就是按照“队列”的数据结构来设计的
队列模型

  • 如果有多个生产者往同一个队列里面发送消息,这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。
  • 如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。

3.2 发布 - 订阅模型(Publish-Subscribe Pattern)

如果需要将一份消息数据分发给多个消费者,要求每个消费者都能收到全量的消息,例如,对于一份订单数据,风控系统、分析系统、支付系统等都需要接收消息。这个时候,单个队列就满足不了需求,一个可行的解决方式是,为每个消费者创建一个单独的队列,让生产者发送多份。

显然这是个比较蠢的做法,同样的一份消息数据被复制到多个队列中会浪费资源,更重要的是,生产者必须知道有多少个消费者。为每个消费者单独发送一份消息,这实际上违背了消息队列“解耦”这个设计初衷。

为了解决这个问题,演化出了另外一种消息模型:“发布 - 订阅模型(Publish-Subscribe Pattern)”。
发布订阅模型

在发布 - 订阅模型中:

  • 消息的发送方称为发布者(Publisher)
  • 消息的接收方称为订阅者(Subscriber)
  • 服务端存放消息的容器称为主题(Topic)

发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。

4. 消息队列的常见问题

4.1 如何确保消息不丢失

现在主流的消息队列产品都提供了非常完善的消息可靠性保证机制,完全可以做到在消息传递过程中,即使发生网络中断或者硬件故障,也能确保消息的可靠传递,不丢消息。

检测消息丢失的方法

我们可以利用消息队列的有序性来验证是否有消息丢失:

  • 原理非常简单,在 Producer 端,我们给每个发出的消息附加一个连续递增的序号
  • 然后在 Consumer 端来检查这个序号的连续性。
  • 如果没有消息丢失,Consumer 收到消息的序号必然是连续递增的,或者说收到的消息,其中的序号必然是上一条消息的序号 +1。如果检测到序号不连续,那就是丢消息了。
  • 还可以通过缺失的序号来确定丢失的是哪条消息,方便进一步排查原因。

大多数消息队列的客户端都支持拦截器机制,你可以利用这个拦截器机制,在 Producer 发送消息之前的拦截器中将序号注入到消息中,在 Consumer 收到消息的拦截器中检测序号的连续性,这样实现的好处是消息检测的代码不会侵入到你的业务代码中,待你的系统稳定后,也方便将这部分检测的逻辑关闭或者删除。

确保消息可靠传递
确保消息可靠传递

  • 在生产阶段,你需要捕获消息发送的错误,并重发消息。
  • 在存储阶段,你可以通过配置刷盘和复制相关的参数,让消息写入到多个副本的磁盘上,来确保消息不会因为某个 Broker 宕机或者磁盘损坏而丢失。
  • 在消费阶段,你需要在处理完全部消费业务逻辑之后,再发送消费确认。

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

消息重复的情况必然存在:

在 MQTT 协议中,给出了三种传递消息时能够提供的服务质量标准,这三种服务质量从低到高依次是:

  • At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
  • At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
  • Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。

我们现在常用的绝大部分消息队列提供的服务质量都是 At least once,包括 RocketMQ、RabbitMQ 和 Kafka 都是这样。也就是说,消息队列很难保证消息不重复。

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

一般解决重复消息的办法是,从业务逻辑设计上入手,将消费的业务逻辑设计成具备幂等性的操作。

一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同。
几种常用的设计幂等操作的方法:

  • 利用数据库的唯一约束实现幂等:INSERT IF NOT EXIST
  • 为更新的数据设置前置条件:在发消息时在消息体中带上当前的余额,在消费的时候进行判断数据库中,当前余额是否与消息中的余额相等,只有相等才执行变更操作

4.3 消息积压了该如何处理

我们在设计系统的时候,一定要保证消费端的消费性能要高于生产端的发送性能,这样的系统才能健康的持续运行。

消费端的性能优化除了优化消费业务逻辑以外,也可以通过水平扩容,增加消费端的并发数来提升总体的消费性能。

如果短时间内没有足够的服务器资源进行扩容,没办法的办法是,将系统降级,通过关闭一些不重要的业务,减少发送方发送的数据量,最低限度让系统还能正常运转,服务一些重要业务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值