RocketMQ

运行原理

RocketMQ是一个分布式消息中间件,它采用了发布/订阅模式和队列模型来实现消息的传递。RocketMQ的运行原理如下:

Namesrv模块:Namesrv是RocketMQ的路由注册与发现模块,负责维护Topic和Broker的路由信息。当Producer发送消息时,Namesrv会返回一个可用的Broker地址;当Consumer订阅Topic时,Namesrv会返回该Topic下所有可用的Broker地址。

Broker模块:Broker是RocketMQ的消息存储与转发模块,每个Broker都有一个或多个Message Queue用于存储消息。当Producer发送消息时,Broker会将消息存储到对应的Message Queue中;当Consumer消费消息时,Broker会从对应的Message Queue中拉取消息并返回给Consumer。

Producer模块:Producer是RocketMQ的消息生产者模块,负责将消息发送到Broker中的Message Queue中。Producer可以通过指定Topic和Message Key来发送消息,同时还可以指定消息的Tag和属性等信息。

Consumer模块:Consumer是RocketMQ的消息消费者模块,负责从Broker中的Message Queue中拉取并消费消息。Consumer可以通过指定Topic和Message Tag来订阅消息,同时还可以指定消费组和消费策略等信息。

总之,RocketMQ通过Namesrv、Broker、Producer和Consumer四个模块的协同工作,实现了高效、可靠、可扩展的消息传递服务。

RocketMQ消息如何保证消息可靠性

分布式系统中一个重要的前提假设是所有的网络传输都是不可靠的,在网络传输不可靠的情况下,保证消息的可靠传输,除了进行重试投递别无他法。常用的绝大多数消息队列RocketMQ、RabbitMQ等在消息传输上都只能保证至少传输成功一次,也即(At least once),而不能保证只传输成功一次(Exactly once)。由于分布式系统网络的不可靠,可能就会出现消息丢失的现象,那么RocketMQ是如何最大限度的保证消息不丢失的呢?那就需要从消息的产生到最终消费的整个过程来分析,消息完整链路可以划分为以下三个阶段:

  1. 生产阶段:消息在 Producer 发送端创建出来,经过网络传输发送到 Broker 存储端。
  2. 存储阶段:消息在 Broker 端存储,如果是主备或者多副本,消息会在这个阶段被复制到其他的节点或者副本上。
  3. 消费阶段:Consumer 消费端从 Broker存储端拉取消息,经过网络传输发送到 Consumer 消费端上,并通过重试来最大限度的保证消息的消费。

发送端消息可靠性

发送端Producer发送消息Broker端的核心逻辑如下图所示:
在这里插入图片描述
消息发送一般有以下几种方式:同步发送、异步发送以及单向发送,业务具体选择哪种方式进行消息发送,需要根据情况进行判断,下面具体介绍不同的发送方式实现的消息可靠性保证。

同步发送

同步发送是指发送端在发送消息时,阻塞线程进行等待,直到服务器返回发送的结果。发送端如果需要保证消息的可靠性,防止消息发送失败,可以采用同步阻塞式的发送,然后同步检查Brocker返回的状态来判断消息是否持久化成功。如果发送超时或者失败,则会默认重试2次,RocketMQ选择至少传输成功一次的消息模型,但是有可能发生重复投递,因为网络传输是不可靠的,具体的重试策略可以参照第四小节。

异步发送

异步发送是指发送端在发送消息时,传入回调接口实现类,调用该发送接口后不会阻塞,发送方法会立即返回,回调任务会在另一个线程中执行,消息发送结果会回传给相应的回调函数。具体的业务实现可以根据发送的结果信息来判断是否需要重试来保证消息的可靠性。

单向发送

单向发送是指发送端发送完成之后,调用该发送接口后立刻返回,并不返回发送的结果,业务方无法根据发送的状态来判断消息是否发送成功,单向发送相对前两种发送方式来说是一种不可靠的消息发送方式,因此要保证消息发送的可靠性,不推荐采用这种方式来发送消息。

发送重试策略

