【消息队列】RocketMQ笔记&知识点

250 阅读9分钟

RocketMQ常见问题总结(非常好的总结性的文章)

初识RocketMQ | RocketMQ(RocketMQ官网!!)

万字长文讲透 RocketMQ 的消费逻辑(这篇文章很全面,全面介绍了rocketMQ)

千锋教育RocketMQ全套视频教程,快速掌握MQ消息中间件_哔哩哔哩_bilibili

📎rocketmq.pdf(千峰教育配套课件)

顺序消费

19-消息示例-局部顺序消息的实现_哔哩哔哩_bilibili

20-消息示例-全局顺序消息及乱序消息_哔哩哔哩_bilibili

局部有序

  1. 生产者 将同类型的消息 发送 到相同的队列(可以通过哈希取模这种方式)
  2. 消费者通过设置MessageListenerOrderly()达到局部有序消费。同一个队列/同一个类型的消息有序。

结论: MessageListenerOrderly()使得消费者消费同一个队列的消息是有序的

MessageListenerOrderly()顺序消费的原理?

全局有序

消费者消费全部消息都是顺序的,只能通过⼀个某个topic只有一个队列去实现。

这种应⽤场景较少 ,且性能较差

乱序消费

也是并发消费

默认使用 new MessageListenerConcurrently() 就是乱序消费。就算同类型的消息在同一个队列中,同一个队列中的消息也会乱序消费

为什么messagequeue会出现乱序消费?

其实就是并发消费,就算只有一个consumer和一个messageQueue,如果设置 new MessageListenerConcurrently() 也可能乱序。这种情况下一个consumer会起多个线程消费,导致乱序

幂等消息

同步消息与异步消息(生产者)

同步消息异步消息,MQ官网

Apache RocketMQ可用于以三种方式发送消息:同步、异步和单向传输。前两种消息类型是可靠的,因为无论它们是否成功发送都有响应。

都可以设置生产者发送失败的重试次数。

同步消息

同步发送等待结果最后返回SendResult,SendResult包含实际发送状态还包括SEND_OK(发送成功), FLUSH_DISK_TIMEOUT(刷盘超时), FLUSH_SLAVE_TIMEOUT(同步到备超时), SLAVE_NOT_AVAILABLE(备不可用),如果发送失败会抛出异常。

异步消息

异步发送是指发送方发出一条消息后,不等服务端返回响应,接着发送下一条消息的通讯方式。

消息重试(消费者)

RocketMQ的重试方案

消费失败的消息肯定不会立即重试,因为立即重试大概率不会成功。

三种消费情况会引发消息重试

  1. 消费函数中抛异常
  2. return ConsumeConcurrentlyStatus.RECONSUME_LATER;
  3. return null;
consumer.registerMessageListener(new MessageListenerConcurrently() {
    @Override
    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
        for (MessageExt messageExt : list) {
            System.out.println("receive the msg: " +  new String(messageExt.getBody()) );
        }
//                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        return null;
        return ConsumeConcurrentlyStatus.RECONSUME_LATER;       // 重试
    }

});

重试次数

RocketMQ对于重 试消息的处理是先保存⾄Topic名称为“SCHEDULE_TOPIC_XXXX”的延迟队列中,后 台定时任务按照对应的时间进⾏Delay后重新保存⾄“%RETRY%+consumerGroup”的 重试队列中。 与延迟队列的设置相同,消息默认会重试16次,每次重试的时间间隔如下:

10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h

如果都没有消费成功会加入死信队列。

消息模式

广播消息

广播模式下,队列中一条消息会发送给 同一个消费组中的所有消费者

集群消息

集群模式下,队列中一条消息只会发送给(同一个消费者中的)一个消费者实例。

消费模式Push&Pull

推push 和 拉pull 模式,分析。

  • 推:实时性好,但客户端没做好流控造成消息挤压和系统崩溃。
  • 拉:客户端按照自己消费速率在拉消息,但中间件负载较高。
  • 推常用的实现方式是:长连接/注册回调函数。拉常用的实现方式是:轮询。
  • 但常用的实现中会采用长轮询的方式(长连接+轮询)。结合了长连接+轮询的优缺点。客户端先发一个长轮询请求拉,如果有消息直接返回,如果没有消息也不会立刻断开当前连接,然后等会,期间有消息会直接返回,期间没有消息时间到了就会断开连接。结束等待下次长轮询。

消息存储

CommitLog和ConsumerQueue

CommitLog和ConsumerQueue

