RocketMQ学习

RocketMQ

如何保证消息不丢失

RocketMQ的消息想要确保不丢失,需要生产者、消费者以及Broker的共同努力,缺一不可。

  1. 首先在生产者端,消息的发送分为同步和异步两种,在同步发送消息的情况下,消息的发送会同步阻塞等待Broker返回结果,在Broker确认收到消息之后,生产者才会拿到SendResult。如果这个过程中发生异常,那么就说明消息发送可能失败了,就需要生产者进行重新发送消息。

但是Broker其实并不会立即把消息存储到磁盘上,而是先存储到内存中,内存存储成功之后,就返回给确认结果给生产者了。然后再通过异步刷盘的方式将内存中的数据存储到磁盘上。但是这个过程中,如果机器挂了,那么就可能会导致数据丢失。

如果想要保证消息不丢失,可以将 消息保存机制修改为同步刷盘 ,这样,Broker会在同步请求中把数据保存在磁盘上,确保保存成功后再返回确认结果给生产者。

除了同步发送消息,还有异步发送,异步发送的话就需要生产者重写SendCallback的onSuccess和onException方法,用于给Broker进行回调。在方法中实现消息的确认或者重新发送。

  1. 为了保证消息不丢失,RocketMQ肯定要通过集群方式进行部署,Broker 通常采用一主多从部署方式,并且采用主从同步的方式做数据复制。

当主Broker宕机时,从Broker会接管主Broker的工作,保证消息不丢失。同时,RocketMQ的Broker还可以配置多个实例,消息会在多个Broker之间进行冗余备份,从而保证数据的可靠性。

默认方式下,Broker在接收消息后,写入 master 成功,就可以返回确认响应给生产者了,接着消息将会异步复制到 slave 节点。但是如果这个过程中,Master的磁盘损坏了。那就会导致数据丢失了。

如果 想要解决这个问题,可以配置同步复制的方式,即Master在将数据同步到Slave节点后,再返回给生产者确认结果。

## 默认为 ASYNC_MASTER
brokerRole=SYNC_MASTER
  1. 在消费者端,需要确保在消息拉取并消费成功之后再给Broker返回ACK,就可以保证消息不丢失了,如果这个过程中Broker一直没收到ACK,那么就可以重试。

所以,在消费者的代码中,一定要在业务逻辑的最后一步 return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;** **当然,也可以先把数据保存在数据库中,就返回,然后自己再慢慢处理。 RocketMQ如何保证消息的顺序性?

RocketMQ中也提供了基于队列(分区)的顺序消费。即同一个队列内的消息可以做到有序,但是不同队列内的消息是无序的

当我们作为MQ的生产者需要发送顺序消息时,需要在send方法中,传入一个MessageQueueSelector

MessageQueueSelector中需要实现一个select方法,这个方法就是用来定义要把消息发送到哪个MessageQueue的,通常可以使用取模法进行路由

通过以上形式就可以将需要有序的消息发送到同一个队列中 。需要注意的时候,这里需要使用同步发送的方式!


消息按照顺序发送的消息队列中之后,那么,消费者如何按照发送顺序进行消费呢?

RocketMQ的MessageListener回调函数提供了两种消费模式,有序消费模式MessageListenerOrderly和并发消费模式MessageListenerConcurrently。所以,想要实现顺序消费,需要使用MessageListenerOrderly模式接收消息:

当我们用以上方式注册一个消费之后,为了保证同一个队列中的有序消息可以被顺序消费,就要保证RocketMQ的Broker只会把消息发送到同一个消费者上,这时候就需要加锁了。

在实现中,ConsumeMessageOrderlyService 初始化的时候,会启动一个定时任务, 会尝试向 Broker 为当前消费者客户端申请分布式锁 。如果获取成功,那么后续消息将会只发给这个Consumer

接下来在消息拉取的过程中,消费者会一次性拉取多条消息的,并且会将拉取到的消息放入 ProcessQueue,同时将消息提交到消费线程池进行执行。

那么拉取之后的消费过程,怎么保证顺序消费呢?这里就需要更多的锁了。