RocketMQ架构模型中会有多个Borker为某个topic提供服务,一个topic下的消息分散存储在多个Broker存储端,它们是多对多关系。Broker会将其提供存储服务的topic的元数据信息上报到NameServer,对等NameServer节点组成的高可用服务会维护topic与Broker之间的映射关系,多对多的映射关系为消息可以重试发送到多个Broker端提供了前提与基础。
当发送端需要发送消息时,如果发送端中缓存了topic的路由信息,并包含了消息队列,则直接返回该路由信息,如果没有缓存或没有消息队列,则向NameServer查询该topic的路由信息,查询到路由消息之后,采用指定的队列选择策略选择相应的queue发送消息,默认是采用轮询策略,发送成功则返回, 收到异常则根据相应的策略进行重试,可以根据发送端感知到的Broker的时延、上次发送失败的Broker信息和发送端配置的是否重试不同Broker的参数以及发送端设置的最大超时时间等等策略来灵活地实现不同等级的消息发送可靠性保证。重试策略可以有效的保证消息发送成功的概率,最终提高消息发送的可靠性。

存储端消息可靠性

RocketMQ的消息存储结构如下图所示:
在这里插入图片描述

  • 消息队列存储的最小单位是消息Message。
  • 同一个Topic下的消息映射成多个逻辑队列。
  • 不同Topic的消息按照到达broker的先后顺序以Append的方式添加至CommitLog,顺序写,随机读。

目前RocketMQ存储模型使用本地磁盘进行存储,数据写入为producer -> direct memory -> pagecache -> 磁盘,数据读取如果pagecache有数据则直接从pagecache读,否则需要先从磁盘加载到pagecache中。Broker存储节点的文件存储模式如下图所示:
在这里插入图片描述
Broker端CommitLog采用顺序写,可以大大提高写入效率,同时采用不同的刷盘模式提供不同的数据可靠性保证,此外采用了ConsumeQueue中间结构来存储偏移量信息,实现消息的分发。由于ConsumeQueue结构固定且大小有限,在实际情况中,大部分的ConsumeQueue 能够被全部读入内存,可以达到内存读取的速度。此外为了保证CommitLog和ConsumeQueue的一致性, CommitLog里存储了Consume Queues 、Message Key、Tag等所有信息,即使ConsumeQueue丢失,也可以通过 commitLog完全恢复出来,这样只要保证commitLog数据的可靠性,就可以保证Consume Queue的可靠性。

RocketMQ存储端采用本地磁盘进行CommitLog消息数据的存储,不可避免的就会带来存储可靠性的挑战,如何保证消息不丢失,RocketMQ消息服务一直在不断提高数据的可靠性。

存储可靠性挑战

RocketMQ存储端也即Broker端在存储消息的时候会面临以下的存储可靠性挑战:

  • 1、Broker正常关闭
  • 2、Broker异常Crash
  • 3、OS Crash
  • 4、机器掉电,但是能立即恢复供电情况
  • 5、机器无法开机(可能是cpu、主板、内存等关键设备损坏)
  • 6、磁盘设备损坏

1正常关闭,Broker 可以正常启动并恢复所有数据。2、3、4同步刷盘可以保证数据不丢失,异步刷盘可能导致少量数据丢失。5、6属于单点故障,且无法恢复。解决单点故障可以采用增加Slave节点,主从异步复制仍然可能有极少量数据丢失,同步复制可以完全避免单点问题。

这里一般来说就需要在性能和可靠性之间做出取舍,对于RocketMQ来说,Broker的可靠性主要由两个方面保障:

  • 单机的刷盘机制
  • 主从之间的数据复制

如果设置为每条消息都强制刷盘、主从复制,那么性能无疑会降低;如果不这样设置,就会有一定的可能性丢失消息。RocketMQ一般都是先把消息写到PageCache中,然后再持久化到磁盘上,数据从pagecache刷新到磁盘有两种方式,同步和异步。整体的消息写入和读取如下图所示:
在这里插入图片描述
针对broker端单机存储可靠性,主要依赖单机的刷盘策略,主从之间的副本复制可以参考下一章节的主从模式。

同步刷盘

