消息队列RibbitMQ

本文探讨了消息队列如何通过异步处理、削峰解耦提升系统性能,以及带来的问题如可用性降低、复杂性增加和一致性挑战。对比了RPC和消息队列的区别,并针对RabbitMQ、RocketMQ、Kafka、Pulsar等常见消息队列的特性和适用场景进行了介绍,还涉及了高可用性和解决消息问题的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、为什么使用消息队列?消息队列好处

1、异步:通过异步处理提高系统性能(减少响应所需时间)

2、削峰:削峰/限流

3、解耦:降低系统耦合性。

通过异步处理提高系统性能(减少响应所需时间)

将用户的请求数据存储到消息队列之后就立即返回结果。随后,系统再对消息进行消费。

因为用户请求数据写入消息队列之后就立即返回给用户了,但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此,使用消息队列进行异步处理之后,需要适当修改业务流程进行配合

比如用户在提交订单之后,订单数据写入消息队列,不能立即返回用户订单提交成功,需要在消息队列的订单消费者进程真正处理完该订单之后,甚至出库后,再通过电子邮件或短信通知用户订单成功,以免交易纠纷。这就类似我们平时手机订火车票和电影票。

​​​​​​​削峰/限流

先将短时间高并发产生的事务消息存储在消息队列中,然后后端服务再慢慢根据自己的能力去消费这些消息,这样就避免直接把后端服务打垮掉。

举例:在电子商务一些秒杀、促销活动中,合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。

​​​​​​​降低系统耦合性

使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用,那么新增模块或者修改模块就对其他模块影响较小,这样系统的可扩展性无疑更好一些。

消息队列使用发布-订阅模式工作,消息发送者(生产者)发布消息,一个或多个消息接受者(消费者)订阅消息。消息发送者(生产者)和消息接受者(消费者)之间没有直接耦合,消息发送者将消息发送至分布式消息队列即结束对消息的处理,消息接受者从分布式消息队列获取该消息后进行后续处理,并不需要知道该消息从何而来。对新增业务,只要对该类消息感兴趣,即可订阅该消息,对原有系统和业务没有任何影响,从而实现网站业务的可扩展性设计

消息接受者对消息进行过滤、处理、包装后,构造成一个新的消息类型,将消息继续发送出去,等待其他消息接受者订阅该消息。因此基于事件(消息对象)驱动的业务架构可以是一系列流程。

另外,为了避免消息队列服务器宕机造成消息丢失,会将成功发送到消息队列的消息存储在消息生产者服务器上,等消息真正被消费者服务器处理后才删除消息。在消息队列服务器宕机后,生产者服务器会选择分布式消息队列服务器集群中的其他服务器发布消息。

备注:

不要认为消息队列只能利用发布-订阅模式工作,只不过在解耦这个特定业务环境下是使用发布-订阅模式的。除了发布-订阅模式,还有点对点订阅模式(一个消息只有一个消费者),我们比较常用的是发布-订阅模式。另外,这两种消息模型是 JMS 提供的,AMQP 协议还提供了另外 5 种消息模型。

​​​​​​​实现分布式事务

我们知道分布式事务的解决方案之一就是 MQ 事务。

RocketMQ、 Kafka、Pulsar、QMQ 都提供了事务相关的功能。事务允许事件流应用将消费,处理,生产消息整个过程定义为一个原子操作。

​​​​​​​二、使用消息队列会带来哪些问题?

1、系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入 MQ 之前,你不用考虑消息丢失或者说 MQ 挂掉等等的情况,但是,引入 MQ 之后你就需要去考虑了!

2、系统复杂性提高: 加入 MQ 之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!

3、一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!

​​​​​​​三、RPC 和消息队列的区别

RPC 和消息队列本质上是网络通讯的两种不同的实现机制,两者的用途不同,万不可将两者混为一谈。

RPC 和消息队列都是分布式微服务系统中重要的组件之一,简单对比一下两者:

1、从用途来看:RPC 主要用来解决两个服务的远程通信问题,不需要了解底层网络的通信机制。通过 RPC 可以帮助我们调用远程计算机上某个服务的方法,这个过程就像调用本地方法一样简单。消息队列主要用来降低系统耦合性、实现任务异步、有效地进行流量削峰。

2、从通信方式来看:RPC 是双向直接网络通讯,消息队列是单向引入中间载体的网络通讯。

3、从架构上来看:消息队列需要把消息存储起来,RPC 则没有这个要求,因为前面也说了 RPC 是双向直接网络通讯。

4、从请求处理的时效性来看:通过 RPC 发出的调用一般会立即被处理,存放在消息队列中的消息并不一定会立即被处理。

四、消息队列选型

ActiveMQ(不推荐)

性能较差,不推荐使用,目前新项目的话很少有人使用这个。

​​​​​​​RibbitMQ(性能好、延时低)

RabbitMQ 是采用 Erlang 语言实现 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)的消息中间件,它最初起源于金融系统,用于在分布式系统中存储转发消息。