RocketMQ在消费的过程中,需要 申请 MessageQueue 锁 ,确保在同一时间,一个队列中只有一个线程能处理队列中的消息。

获取到 MessageQueue 的锁后,就可以从ProcessQueue中依次拉取一批消息处理了,但是这个过程中,为了保证消息不会出现重复消费,还需要 对ProcessQueue进行加锁 (这个在扩展知识中展开)

然后就可以开始处理业务逻辑了。

总结下来就是三次加锁,先锁定Broker上的MessageQueue,确保消息只会投递到唯一的消费者,对本地的MessageQueue加锁,确保只有一个线程能处理这个消息队列。对存储消息的ProcessQueue加锁,确保在重平衡的过程中不会出现消息的重复消费。

我们知道了RocketMQ的顺序消费是通过在消费者上多次加锁实现的,这种方式带来的问题就是会降低吞吐量,并且如果前面的消息阻塞,会导致更多消息阻塞。所以,顺序消息需要慎用。

RocketMQ如何实现延时消息? 临时存储+定时任务

RocketMQ是支持延迟消息 的,延迟消息写入到Broker后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理的消息,称为延时消息

Broker 收到延时消息了,会先发送到主题(SCHEDULE_TOPIC_XXXX)的相应时间段的 Message Queue 中,然后通过一个定时任务轮询这些队列,到期后,把消息投递到目标 Topic 的队列中,然后消费者就可以正常消费这些消息。

当消息发送到Broker后,Broker会将消息根据延迟级别进行存储。

RocketMQ的延迟消息实现方式是:将消息先存储在内存中,然后使用Timer定时器进行消息的延迟,到达指定的时间后再存储到磁盘中,最后投递给消费者。

但是,RocketMQ的延迟消息并不是支持任意时长的延迟的,它只支持(5.0之前):1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h这几个时长。

另外,RocketMQ 5.0中新增了基于时间轮实现的定时消息。

前面提到的延迟消息,并使用Timer定时器来实现延迟投递。但是,由于Timer定时器有一定的缺陷,比如在定时器中有大量任务时,会导致定时器的性能下降,从而影响消息投递。

因此,在RocketMQ 5.0中,采用了一种新的实现方式:基于时间轮的定时消息。时间轮是一种高效的定时器算法,能够处理大量的定时任务,并且能够在O(1)时间内找到下一个即将要执行的任务,因此能够提高消息的投递性能。

并且,基于时间轮的定时消息能够支持更高的消息精度 ,可以实现秒级、毫秒级甚至更小时间粒度的定时消息。

具体实现方式如下:

  1. RocketMQ在Broker端使用一个时间轮来管理定时消息,将消息按照过期时间放置在不同的槽位中,这样可以大幅减少定时器任务的数量。

  2. 时间轮的每个槽位对应一个时间间隔,比如1秒、5秒、10秒等,每次时间轮的滴答,槽位向前移动一个时间间隔。

  3. 当Broker接收到定时消息时,根据消息的过期时间计算出需要投递的槽位,并将消息放置到对应的槽位中。

  4. 当时间轮的滴答到达消息的过期时间时,时间轮会将该槽位中的所有消息投递给消费者。

RocketMQ怎么实现消息分发的?

RocketMQ 支持两种消息模式:广播消费( Broadcasting )和集群消费( Clustering )。

广播消费:当使用广播消费模式时,RocketMQ 会将每条消息推送给集群内所有的消费者,保证消息至少被每个消费者消费一次。

广播模式下,RocketMQ 保证消息至少被客户端消费一次,但是并不会重投消费失败的消息,因此业务方需要关注消费失败的情况。并且,客户端每一次重启都会从最新消息消费。客户端在被停止期间发送至服务端的消息将会被自动跳过。

集群消费:当使用集群消费模式时,RocketMQ 认为任意一条消息只需要被集群内的任意一个消费者处理即可

集群模式下,每一条消息都只会被分发到一台机器上处理。但是不保证每一次失败重投的消息路由到同一台机器上。一般来说,用集群消费的更多一些。