消息写入内存的 PageCache后,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态。这种方式可以保证数据绝对安全,但是吞吐量不大。

异步刷盘(默认)

消息写入到内存的 PageCache中,就立刻给客户端返回写操作成功,当 PageCache中的消息积累到一定的量时,触发一次写操作,或者定时等策略将 PageCache中的消息写入到磁盘中。这种方式吞吐量大,性能高,但是 PageCache中的数据可能丢失,不能保证数据绝对的安全。
实际应用中要结合业务场景,合理设置刷盘方式,尤其是同步刷盘的方式,由于频繁的触发磁盘写动作,会明显降低性能。

过期文件删除

由于RocketMQ操作CommitLog、ConsumeQueue文件是基于文件内存映射机制,并且在启动的时候会将所有的文件加载,为了避免内存与磁盘的浪费、能够让磁盘能够循环利用、避免因为磁盘不足导致消息无法写入等引入了文件过期删除机制。最终使得磁盘水位保持在一定水平,最终保证新写入消息的可靠存储。

消费端消息可靠性

RockerMQ默认提供了至少消费一次的消费语义来保证消息的可靠消费。
通常消费消息的确认机制一般分为两种思路:

  • 1、先提交后消费
  • 2、先消费,消费成功后再提交
    思路1可以解决重复消费的问题但是会丢失消息,因此RocketMQ默认实现的是思路2,由各自consumer业务方保证幂等来解决重复消费问题。
    消费端Consumer消费消息核心逻辑如下图所示:
    在这里插入图片描述

消费重试

消费者从RocketMQ拉取到消息之后,需要返回消费成功来表示业务方正常消费完成。因此只有返回CONSUME_SUCCESS才算消费完成,如果返回CONSUME_LATER则会按照不同的messageDelayLevel时间进行再次消费,时间分级从秒到小时,最长时间为2个小时后再次进行消费重试,如果消费满16次之后还是未能消费成功,则不再重试,会将消息发送到死信队列,从而保证消息存储的可靠性。

死信队列

未能成功消费的消息,消息队列并不会立刻将消息丢弃,而是将消息发送到死信队列,其名称是在原队列名称前加%DLQ%,如果消息最终进入了死信队列,则可以通过RocketMQ提供的相关接口从死信队列获取到相应的消息,保证了消息消费的可靠性。

消息回溯

回溯消费是指Consumer已经消费成功的消息,或者之前消费业务逻辑有问题,现在需要重新消费。要支持此功能,则Broker存储端在向Consumer消费端投递成功消息后,消息仍然需要保留。重新消费一般是按照时间维度,例如由于Consumer系统故障,恢复后需要重新消费1小时前的数据。RocketMQ Broker提供了一种机制,可以按照时间维度来回退消费进度,这样就可以保证只要发送成功的消息,只要消息没有过期,消息始终是可以消费到的。

RocketMQ会有重复消费的问题吗?如何解决?

在网络中断的情况下可能出现,需要保证消费端处理消息的业务逻辑保持幂等性

RocketMQ延迟消息?如何实现的?

RocketMQ 支持定时消息,但是不支持任意时间精度,仅支持特定的 level,例如定时 5s, 10s, 1m 等。其中,level=0 级表示不延时,level=1 表示 1 级延时,level=2 表示 2 级延时。默认的配置是messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h。

Message msg = new Message(topic, tags, keys, body);
 
msg.setDelayTimeLevel(3);

RocketMQ是推模型还是拉模型?

RocketMQ提供了两种消息消费模式,即PULL和PUSH。在RocketMQ的推模式下,需要知道哪些consumer拥有哪些topic和tags,但在consumer重启或更换topic时,broker无法及时获取信息,可能将消息推送到旧的consumer中,对应consumer主动获取topic,实际上后台会启动异步线程,为每一个queue构建一个pullRequest,并异步地向Broker发起请求。这一过程是通过Broker端阻塞(默认阻塞15秒)的方法来实现的,因此达到了类似推送的效果,这样确保每次主动获取时他对应的topic信息都是最新的。

