概述
AMQP协议?
AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Java、Ruby、PHP、.NET、JavaScript、Go、Elixir、Objective-C、Swift等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。
AMQP三层协议:
- Module Layer:协议最高层,主要定义了一些客户端调用的命令,客户端可以用这些命令实现自己的业务逻辑。
- Session Layer:中间层,主要负责将客户端命令发送给服务器,再将服务端应答返回给客户端,提供可靠性同步机制和错误处理。
- TransportLayer:最底层,主要传输二进制数据流,提供帧的处理、信道复用、错误检测和数据表示等。
AMQP组件:
- 交换器(Exchange):消息代理服务器中用于把消息路由到队列的组件。
- 队列(queue):用来存储消息的数据结构,位于硬盘或内存中。
- 绑定(Binding):一套规则,告知交换器消息应该将消息投递给哪个队列。
RabbitMQ包含哪些要素?
- 生产者:消息队列创建者,发送消息到MQ
- 消费者:连接到RabbitMQ,订阅到队列上,消费消息,持续订阅和单条订阅
- 消息:包含有效载荷和标签,有效载荷指要传输的数据,标签描述了有效载荷,并且RabbitMQ用它来决定谁获得消息,消费者只能拿到有效载荷,并不知道生产者是谁
RabbitMQ各个部分的概念?
信道
是生产者、消费者与RabbitMQ通信的渠道,生产者publish或是消费者subscribe 一个队列都是通过信道来通信的。
信道是建立在TCP连接上的虚拟连接,就是说RabbitMQ在一条TCP上建立成百上千个信道来达到多个线程处理,这个TCP被多个线程共享,每个线程对应一个信道,信道在RabbitMQ都有一个唯一的ID,保证了信道私有性,对应上唯一的线程使用。
Broker
broker是指一个或多个erlang node的逻辑分组,且node上运行着RabbitMQ应用程序。
Cluster
cluster是在broker的基础之上,増加了node之间共享元数据的约束。
Exchange
生产者将消息发送到交换器,由交换器将消息路由到一个或者多个队中。当路由不到时,或返回给生产者或直接丟弃。
RoutingKey
生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
Binding
通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,这样RabbitMq就知道如何正确路由消息到队列了。
死信队列
当消息被RabbitMQ server投递到consumer后,但consumer却通过 Basic.Reject进行了拒绝时(同时设置requeue=false),那么该消息会被放入死信队列中。该 queue 可用于排查 message 被 reject 或 undeliver 的原因。
vhost
vhost可以理解为虚拟broker,即mini-RabbitMQ server。其内部均含有独立的queue、exchange和binding等,但最最、重要的是,其拥有独立的权限系统,可以做到vhost范围的用户控制。当然,从RabbitMQ的全局角度,vhost可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的vhost中)。
RabbitMQ的routing key和binding key的最大长度是多少?
255字节。
RabbitMQ中消息可能有的几种状态?
- alpha:消息内容(包括消息体、属性和headers)和消息索引都存储在内存
- beta:消息内容保存在磁盘中,消息索引保存在内存中
- gamma:消息内容保存在磁盘中,消息索引在磁盘和内存中都有
- delta:消息内容和索引都在磁盘中
RabbitMQ的消息传输保证层级?
- At most once:最多一次。消息可能会丢失,但不会重复传递
- At least once:最少一次。消息绝不会丟失,但可能会重夏传输
- Exactly once:恰好一次。每条消息肯定仅传输一次
RabbitMQ的工作模式?
simple模式(即最简单的收发模式)
生产者产生消息,将消息放入队列
消息的消费者监听消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除(隐患消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。
work工作模式(资源的竞争)
消息产生者将消息放入队列,消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关[syncronize]保证一条消息只能被一个消费者使用)。
publish/subscribe发布订阅(共享资源)
每个消费者监听自己的队列。
生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。
routing路由模式
消息生产者将消息发送给交换机按照路由判断,路由是字符串(info)当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息。
topic主题模式(路由模式的一种)
为路由功能添加模糊匹配,消息产生者产生消息,把消息交给交换机,交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费。
发送消息的过程?
- 生产者将消息发布到一个或多个交换器(Exchange)中。交换器的作用是根据路由键(Routing Key)将消息分配给特定的队列(Queue)。
- 交换器通过路由键将消息路由到一个或多个队列。如果路由键为空,消息会被分配给所有绑定到该交换器的队列。
- 消息进入队列,等待被消费者接收。在队列中,消息会被存储在持久化存储中,以防服务器崩溃或重启时数据丢失。
- 消费者从队列中获取消息并进行处理。消费者可以通过订阅一个或多个队列来接收消息。一旦消息被消费者接收,它将从队列中移除。
为什么要使用RabbitMQ?
- 在分布式系统下具备异步,削峰,负载均衡等一系列高级功能
- 拥有持久化的机制,进程消息,队列中的信息也可以保存下来
- 实现消费者和生产者之间的解耦
- 对于高并发场景下,利用消息队列可以使得同步访问变为串行访问达到一定量的限流,利于数据库的操作
- 可以使用消息队列达到异步下单的效果,排队中,后台进行逻辑下单
RabbitMQ缺点?
- 复杂性:RabbitMQ 需要一定的学习和理解成本,特别是在配置、管理和监控方面。
- 可靠性:RabbitMQ 的可靠性依赖于其配置和管理,需要进行合理的配置和监控,否则可能会出现消息丢失或重复传递等问题。
- 性能:RabbitMQ 的性能与其配置和使用方式密切相关,如果配置不当或使用不当,可能会导致性能问题。
- 成本:RabbitMQ 需要一定的硬件和软件资源支持,特别是在高并发和大规模应用场景下,需要投入更多的资源。
- 可扩展性:RabbitMQ 的可扩展性有一定限制,如果需要支持更高的并发和吞吐量,需要进行集群部署和负载均衡等操作。
- 存储要求:RabbitMQ 依赖于磁盘进行消息存储,因此需要一定的存储空间,并需要定期清理过期消息。
Kafka、ActiveMQ、RabbitMQ、RocketMQ有什么优缺点?
一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以大家还是算了吧,我个人不推荐用这个了;
后来大家开始用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;
不过现在确实越来越多的公司会去用 RocketMQ,确实很不错,毕竟是阿里出品,但社区可能有突然黄掉的风险(目前 RocketMQ 已捐,但 GitHub 上的活跃度其实不算高)对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄;
所以中小型公司,技术实力较为一般,技术挑战不是特别高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发实力较强,用 RocketMQ 是很好的选择;
大数据领域的实时计算、日志采集等场景,使用Kafka。
应用
如何确保消息正确地发送至RabbitMQ?如何确保消息接收方消费了消息?
- 消息确认机制:生产者在发送消息后,可以通过消息确认机制(Confirm)确保消息被正确地发送至 RabbitMQ。消息确认机制分为批量确认和单个确认两种方式,生产者可以选择适合自己业务场景的确认方式。
- 消息持久化:通过将消息设置为持久化的方式,可以确保消息在发送至 RabbitMQ 后即使 RabbitMQ 崩溃或者重启,消息仍然可以得到保留。在发送消息时,可以将消息的 delivery mode 设置为 2,表示消息需要被持久化。
- 连接超时设置:在发送消息时,可以设置连接超时时间,当超过指定时间后仍未能成功发送消息时,可以通过重试等方式确保消息正确地发送至 RabbitMQ。
- 消息序列化和反序列化:在发送和接收消息时,需要将消息对象序列化为二进制数据,再在接收方反序列化为消息对象。因此,需要确保消息对象的序列化和反序列化过程正确无误,可以采用 JSON、Protobuf 等常用的序列化和反序列化工具。
如何避免消息重复投递或重复消费?
- 消息确认机制:在消费者处理消息后,通过发送消息确认(ACK)给 RabbitMQ,告知消息已被正确处理。如果消费者没有发送确认消息,RabbitMQ 会将消息重新投递到队列中,导致重复消费。
- 消息去重机制:可以在应用程序中实现一个消息去重的机制,例如使用分布式缓存、数据库等存储系统,记录已经处理过的消息标识,以便下次处理时进行判断。如果消息已经被处理过,则可以直接忽略,避免重复处理。
- 消息幂等性处理:可以将消费者的处理逻辑设计为幂等操作,即重复执行多次仍然具有相同的效果,避免因为消息重复消费导致业务数据错误。
- 设置消息过期时间:可以在消息发送时设置过期时间,在消息过期后不再投递给消费者,避免重复消费。
- 使用消息唯一标识符:可以在消息中添加唯一标识符,例如 UUID,保证每个消息都有独一无二的标识符,避免重复消费和重复投递。
如何保证消息持久化?
- 将消息的 delivery mode 设置为 2:在发送消息时,可以将消息的 delivery mode 属性设置为 2,表示消息需要被持久化。持久化的消息将会被写入磁盘,即使 RabbitMQ 重启或者崩溃,消息仍然可以得到保留。
- 将队列的 durable 属性设置为 true:在创建队列时,可以将队列的 durable 属性设置为 true,表示队列是持久化的。持久化的队列将会在 RabbitMQ 重启或者崩溃后得到保留。
- 将交换器的 durable 属性设置为 true:在创建交换器时,可以将交换器的 durable 属性设置为 true,表示交换器是持久化的。持久化的交换器将会在 RabbitMQ 重启或者崩溃后得到保留。
- 使用事务机制:在发送消息时,可以使用事务机制来确保消息的持久化。通过开启事务,发送者可以将消息发送到 RabbitMQ,然后等待 RabbitMQ 的确认,确认后再提交事务。使用事务机制可以确保消息的可靠性,但是会影响系统的性能。
消息如何路由?
消息的路由过程是通过交换器(Exchange)来实现的。当消息被发送到 RabbitMQ 时,生产者将消息发布到交换器中,然后根据交换器的类型和绑定规则将消息路由到一个或多个队列中。
下面是消息路由的基本流程:
- 生产者将消息发送到指定的交换器中。
- 交换器根据路由键(Routing Key)和绑定键(Binding Key)将消息发送到一个或多个队列中。路由键和绑定键可以是任意字符串,根据交换器的类型和绑定规则进行匹配。
- 如果交换器类型为 direct,会根据路由键进行精确匹配,将消息发送到所有匹配的队列中。
- 如果交换器类型为 fanout,会将消息发送到所有绑定到该交换器的队列中。
- 如果交换器类型为 topic,会根据通配符匹配规则将消息发送到匹配的队列中。例如,路由键为 “foo.bar” 的消息可以匹配绑定键为 “*.bar” 或 “foo.#” 的队列。
- 如果交换器类型为 headers,会根据消息的属性(headers)进行匹配,将消息发送到匹配的队列中。
- 如果没有匹配的队列,消息将被丢弃或返回给生产者,根据生产者的配置。
需要注意的是,交换器和队列都需要进行绑定,否则消息将无法路由到队列中。另外,可以根据需要在交换器和队列中配置各种属性,例如持久化、自动删除等,以满足不同的业务需求。
RabbitMQ的消息确认过程?
RabbitMQ 的消息确认机制是指消费者在消费一条消息后,向 RabbitMQ 发送确认消息(ACK)的过程,以告知 RabbitMQ 消息已被正确处理。消息确认机制的作用是确保 RabbitMQ 可以正确地将消息从队列中删除,避免重复投递和重复消费。
消息确认机制的过程如下:
- 消费者从 RabbitMQ 中获取消息,处理消息。
- 处理完成后,向 RabbitMQ 发送确认消息(ACK)。确认消息通常是一个简单的 AMQP 基本确认帧,带有消息的标识符(delivery tag)和是否批量确认的标记。
- RabbitMQ 收到确认消息后,将该消息从队列中删除。
- 如果消费者在一定时间内没有发送确认消息,RabbitMQ 将认为消息未被正确处理,将会重新将消息投递到队列中,等待下一次消费。
需要注意的是,在某些情况下,消费者可能无法正确处理消息,例如消费者崩溃或出现异常等。为避免这种情况导致消息丢失,RabbitMQ 还提供了 Nack(Negative Acknowledge)和 Reject 机制,可以将消息标记为无法处理或无法路由的状态,使其重新回到队列中等待下一次投递。此外,可以通过设置重试次数和重试时间间隔等参数,进行消息重试和延迟投递的配置,以满足不同的业务需求。
消息基于什么传输?
由于 TCP 连接的创建和销毁开销较大,且并发数受系统资源限制,会造成性能瓶颈。RabbitMQ 使用信道的方式来传输数据。信道是建立在真实的 TCP 连接内的虚拟连接,且每条 TCP 连接上的信道数量没有限制。
为什么不应该对所有的 message 都使用持久化机制?
使用持久化机制会增加磁盘的负担,特别是在高并发场景下,持久化的成本会更高。如果所有的消息都使用持久化机制,会导致 RabbitMQ 的性能下降,从而影响整个系统的性能。因此,应该根据业务需求和消息的重要性,选择是否使用持久化机制。对于一些重要的消息,应该使用持久化机制,以确保消息的可靠性。而对于一些临时的消息,可以不使用持久化机制,减轻服务器的负担,提高系统的性能。
若 message 设置了 persistent 属性,但 queue 未设置 durable 属性,那么当该 queue 的 owner node 出现异常后,在未重建该 queue 前,发往该 queue 的 message 将被 blackholed ;若 message 设置了 persistent 属性,同时 queue 也设置了 durable 属性,那么当 queue 的 owner node 异常且无法重启的情况下,则该 queue 无法在其他 node 上重建,只能等待其 owner node 重启后,才能恢复该 queue 的使用,而在这段时间内发送给该 queue 的 message 将被 blackholed 。
所以,是否要对 message 进行持久化,需要综合考虑性能需要,以及可能遇到的问题。若想达到 100000 条/秒以上的消息吞吐量(单 RabbitMQ 服务器),则要么使用其他的方式来确保 message 的可靠 delivery ,要么使用非常快速的存储系统以支持全持久化(例如使用SSD)。另外一种处理原则是:仅对关键消息作持久化处理(根据业务重要程度),且应该保证关键消息的量不会导致性能瓶颈。
如何保证高可用的?RabbitMQ 的集群?
普通集群模式:普通集群模式是 RabbitMQ 最常见的集群模式,也是最简单的一种模式。在普通集群模式下,多台 RabbitMQ 服务器通过网络连接组成一个集群,共同管理消息队列,并通过节点之间的通信进行消息的传递和路由。普通集群模式适用于大多数应用场景,提供了高可用性和可靠性。
镜像集群模式:镜像集群模式是一种高可用性的集群模式,可以提高 RabbitMQ 集群的可靠性和容错能力。在镜像集群模式下,每个节点都有多个镜像节点,镜像节点会自动复制主节点的消息队列,并在主节点出现故障时接管消息队列的处理。镜像集群模式适用于对消息可靠性要求较高的场景,但是会增加网络带宽和存储成本。
Federated集群模式:Federated 集群模式是 RabbitMQ 的一种特殊的集群模式,可以将多个 RabbitMQ 集群组成一个逻辑上的整体,并通过 Federation 插件实现集群之间的消息传递和路由。Federated 集群模式适用于需要跨多个数据中心或地理位置分布的场景,但是会增加网络延迟和复杂度。
RabbitMQ上的一个queue中存放的message是否有数量限制?
队列的大小限制:队列可以通过配置参数限制队列的大小,当队列的大小达到限制时,新的消息将无法入队。队列大小的限制可以通过 RabbitMQ 的管理界面或者 AMQP 协议进行设置。
内存限制:RabbitMQ 中的消息队列是保存在内存中的,当队列中的消息数量过多时,会占用大量的内存空间,可能会导致系统性能下降或者崩溃。因此,系统的内存大小也是队列中存放消息数量的限制因素之一。
磁盘限制:当 RabbitMQ 的磁盘空间不足时,也会限制队列中存放消息的数量。
在单node系统和多node构成的cluster系统中声明queue, exchange ,以及进行binding会有什么不同?
当你在单node上声明queue时,只要该node上相关元数据进行了变更,你就会得到Queue.Declare-ok回应;而在cluster上声明queue,则要求 cluster上的全部node都要进行元数据成功更新,才会得到Queue.Declare-ok 回应。另外,若node类型为RAM node则变更的数据仅保存在内存中,若类型为 disk node则还要变更保存在磁盘上的数据。
客户端连接到duster中的任意node上是否都能正常工作?
是的,客户端感觉不到不同。
若cluster中拥有某个queue的owner node失效了,且该queue 被声明具有durable属性,是否能够成功从其他node上重新声明该queue ?
不能,在这种情况下,将得到404 NOT FOUND错误。只能等queue所属的 node恢夏后才能使用该queue 。但若该queue本身不具有durable属性, 则可在其他node上重新声明。
cluster中node的失效会对consumer产生什么影响?若是在cluster中创建了 mirrored queue ,这时node失效会对consumer产生什么影响?
若是consumer所连接的那个node失效(无论该node是否为consumer所订阅queue的owner node),则会在发现TCP连接断开时, 按标准行为执行重连逻辑,并根据"Assume Nothing"原则重建相应的fabric即可。
若是失效的 node 为 consumer 订阅 queue 的 owner node,则 consumer 只能通过Consumer Cancellation Notification机制来检测与该queue订阅关系的终止,否则会出现傻等却没有任何消息来到的问题。
能够在地理上分开的不同数据中心使用RabbitMQ cluster吗?
不能。第一,你无法控制所创建的queue实际分布在cluster里的哪个node 上(一般使用 HAProxy + cluster 访问时的常见问题;第二,Erlang的OTP通信框架对延迟的容忍度有限,这可能会触发各种超时,导致业务疲于处理;第三,在广域网上的连接失效问题将导致经典的"脑裂"问题,而RabbitMQ目前无法处理(该问题主要是说Mnesia)。
为什么heavy RPC的使用场景下不建议采用disk node ?
heavy RPC是指在业务逻辑中高频调用RabbitMQ提供的RPC机制,导致不断创建、销毁reply queue ,进而造成disk node的性能问题(因为会针对元数据不断写盘)。所以在使用RPC机制时需要考虑自身的业务场景。
向不存在的exchange发publish消息会发生什么?向不存在的queue执行consume动作会发生什么?
都会收到Channel.Close信令告之不存在(内含原因404 NOT FOUND)
为什么说保证message被可靠持久化的条件是queue和 exchange具有durable属性,同时message具有 persistent属性才行?
binding关系可以表示为exchange - binding - queue。从文档中我们知道, 若要求投递的message能够不丟失,要求message本身设置persistent属性,要求exchange和queue都设置durable属性。其实这问题可以这么想,若exchange或queue未设置durable属性,则在其crash之后就会无法恢复,那么即使message设置了 persistent属性,仍然存在message虽然能恢复但却无处容身的问题;同理,若message本身未设置persistent属性,则message的持久化更无从谈起。
Consumer Cancellation Notification 机制用于什么场景?
用于保证当镜像queue中master挂掉时,连接到slave上的consumer可以收到自身consume被取消的通知,进而可以重新执行consume动作从新选出的master处获得消息。若不采用该机制,连接到slave上的consumer将不会感知master挂掉这个事情,导致后续无法再收到新master广播出来的 message。另外,因为在镜像queue模式下,存在将message进行 requeue 的可能,所以实现consumer的逻辑时需要能够正确处理出现重夏message的情况。
Basic.Reject的用法是什么?
该信令可用于consumer对收到的message进行reject。若该信令设置requeue=true,则当RabbitMQ server收到该拒绝信令后,会将该 message 重新发送到下一个处于consume状态的consumer处(理论上仍可能将该消息发送给当前consumer)。若设置 requeue=false ,则RabbitMQ server 在收到拒绝信令后,将直接将该message从queue中移除。另外一种移除queue中message的小技巧是,consumer回复Basic.Ack 但不对获取到的message做任何处理。而Basic.Nack是对Basic的扩展,以支持一次拒绝多条message的能力。
死信队列和延迟队列的使用?
死信队列:当消息无法被正确处理时,可以将该消息转发到死信队列中,以便进行进一步的处理。通过使用死信队列,可以将无法处理的消息统一存储和管理,并通过设置合适的 TTL 和 DLX 等参数,灵活控制消息的转发和重新处理。
使用死信队列的主要步骤如下:
- 创建一个普通队列和一个死信交换器;
- 将普通队列绑定到死信交换器,并指定死信队列的路由键;
- 在发送消息时,可以将消息的 TTL 设置为一个较小的值,当消息未被消费者处理时,该消息会被转发到死信队列中。
延迟队列:当需要在一定时间后才能处理某个消息时,可以使用延迟队列。通过设置消息的 TTL 和 DLX 等参数,可以将消息转发到一个指定的队列中,以便在一定的时间后再进行处理。使用延迟队列可以灵活地控制消息的发送和处理时间,适用于很多场景,如订单超时处理、提醒任务等。
使用延迟队列的主要步骤如下:
- 创建一个普通队列和一个交换器;
- 在交换器中设置消息的 TTL 和 DLX 等参数,将消息转发到指定的队列中;
- 在指定的队列中处理消息。
什么情况下producer不主动创建queue是安全的?
message是允许丟失的;实现了针对未处理消息的republish功能。
如何保证消息的顺序性?
一个队列只有一个消费者的情况下才能保证顺序,否则只能通过全局ID实现(每条消息都一个msgld,关联的消息拥有一个parentMsgld。可以在消费端实现前一条消息未消费,不处理下一条消息;也可以在生产端实现前一条消息未处理完不发下一条信息。
无法被路由的消息去了哪里?
mandatory: true 返回消息给生产者。
mandatory: false 直接丟弃。
消息什么时候会变成死信?
消息拒绝并且没有设置重新入队;消息过期;消息堆积,并且队列达到最大长度,先入队的消息会变成DL。
RabbitMQ事务机制?
RabbitMQ 支持事务机制,用于在发送消息时保证事务的原子性。事务机制允许在多个 RabbitMQ 操作中声明事务,并在最终确认消息被完全处理之前,将多个操作打包为一个原子操作。
在 RabbitMQ 中,事务机制的使用流程如下:
- 开启事务:在发送消息之前,使用 txSelect 方法开启事务;
- 发送消息:使用 basicPublish 方法发送消息;
- 提交事务:使用 txCommit 方法提交事务,如果提交成功,则消息会被 RabbitMQ 确认,否则消息会被回滚;
- 回滚事务:使用 txRollback 方法回滚事务,如果回滚成功,则之前发送的消息会被撤销,否则消息会被继续处理。
需要注意的是,使用事务机制会对 RabbitMQ 的性能产生一定的影响,因此建议在必要的情况下使用,例如在消息的可靠性要求非常高的场景下。在消息量较大的场景下,可以使用事务机制的替代方案,如消息确认机制(ACK机制)等,以保证系统的高性能和可靠性