默认情况下就是集群消费,这种模式下一个消费者组共同消费一个主题的多个队列,一个队列只会被一个消费者消费,如果某个消费者挂掉,分组内其它消费者会接替挂掉的消费者继续消费。

而广播消费消息会发给消费者组中的每一个消费者进行消费。

RocketMQ有几种集群方式?

3种,分别是单Master模式、多Master模式以及多Master多Slave模式。

单Master集群,这是一种最简单的集群方式,只包含一个Master节点和若干个Slave节点。所有的写入操作都由Master节点负责处理,Slave节点主要用于提供读取服务。当Master节点宕机时,集群将无法继续工作。

**多Master集群:这种集群方式包含多个Master节点,不部署Slave节点。**这种方式的优点是配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;缺点是单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。

多Master多Slave集群:这种集群方式包含多个Master节点和多个Slave节点。每个Master节点都可以处理写入操作,并且有自己的一组Slave节点。当其中一个Master节点宕机时,消费者仍然可以从Slave消费。优点是数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;缺点是性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。

RocketMQ消息堆积了怎么解决?

RocketMQ的消息堆积,一般都是因为客户端本地消费过程中,由于消费耗时过长或消费并发度较小等原因,导致客户端消费能力不足,出现消息堆积的问题。

当线上出现消息堆积的问题时,一般有以下几种方式来解决:

增加消费者数量:消息堆积了,消费不过来了,那就把消费者的数量增加一下,让更多人的实例来消费这些消息。

提升消费者消费速度 :消费者消费的慢可能是消息堆积的主要原因,想办法提升消费速度,比如引入线程池、本地消息存储后即返回成功后续再慢慢消费等

降低生产者的生产速度:如果生产者可控的话,可以让生产者生成消息的速度慢一点。

清理过期消息:有一些过期消息、或者一直无法成功的消息,在业务做评估之后,如果无影响或者影响不大,其实是可以清理的。

调整 RocketMQ 的配置参数:RocketMQ 提供了很多可配置的参数,例如消息消费模式、消息拉取间隔时间等,可以根据实际情况来调整这些参数,从而优化消息消费的效率。

增加 Topic 队列数:如果一个 Topic 的队列数比较少,那么就容易出现消息堆积的情况。可以通过增加队列数来提高消息的处理并发度,从而减少消息堆积。

  • 消费者扩容:如果当前 Topic 的 Message Queue 的数量大于消费者数量,就可以对消费者进行扩容,增加消费者,来提高消费能力,尽快把积压的消息消费玩。
  • 消息迁移 Queue 扩容:如果当前 Topic 的 Message Queue 的数量小于或者等于消费者数量,这种情况,再扩容消费者就没什么用,就得考虑扩容 Message Queue。可以新建一个临时的 Topic,临时的 Topic 多设置一些 Message Queue,然后先用一些消费者把消费的数据丢到临时的 Topic,因为不用业务处理,只是转发一下消息,还是很快的。接下来用扩容的消费者去消费新的 Topic 里的数据,消费完了之后,恢复原状。

RocketMQ的事务消息是如何实现的?

RocketMQ的事务消息是通过TransactionListener接口来实现的。

在发送事务消息时,首先向RocketMQ Broker发送一条"half消息"(即半消息),半消息将被存储在Broker端的事务消息日志中,但是这个消息还不能被消费者消费。

接下来,在半消息发送成功后,应用程序通过执行本地事务来确定是否要提交该事务消息

如果本地事务执行成功,就会通知RocketMQ Broker提交该事务消息,使得该消息可以被消费者消费;否则,就会通知RocketMQ Broker回滚该事务消息,该消息将被删除,从而保证消息不会被消费者消费。

拆解下来的话,主要有以下4个步骤:

发送半消息:应用程序向RocketMQ Broker发送一条半消息,该消息在Broker端的事务消息日志中被标记为"prepared"状态。

执行本地事务:RocketMQ会通知应用程序执行本地事务。如果本地事务执行成功,应用程序通知RocketMQ Broker提交该事务消息。

提交事务消息:RocketMQ收到提交消息以后,会将该消息的状态从"prepared"改为"committed",并使该消息可以被消费者消费。