RabbitMQ 在吞吐量方面虽然稍逊于 Kafka、RocketMQ 和 Pulsar,但是由于它基于 Erlang 开发,所以并发能力很强,性能极其好,延时很低,达到微秒级。但是也因为 RabbitMQ 基于 Erlang 开发,所以国内很少有公司有实力做 Erlang 源码级别的研究和定制。如果业务场景对并发量要求不是太高(十万级、百万级),那这几种消息队列中,RabbitMQ 或许是你的首选。

​​​​​​​RocketMQ(一致性、可靠性)

RocketMQ 是阿里开源的一款云原生“消息、事件、流”实时数据处理平台,借鉴了 Kafka,已经成为 Apache 顶级项目。

RocketMQ支持强一致性,对消息一致性要求比较高的场景可以使用。RocketMQ 阿里出品,Java 系开源项目,源代码我们可以直接阅读,然后可以定制自己公司的 MQ。并且 RocketMQ 有阿里巴巴的实际业务场景的实战考验。

RocketMQ 已经成为业内共识的金融级可靠业务消息首选方案,对消息可靠性要求很高,甚至要求支持事务的场景,比如金融互联网,可以选择 RocketMQ。

分布式事务解决方案:可靠消息最终一致性有两种实现:一种是基于本地消息表,另一种就是基于RocketMQ事务消息。

​​​​​​​Kafka(高吞吐量、大数据、日志采集)

Kafka 是 LinkedIn 开源的一个分布式流式处理平台,已经成为 Apache 顶级项目,早期被用来用于处理海量的日志,后面才慢慢发展成了一款功能全面的高性能消息队列。

仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展。同时 Kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量。Kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略这个特性天然适合大数据实时计算以及日志收集。如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

​​​​​​​Pulsar强一致性、高吞吐、低延时

Pulsar 是下一代云原生分布式消息流平台,最初由 Yahoo 开发,已经成为 Apache 顶级项目。

Pulsar 集消息、存储、轻量化函数式计算为一体,采用计算与存储分离架构设计,支持多租户、持久化存储、多机房跨区域数据复制,具有强一致性、高吞吐、低延时及高可扩展性等流数据存储特性,被看作是云原生时代实时消息流传输、存储和计算最佳解决方案

pulsar 支持强一致性,对消息一致性要求比较高的场景可以使用。

​​​​​​​五、顺序消费

两种情况

1、生产者按序生产消息,队列里的消息也是有序的,多个消费者去消费。

2、多个生产者,队列里的消息无序。

​​​​​​​六、可靠性、消息丢失

消息丢失有三种情况

1、生产者发送消息到队列过程中消息丢失

2、MQ自身丢失消息

3、MQ到消费过程中丢失消息

解决:

1、生产者到 MQ:生产者确认机制以及重试。

在生产者发送消息时,需要给每个消息设置全局唯一的 id,用以区分不同的消息,避免 ack 冲突。

RabbitMQ 提供了 publisher confirm 机制来避免消息在发送到 MQ 过程中丢失。即消息在成功发送到 MQ 后,会返回结果ack给生产者,如果消息发送失败就进行重试。

2、MQ 自身:持久化、集群。

3、MQ 到消费者:消费者消息确认、死信队列

消费阶段采用和生产阶段类似的确认机制来保证消息的可靠传递

RabbitMQ 支持消费者确认机制,即消费者在处理完信息后向MQ发送回执,MQ收到回执之后才会正式删除该消息。

RabbitMQ还可以用死信队列存储无法被正常消费的消息。通过使用死信队列,可以避免消息丢失,并将无法处理的消息进行集中处理,方便后续的分析和处理。

​​​​​​​七、消息堆积

当生产者发送消息的速度大于消费者处理消息的速度,就会导致队列中的消息堆积,直至达到上限;这时最早接收到的消息很有可能就会成为“死信”。

首先检查是不是程序问题,在排除了程序问题后,

解决消息堆积的 3 种思路:

1、增加消费者数量,比如消费者集群部署

2、在消费者内部开启线程池加快消息的处理速度

3、扩大队列容积,提高堆积上限,比如RibbitMQ可以使用惰性队列

​​​​​​​八、重复消费

将消费的业务逻辑设计成具备幂等性的操作,在编程中一个幂等 操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

可以通过在消费端使用唯一标识来判断消息是否已经被消费过,例如判断数据状态是否满足条件或者使用redis的SETNX方式来保证幂等性。

​​​​​​​九、高可用

高可用的关键就是集群。

十、RabbitMQ

10.1、惰性队列

从 RabbitMQ 3.6.0 开始,新增 Lazy Queues (惰性队列,支持数以百万计的消息存储,因为是在磁盘中而不是内存)概念。

Queue 接收到消息后直接将其存储至磁盘,而非内存。

当 消费者 要 消费 消息时, Queue 才会将消息加载到内存。

设置惰性队列的 2 种方式:

未声明的队列:在声明队列时,指定 x-queue-mode属性为lazy

已声明的队列:修改队列属性值 queue-mode为lazy

​​​​​​​10.2、死信队列

当消息在一个队列中变成死信 (dead message) 之后,它能被重新被发送到另一个交换器中,这个交换机就是死信交换机,绑定死信交换机的队列就称之为死信队列。