不要被上面的箭头方向误导了。

  • 理解:实体消息都是在CommitLog这个文件中,不管是哪个topic的消息,都是追加写入CommitLog。而ConsumerQueue又是另一个文件,一个ConsumerQueue对应topic中的一个队列ConsumerQueue中每个条目存储的不是实际的消息,而是消息在CommitLog中的偏移量offset及消息的大小,所以可以将ConsumerQueue这个文件看成一个索引文件。而且consumerQueue的每个记录都是固定大小20字节。

  • 在图中最左边说明了红色方块代表被写入的消息,虚线方块代表等待被写入的。左边的生产者发送消息会指定 Topic、QueueId 和具体消息内容,而在 Broker 中管你是哪门子消息,他直接 全部顺序存储到了 CommitLog。而根据生产者指定的 Topic 和 QueueId 将这条消息本身在 CommitLog 的偏移(offset) ,消息本身大小,和 tag 的 hash 值存入对应的 ConsumeQueue 索引文件中。而在每个队列中都保存了 ConsumeOffset 即每个消费者组的消费位置(因为不同消费者组都有自己的消费offset)(我在架构那里提到了,忘了的同学可以回去看一下),而消费者拉取消息进行消费的时候只需要根据 ConsumeOffset 获取下一个未被消费的消息就行了。
  • 总结,CommitLog存储实际消息且追加写。ConsumerQueue类似于一个索引文件, 一个队列对应consumeQueue,每个consumeQueue有多个ConsumeOffset。

commitLog文件最大是多少?

最大一个G,如果写满了就新建一个commitLog文件往里面写。

commitlog和indexFile

RocketMQ【消息存储】之IndexFile_rocketmq indexfile-CSDN博客 (很详细地讲了indexFile的物理结构)

IndexFile(索引文件)提供了一种可以通过key或时间区间来查询消息的方法Index文件的存储位置是:SHOME\storelindexS{fileName},文件名fileName是以创建时的时间戳命名的,固定的单个IndexFile文件大小约为400M,一个IndexFile可以保存 2000W个索引,IndexFile的底层存储设计为在文件系统中实现HashMap结构(数组),故rocketmg的索引文件其底层实现为hash索引

RocketMQ的混合型存储结构(多个Topic的消息实体内容都存储于一个CommitLog中)针对Producer和Consumer分别采用了数据和索引部分相分离的存储结构,Producer发送消息至Broker端,然后Broker端使用同步或者异步的方式对消息刷盘持久化,保存至CommitLog中。只要消息被刷盘持久化至磁盘文件CommitLog中,那么Producer发送的消息就不会丢失。正因为如此,Consumer也就肯定有机会去消费这条消息。当无法拉取到消息后,可以等下一次消息拉取,同时服务端也支持长轮询模式,如果一个消息拉取请求未拉取到消息,Broker允许等待30s的时间,只要这段时间内有新消息到达,将直接返回给消费端

CommitLog和ConsumerQueue实践

rocketMQ消息存储文件夹,broker的文件存储目录。

commitlog目录

Broker 单个实例下所有的队列共用一个数据文件(commitlog)来存储,单个commitlog文件大小默认 1G ,超了就新起一个文件

consumeQueue目录

  • consumeQueue存了所有topic的consumeQueue文件,如果一个topic有n个队列,那么每个topic文件夹下还有n个文件夹,表示n个consumeQueue,每个队列一个文件夹(因为可能一个文件不够存)。
  • 例如,下面myTopic1有8个队列

config与consumerOffset

在config下的consumerOffset.json

在json文件中可以看到,(集群消费模式)每个topic为每个消费者组都维护了offset,记录了每个consumerQueue(队列)的消费进度offset。

在进度文件 consumerOffset.json 里,数据以 key-value 的结构存储,key 表示:主题@消费者组 , value 是 consumequeue 中每个队列对应的逻辑偏移量 。

消息刷盘

注意,同步刷盘异步刷盘 同步发送消息异步发送消息 是两个概念。 同步发送和异步发送区别是发下一条消息前是否需要broker响应,同步发送异步发送都能保证可靠(官网说的) ****同步发送是生产者这边保证消息可靠 (还有生产者的重试机制保证),同步刷盘则是MQ来保证消息可靠

同步刷盘异步刷盘都需要返回ACK,所以不管是同步刷盘还是异步刷盘都是同步发送消息。

  • 同步刷盘,刷磁盘

如上图所示,只有在消息真正持久化⾄磁盘后RocketMQ的Broker端才会真正返回给Producer端⼀个成功的ACK响应。同步刷盘对MQ消息可靠性来说是⼀种不错的保障,但是性能上会有较⼤影响,⼀般适⽤于⾦融业务应⽤该模式较多。

  • 异步刷盘,刷PageCache(有点像aof的刷盘策略,也是可以通过配置看刷到磁盘或者pageCache

能够充分利⽤OS的PageCache的优势,只要消息写⼊PageCache即可将成功的ACK返回给Producer端。消息刷盘采⽤后台异步线程提交的⽅式进⾏,降低了读写延迟,提⾼了MQ的性能和吞吐量。

同步复制和异步复制

  • 同步复制:也叫 “同步双写”,也就是说,只有消息同步双写到主从节点上时才返回写入成功
  • 异步复制:消息写入主节点之后就直接返回写入成功

首先,不是说 异步复制 就降低可靠性了,异步复制只是降低了可用性。(可靠性和可用性是不同的概念)

为什么?比如说,主节点收到消息后,已经刷盘但未同步,主节点挂了,从节点成为新的主节点(只能被消费,生产者不能生产)后虽然 未同步主节点那一部分消息,但是旧主节点重启后依然可以将磁盘中未同步的消息进行同步