回滚事务消息:如果本地事务执行失败,应用程序通知RocketMQ Broker回滚该事务消息,RocketMQ将该消息的状态从"prepared"改为"rollback",并将该消息从 事务消息日志 中删除,从而保证该消息不会被消费者消费。

扩展知识

如果一直没收到COMMIT或者ROLLBACK怎么办?

在RocketMQ的事务消息中,如果半消息发送成功后,RocketMQ Broker在规定时间内没有收到COMMIT或者ROLLBACK消息。

RocketMQ会向应用程序发送一条检查请求,应用程序可以通过回调方法返回是否要提交或回滚该事务消息。如果应用程序在规定时间内未能返回响应,RocketMQ会将该消息标记为"UNKNOW"状态。

在标记为"UNKNOW"状态的事务消息中,如果应用程序有了明确的结果,还可以向MQ发送COMMIT或者ROLLBACK。

但是MQ不会一直等下去,如果过期时间已到,RocketMQ会自动回滚该事务消息,将其从事务消息日志中删除。

第一次发送半消息失败了怎么办?

在事务消息的一致性方案中,我们是先发半消息,再做业务操作的

所以,如果半消息发失败了,那么业务操作也不会进行,不会有不一致的问题。

遇到这种情况重试就行了。(可以自己重试,也可以依赖上游重试)

为什么要用事务消息?

很多人看完事务消息会有一个疑惑:本地事务执行完成之后再发送消息有什么区别?为什么要有事务消息呢?

主要是因为:本地事务执行完成之后再发送消息可能会发消息失败。

一旦发送消息失败了,那么本地事务提交了,但是消息没成功,那么监听者就收不到消息,那么就产生数据不一致了。

那如果用事务消息。先提交一个半消息,然后执行本地事务,再发送一个commit的半消息。如果后面这个commit半消息失败了,MQ是可以基于第一个半消息不断反查来推进状态的。这样只要本地事务提交成功,最终MQ也会成功。如果本地事务rolllback,那么MQ的消息也会rollback。保证了一致性。

RocketMQ的架构是怎么样的?

RocketMQ主要由Producer、Broker和Consumer三部分组成,如下图所示:

Producer:消息生产者,负责将消息发送到Broker。

Broker:消息中转服务器 ,负责存储和转发消息。RocketMQ支持多个Broker构成集群,每个Broker都拥有独立的存储空间和消息队列。

Consumer:消息消费者,负责从Broker消费消息。

NameServer:名称服务,负责维护Broker的元数据信息,包括Broker地址、Topic和Queue等信息。Producer和Consumer在启动时需要连接到NameServer获取Broker的地址信息。

Topic:消息主题,是消息的逻辑分类单位。Producer将消息发送到特定的Topic中,Consumer从指定的Topic中消费消息。

Message Queue:消息队列,是Topic的物理实现。一个Topic可以有多个Queue,每个Queue都是独立的存储单元。Producer发送的消息会被存储到对应的Queue中,Consumer从指定的Queue中消费消息。

RocketMQ的消息是推还是拉?

MQ的消费模式可以大致分为两种,一种是推Push,一种是拉Pull
Push是服务端主动推送消息给客户端,Pull是客户端需要主动到服务端轮询获取数据

他们各自有各自的优缺点,推优点是及时性较好,但如果客户端没有做好流控,一旦服务端推送大量消息到客户端时,就会导致客户端消息堆积甚至崩溃。

拉优点是客户端可以依据自己的消费能力进行消费,但是频繁拉取会给服务端造成压力,并且可能会导致消息消费不及时。

建议使用 push

**RocketMQ的push模式其实底层的实现还是基于pull实现的,**只不过他把pull给封装的比较好,让你以为是在push。 长轮询模式实现

Push 模式 相对来说更简单,因为它的实现更加直接,开发者只需要编写一个回调函数来处理消息,RocketMQ 会自动将消息推送给消费者。这种模式省去了手动控制消息拉取的逻辑,所以适合想快速上手并实现消息消费的场景。