死信:当一个队列中的消息满足以下情况之一时,便可以称为死信

1、消费者使用 basic.reject 或 basic.nack 声明消费失败,消息的 requeue 参数设置为 false

2、消息TTL过期

3、队列满了,消息堆积过多,最早的消息被抛弃

死信交换机:如果一个队列配置了 dead-letter-exchange 属性,并且指定了一个交换机,那么队列中的所有死信就会投递到这个交换机中,而这个交换机也被成为“死信交换机”,这个队列就称为死信队列。

​​​​​​​10.3、TTL

Time To Live,存活时间(默认未设置)

如果一个队列中的消息在 TTL 结束时仍未被消费,则会变为“死信”,具体可以分为 2 种情况:

1、消息所在的队列设置了存活时间

2、消息本身设置了存活时间

注意:如果两者同时设置了,则以【时间短的】为准!

TTL应用:

1、设置消息的超时时间

2、延迟消费者对消息的接收

10.4、​​​​​​​延迟队列,实现消息延迟投递

延迟队列指的是存储对应的延迟消息,消息被发送以后,并不想让消费者立刻拿到消息,而是等待特定时间后,消费者才能拿到这个消息进行消费。

RabbitMQ 本身是没有延迟队列的,要实现延迟消息,一般有两种方式:

1、通过 RabbitMQ 本身队列的特性来实现,需要使用 RabbitMQ 的死信交换机(Exchange)和消息的存活时间 TTL(Time To Live)。

2、在 RabbitMQ 3.5.7 及以上的版本提供了一个插件(rabbitmq-delayed-message-exchange)来实现延迟队列功能。同时,插件依赖 Erlang/OPT 18.0 及以上。

也就是说,AMQP 协议以及 RabbitMQ 本身没有直接支持延迟队列的功能,但是可以通过 TTL 和 DLX 模拟出延迟队列的功能。

10.5、工作模式

10.5.1、simple简单模式

1、消息产生者将消息放入队列

2、消息的消费者(一个队列只有一个消费者)监听消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患:消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失)

3、应用场景:聊天(中间有一个过度的服务器;p端,c端)。

​​​​​​​10.5.2、work 工作队列模式

1、多个消费者绑定到一个队列,采用轮询的方式将消息平均发送给消费者,同一条消息只会被一个消费者处理。

2、通过设置prefetch来控制消费者预取的消息数量。

3、应用场景:大项目中的资源调度(任务分配系统不需知道哪一个任务执行系统在空闲,直接将任务扔到消息队列中,空闲的系统自动争抢)。

10.5.3、pub/sub 发布订阅模式

10.5.3.1、Fanout(广播)

1、多个队列绑定同一个Fanout类型的Exchange(交换机)。

2、生产者将消息发给交换机,由交换机将消息转发给每个绑定到该交换机上的队列,订阅队列的消费者都能拿到消息。

3、应用场景:邮件群发,群聊天,广播(广告)

​​​​​​​10.5.3.2、Direct(路由)

1、消息的发送方在 向 Exchange发送消息时,必须指定消息的 路由键RoutingKey。

2、Queue和Exchange之间要设置绑定binding,一个绑定可以设置一个或多个绑定键BindingKey。

3、Exchange将消息路由到那些BindingKey与消息RoutingKey完全匹配的 Queue 中。

10.5.3.3、Topic(主题)

Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型的交换机,在与队列的绑定时,BindingKey可以使用通配符!

Routingkey一般都是由一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert.com。BindingKey 和 RoutingKey 一样也是点号”.”分隔的字符串;

BindingKey 中可以存在两种特殊字符串”*”和”#”,用于做模糊匹配,其中”*”用于匹配一个单词,”#”用于匹配一个或多个词。

通配符规则:

        #:匹配一个或多个词,比如item.#:能够匹配item.spu.insert和item.spu

        *:匹配1个词,比如item.*:只能匹配item.spu

应用场景:可以进行个性化设置,比如让一些人接收短信通知,让另外一些人接收邮件通知,让剩余的人既可以接收短信通知也可以接收邮件通知。

​​​​​​​10.6、集群

RabbitMQ 有两种集群模式:普通集群模式、镜像集群模式

RabbitMQ是基于主从(非分布式)做高可用性的。

​​​​​​​普通集群模式

不同的节点之间只会相互同步元数据,而不会同步消息内容。

就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,包含队列的名称、交换机名称及属性、交换机与队列的绑定关系等,通过元数据,可以找到 queue 所在实例)。

你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。

很显然,这种集群方式并不能保证 Queue 的高可用,因为一旦 Queue 所在的节点挂了,那么这个 Queue的消息就没办法访问了。

​​​​​​​镜像集群模式

消息内容会在所有的镜像节点间同步,可用性更高。不过也有一定的副作用,系统性能会降低,节点过多的情况下同步数据的代价会比较大。

这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

这样的好处在于,你任何一个机器宕机了,没事儿,其它机器(节点)还包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据。坏处在于,这个性能开销太大,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值