值得注意的是,无论是推模式还是拉模式,RocketMQ的底层实现都是基于拉模型的。这源于消息实际存储在Broker中,通过topic和tags来区分不同的消息队列。在这种设计下,Producer在发送消息时仅需要将消息发送到对应的Broker以及特定的topic和tags,而无需关心Consumer的具体配置。这种设计使得系统在处理大量的消息传递任务时能够保持高效和稳定。

consumer被分为2类:MQPullConsumer和MQPushConsumer,其实本质都是拉模式(pull),即consumer轮询从broker拉取消息。 区别:

  • MQPushConsumer方式,consumer把轮询过程封装了,并注册MessageListener监听器,取到消息后,唤醒MessageListener的consumeMessage()来消费,对用户而言,感觉消息是被推送(push)过来的。主要用的也是这种方式。
  • MQPullConsumer方式,取消息的过程需要用户自己写,首先通过打算消费的Topic拿到MessageQueue的集合,遍历MessageQueue集合,然后针对每个MessageQueue批量取消息,一次取完后,记录该队列下一次要取的开始offset,直到取完了,再换另一个MessageQueue。

RocketMQ的负载均衡

生产者负载均衡

从MessageQueue列表中随机选择一个(默认策略),通过自增随机数对列表大小取余获取位置信息,但获得的MessageQueue所在的集群不能是上次的失败集群。

集群超时容忍策略,先随机选择一个MessageQueue,如果因为超时等异常发送失败,会优先选择该broker集群下其他的messeagequeue进行发送。如果没有找到则从之前发送失败broker集群中选择一个MessageQueue进行发送,如果还没有找到则使用默认策略。

消费者负载均衡

  • 平均分配策略(默认)(AllocateMessageQueueAveragely)
  • 环形分配策略(AllocateMessageQueueAveragelyByCircle)
  • 手动配置分配策略(AllocateMessageQueueByConfig)
  • 机房分配策略(AllocateMessageQueueByMachineRoom)
  • 一致性哈希分配策略(AllocateMessageQueueConsistentHash)
  • 靠近机房策略(AllocateMachineRoomNearby)

RocketMQ消息积压

提高消费并行读 同一个Consumer Group下,通过增加Consumer实例的数量来提高并行度,超过订阅队列数的Consumer实例无效。

提高单个Consumer的消费并行线程,通过修改Consumer的consumerThreadMin和consumerThreadMax来设置线程数

  • 批量方式消费 通过设置Consumer的consumerMessageBathMaxSize这个参数,默认是1,一次只消费一条消息,例如设置N,那么每次消费的消息条数小于等于N

  • 丢弃非重要消息 当消息发生堆积时,如果消费速度跟不上消费速度,可以选择丢弃一些不重要的消息

  • 优化消息消费的过程 对于消费消息的过程一般包括业务处理以及跟数据库的交互,可以试着通过一些其他的方法优化消费的逻辑。

临时解决方案:
新建一个topic,写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的queue中。临时用一部分机器来部署consumer,每一批consumer消费一个临时queue的数据。等快速消费完积压数据之后,得恢复原先部署架构,重新用原先的consumer机器来消费消息

死信队列和延迟队列的使用

死信消息

  • 消息被拒绝(Basic.Reject或Basic.Nack)并且设置 requeue 参数的值为 false
  • 消息过期了
  • 队列达到最大的长度

过期消息

在 rabbitmq 中存在2种方可设置消息的过期时间,第一种通过对队列进行设置,这种设置后,该队列中所有的消息都存在相同的过期时间,第二种通过对消息本身进行设置,那么每条消息的过期时间都不一样。如果同时使用这2种方法,那么以过期时间小的那个数值为准。当消息达到过期时间还没有被消费,那么那个消息就成为了一个 死信 消息。

队列设置:在队列申明的时候使用 x-message-ttl 参数,单位为 毫秒

单个消息设置:是设置消息属性的 expiration 参数的值,单位为 毫秒

延时队列:在rabbitmq中不存在延时队列,但是我们可以通过设置消息的过期时间和死信队列来模拟出延时队列。消费者监听死信交换器绑定的队列,而不要监听消息发送的队列。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值