为什么 Push 模式更简单?

  1. 无需手动拉取消息 : 在 Push 模式下,消息会自动推送到消费者,开发者只需专注于如何处理消息,而不用管理消息何时拉取。
  2. 减少代码量 : 实现 Push 模式的代码更少、更简洁,因为不需要自己编写轮询和拉取消息的逻辑。
  3. 快速上手 : 对于初学者或者对消息队列不熟悉的开发者,Push 模式更易于理解和实现,可以更快地集成到项目中。

介绍一下RocketMQ的工作流程?

RocketMQ中有这样几个角色:NameServer、Broker、Producer和Consumer

NameServer:NameServer是RocketMQ的路由和寻址中心,它维护了Broker和Topic的路由信息,提供了Producer和Consumer与正确的Broker建立连接的能力。NameServer还负责监控Broker的状态,并提供自动发现和故障恢复的功能。

Broker:Broker是RocketMQ的核心组件,负责存储、传输和路由消息。它接收Producer发送的消息,并将其存储在内部存储中。并且还负责处理Consumer的订阅请求,将消息推送给订阅了相应Topic的Consumer。

Producer(消息生产者):Producer是消息的生产者,用于将消息发送到RocketMQ系统。

Consumer(消息消费者):Consumer是消息的消费者,用于从RocketMQ系统中订阅和消费消息。

RocketMQ的工作过程大致如下:

1、启动NameServer,他会等待Broker、Producer以及Consumer的链接。

2、启动Broker,会和NameServer建立连接,定时发送心跳包。心跳包中包含当前Broker信息(ip、port等)、Topic信息以及Borker与Topic的映射关系。

3、启动Producer,启动时先随机和NameServer集群中的一台建立长连接,并从NameServer中获取当前发送的Topic所在的所有Broker的地址;然后从队列列表中轮询选择一个队列,与队列所在的Broker建立长连接,进行消息的发送。

4、Broker接收Producer发送的消息,当配置为同步复制时,master需要先将消息复制到slave节点,然后再返回"写成功状态"响应给生产者;当配置为同步刷盘时,则还需要将消息写入磁盘中,再返回"写成功状态";要是配置的是异步刷盘和异步复制,则消息只要发送到master节点,就直接返回"写成功"状态。

5、启动Consumer,过程和Producer类似,先随机和一台NameServer建立连接,获取订阅信息,然后在和需要订阅的Broker建立连接,获取消息。

用了RocketMQ一定能实现削峰的效果吗?

典型回答

我们都知道,MQ有三个好处,异步、解耦,削峰填谷。
在高并发场景下,系统可能会面临短时间内大量请求涌入,导致系统负载急剧上升,甚至超过系统的处理能力,造成服务瘫痪。使用MQ来缓冲这些请求,可以将大量并发请求暂存到队列中,然后按照系统能够处理的速度逐渐消费这些请求。这样可以避免系统因为瞬间的高并发而崩溃,实现系统流量的平滑处理。这就是削峰填谷。
但是,并不是说用了MQ了就一定实现了削峰填谷了。这要看MQ的消费方式。
MQ的消息有推和拉两种模式,如果是那种推的模式,会在上游发送消息之后,就立即推给消费者,那这种情况对于接收消息的人来说,还是承担了很大的请求量。而如果消费者的消费能力不行,那么消息也会堆积到消费者端。
并且一旦消息量太大,也会导致消费失败,那么消息就会重投,这就会导致更多的请求量过来。

所以,如果想要起到很好地削峰填谷的作用,需要使用拉的模式来获取消息,这样自己就可以控制速度了。消息就可以在MQ的队列中堆积,而不是在客户端堆积,通过队列的缓冲来起到削峰填谷的作用!

相关推荐
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
QQ同步助手2 小时前
如何正确使用人工智能:开启智慧学习与创新之旅
人工智能·学习·百度
流浪的小新2 小时前
【AI】人工智能、LLM学习资源汇总
人工智能·学习
A懿轩A3 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
南宫生11 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__11 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界0113 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐13 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
OopspoO15 小时前
qcow2镜像大小压缩
学习·性能优化
A懿轩A16 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列