装潢设计怎么样,做seo网站优化价格,网页制作与网站建设实战教程视频教程,wordpress免备案空间架构图 Broker主从架构与集群模式 RocketMQ原理深入剖析 Broker主从架构原理 HAConnection与HAClient Broker基于raft协议的主从架构 Consumer运行原理 基础知识 001_RocketMQ架构设计与运行流程分析 RocketMQ这一块#xff0c;非常关键的一个重要的技术#xff0c;面试的时候… 架构图 Broker主从架构与集群模式 RocketMQ原理深入剖析 Broker主从架构原理 HAConnection与HAClient Broker基于raft协议的主从架构 Consumer运行原理 基础知识 001_RocketMQ架构设计与运行流程分析 RocketMQ这一块非常关键的一个重要的技术面试的时候也是非常的高频 建议的学习顺序分为三块 1、儒猿技术窝里的付费的专栏《从0带你成为消息中间件实战高手》几十块钱rocketmq整体的基本原理高阶的玩法以及一些案例的实践都讲了一下用在项目里的时候可能出一些什么问题如何解决
2、rocketmq内核原理会把rocketmq内核级的深入的源码讲解一遍让大家站在第一点的基础之上可以对rocketmq有一个技术深度的理解和掌握
3、会给大家去讲解电商场景下的一些基于rocketmq实战案例包括实战代码生产环境下如何对rocketmq进行每秒上10w并发的大压力的生产经验和优化
4、rocketmq的源码不在跳槽营的范围之内儒猿有自己的高阶课的体系架构级的一些课程本身来说是在儒猿高阶的课程里 002_NameServer是如何管理Broker集群的 为什么各个Namesrv之间不通信为什么设计上要做这样的取舍保证Namesrv功能的单一、简单不需要维持各个Namesrv之间的peer to peer也就是不需要强制维持各个Namesrv之间数据强一致。把压力给到broker端去就算两台Namesrv之间数据不一致表现出来的效果也是下层不同broker受到的读写压力不一样
Eureka之间是互相通信保证数据一致的这其实就是一种设计上的取舍无所谓好坏 003_Broker主从架构与集群模式原理分析
broker主从broker集群大家一定要把这些概念理清楚才能深入理解nameserver的内核的原理 一个broker cluster集群会有多套broker主从 004_基于 Topic 队列机制实现的数据分片架构
注意理解RocketMQ是如何实现的数据分片治理 消息中间件有一个关键的数据模型和概念Topic它是逻辑上的一个大的数据集合逻辑概 念不是物理概念要往一个 Topic 里写入消息Topic 是虚的概念底下实的概念其实是 broker但是对我们的业务系统来说我们在写代码的时候是没有 broker的概念在里面只有 topic 的概念topic - broker 之间需要有一个关联虚到实 消息数据分布式存储
数据分片概念producer 写出来的消息会分散在很多台 broker 服务器上每台 broker 服务器上存储的消息数据都是一个shard数据分片 此时引入一个RocketMQ 里的很关键的概念topic - 多个 queue逻辑上的概念映射到物理 层面去了每个 queue 可以认为就是一台 broker 服务器上的一个磁盘数据已经可以初步的 做一个映射关系了 一个topic 在各个 broker 分组里的 queue 是如何分布的呢
默认情况下一个 topic 你创建之后会在每个 broker 分组里搞 4 个 write queue4 个 read queue 005_NameServer 内存中核心数据模型分析 // 表示有RuyuanBroker01, RuyuanBroker02两套主从 clusterAddrTable: { RuyuanRocketMQCluster: [RuyuanBroker01, RuyuanBroker02] } brokerAddrTable: [{ RuyuanBroker01: { cluster: RuyuanRocketMQCluster, brokerAddrs: [0: xx.xx.xx.xx:8888, 1: xx.xx.xx.xx:8889] } },{ RuyuanBroker02: { cluster: RuyuanRocketMQCluster, brokerAddrs: [0: xx.xx.xx.xx:8888, 1: xx.xx.xx.xx:8889] } }] brokerLiveTable: { xx.xx.xx.xx:8888: { lastUpdateTimestamp: 242342341, haServerAddr: xx.xx.xx.xx:8889 }, xx.xx.xx.xx:8889: { lastUpdateTimestamp: 242342341, haServerAddr: xx.xx.xx.xx:xxx } } topicQueueTable: { RuyuanTopic: { { brokerName: RuyuanBroker01, readQueueNums: 4 writeQueueNums: 4 }, { brokerName: RuyuanBroker02, readQueueNums: 4 writeQueueNums: 4 } } } 006_Producer 内核级发送消息机制分析
1、 消息是如何发送到 broker去的 2、 如果说消息要是发送失败了此时会如何处理 3、 发送消息的时候有哪些比较高阶的特性可以使用按照 key hashorderid 相同的消息orderid001都进入到一个 queue 里去以保证他们的顺序性
发送消息肯定是指定一个topic 往topic里去进行一个发送RuyuanTopic send message把一个消息发送到我们的 topic 里去首先需要获取该topic他有哪些 queue这些 queue 分布在哪些 broker 上 生产者进行消息发送时 先从生产者本地关于topic信息的缓存中通过比如轮询的负载均衡算法在topicQueueTable中选择出一个queue然后根据选择出来的queue对应的brokerName从brokerAddrTable中找出该brokerName对应的brokerAddrs列表中brokerId为0的主节点然后对着这个主节点进行真正的网络通信发送消息的过程 007_ 基于 Producer内核分析消息hash分发原理
在各个broker上一个topic是有很多的queue默认情况下往一个topic里写入的数据会均匀分散到各个broker的各个queue里去
同一个queue就代表一个队列所以消息进入到同一个queue里去的时候在同一个queue里的消息是有顺序的。 但是不同的queue之间的消息是没有顺序的如果说有一些场景让某一类数据有一定的特殊顺序性比如orderidorderid11001对应的多条消息可以有顺序唯一的选择就是让 orderid11001的所有消息都进入同一个queue 同一个队列保证他们在同一个队列里是有顺序的
用这个字段值的hash值对queue的数量进行取模就可以确保同一个字段值 - 同一个hash值 - 取模出来同一个queue 序号 - 进入同一个 queue - 保证有顺 今序
topic queue - data shard分片 发送高可用
broker故障延迟感知因为nameserver在发现某个broker没有更新心跳挂了以后不会主动的通知各个producer说某个broker挂了。只有producer自己每30s主动拉取刷新一次路由缓存才能够感知到某个broker挂了而在这期间producer发送到这台broker的消息可能会失败但是失败后可以有故障避退和重试发送的机制来保证整个的发送过程高可用
自动重试机制
故障退避机制 nameserver在发现某个broker挂了以后不会主动的通知各个producer因为这样会使得nameserver的实现变得很复杂每次注册表变化还得通知各个producer但zk是这样实现的 此时的消息发送的负载均衡机制就从轮询变成了hash取模的负载均衡机制
其实rocketmq的多个broker就已经组成了一个分布式存储系统了客户端发送过来的消息通过一定的负载均衡机制打散存储在各个broker的queue之上 008_Broker 如何实现高并发消息数据写入
写入消息有两种方式一种随机写一种顺序写 写哪里 内存、磁盘
一般来说如果说你要持久化保存我们的消息数据的话消息必须是要落地到磁盘里去的 落地到内存里去避免内存里的dsfdsdf数据丢失此时就需要设计一套避免内存里数据不丢失的一套机制这套机制一般都是基于WALwrite ahead log这种也是需要写磁盘的 到底是用随机写顺序写
随机写磁盘文件里面的数据格式是你自定义的每次写入数据都是需要找到磁盘文件里的某个位置在那个位置里插入你最新的数据 word文件肯定是放在我笔记本电脑的磁盘里的 磁盘文件每次写入数据都是在一个文件末尾去进行追加就可以了绝对不会随机在文件里寻址找到一个中间的位置插入数据 而是就是不停的对文件进行数据追加这种就叫做顺序写
如果要是不把数据写在磁盘里而是写在内存里 顺序写和随机写意思是差不多的 内存里随机写内存也是有地址空间 就是在内存里随机的内存地址的寻址再去插入数据一块连续 的内存空间里顺序的不停追加写入数据避免了随机寻址 在内存里随机写数据性能当然是非常高的了
随机写磁盘文件 - 几十 ms 、 几百 ms顺序写磁盘文件 约等于在内存里随机写数据 - 毫秒级
Commitlog默认是1GB每个文件不要过大过大了以后对后续的数据读写性能都不太好所以写 满 了一个文件就切换下一个文件就可以了 009_RocketMQ读写队列的运作原理分析
设计write queue和read queue概念的原因
主要是为了让producer发送消息是路由到哪些队列和consumer消费时消费哪个队列进行分离从而为了进行队列的扩容和缩容的方便
write queue是一个实的是有磁盘文件一一对应的和
read queue是一个虚的一般一个read queue都会绑定一个write queue从而方便consumer通过read queue实际消费到write queue中的数据 010_Consumer 内核级消息拉取原理分析
rocketmq写concumser代码的时候一般都是写一个consumer listener 回调监听函数让 consumer在拉取消费到消息之后把消息回调来调用我们自己写的回调监听函数在函数里进行消息的处理就可以了 老师有一个问题 当我们不使用rocketmq在消费端自带的线程池而使用我们自定义的业务线程池来消费消息 那当我们把在ComsumerListener#consumeMessage()方法中把消息一丢入自己定义的业务线程池就相当于返回了Consume_Success那消费进度就提交了此时如果自定义的消费线程池出了问题当前这条消息不就丢失了吗 这个问题一般有什么解决方案呢
管理员(2023-9-20): 同学你好可以在消费者端在消息消费之前将消息本地存储起来然后将消息提交给自定义的业务线程池。如果自定义线程池出现问题你可以实现一个重试机制定期检查未确认的消息并重新处理它们 011_CommitLog和ConsumeQueue物理存储格式
consumer读取消息的场景consumer 正常来说都是按照顺序一条消息一条消息的去进行读取 一个 topic 是可以给多个 consumer group 去进行消费的
比如RuyuanTopic这个topic 8 个 queue - 业务系统 A 部署了 3 台机器 这三台机器组成一个consumer group 每台机器就是一个 consumer 业务系统B 部署了 5 台机器组成另一个consumer group 每台机器就是一个 consumer
不同的 consumer group 对一个 queue 消费的 进度是不一样的 有的consumer group对这个queue可能已经消费了 500 个消息了有的 consumer group可能才消费了100 个消息
假设这个queue 里已经有了 1 000 个消息了 但是有的 consumer group 才消费到了 1 00 个消息 此时下一个消息的消费是从 101 个消息继续 往后去进行消费的broker在接收到consumer发送过来的拉取offset从101开始的消息的请求时需要能快速的定位到第101条消息
一个consume queue是一个磁盘上的文件甚至是多个文件如果想定位到第101条消息此时就涉及到随机磁盘读 也就是说我们希望能够随时 根据我要消费的消息的序号快速定位到那条消息在磁盘文件里的位置再定位到那个位置从那个位置去进行一个读取需求
针对这样的一个读取的需求我们的 consume queue 磁盘文件应该如何设计才能支持我们高效的磁盘位置定位以及读取呢 012_ConsumeQueue 的高性能物理存储设计
ConsumeQueue对应的物理磁盘目录层级结构为~/topicName/queueId/多个磁盘文件
比如~/RuyuanTopic/0/ 多个磁盘文件每个磁 盘文件里都会存储一条一条消息的索引条目每条索引条目在磁盘文件里存储的是什么东西 commitlog 偏移量 8 个字节大小 4 个字节tag 哈希码8 个字节每条消息在 ConsumeQueue 磁 盘文件里存储的大小是定长的 20 个字节一条条目数据固定下来每个 ConsumeQueue 里面大概是 30 万个条目每个 ConsumeQueue 的磁盘文件大概就是 5.72MB 大小 013_ConsumeQueue 如何实现高性能消息读的
某一个consumer group来读取的消息每条消息都是有一个自己在queue中的逻辑offset 逻辑 offset可以大致认是queue里的第几条消息
consumer group里的一个consumer是负责读取某个queue的消息的此时他知道说要读取的是这个queue 里的第几个消息 比如说要读取的是第 150 个消息应该先是根据消息的逻辑上的 offset根据他是queue里的第几个消息 先去定位到他是属于该 ConsumeQueue 的哪一个磁盘文件 就可以 150 * 20 个字 节 1 - 1 * 20 个字 节 0 字 节 起始位置 从 0 字 节 开始 读取 20 个字 节就可以把第一个消息读取出来了 150 – 1 * 20 个字 节 算出来一个字节 数就是这消息的起始位置对于这个起始位置是真实的磁盘中的物理位置
物理位置是根据字节计算出来的从那个起始位置开始读连续读20个字节就可以把第150 个消息索引条目给读取出来
这种方式其实是一种极为高效率高性能的 consume queue 读取方式根本就不需要进行遍 历读取磁盘文件里一条一条的消息来查找不需要遍历 有点类似于跟数组 [index] 直接定位到一个位置然后从那个位置把数据读取出来一 样的效果 不需要像链表一样一直遍历下去才知道当前消息是queue中的第几条消息而是类似数据的定位方式这使得consumer带着消息逻辑偏移来broker拉取消息时broker能通过这种快速定位的方式快速的找到该条消息这一点是非常重要的 014_CommitLog 基于内存的高并发写入优化 1 、 Broker 写入性能优化 1 CommitLog在写入的时候是基于磁盘顺 序写来提升性能的 2 ConsumeQueue是基于异步转发写入机制来提升一个性能的
2 、 Broker读取性能优化 1 ConsumeQueue 基于定长消息索引条目 定长文件 消息索引条目的一次定位 读取 2 CommitLog 是基于文件名 第一条消息的 总物理偏移量 基于消息物理偏移量可以快速的定位CommitLog文件以及其中的物理起始位置 一次定位 读取
无论是写入还是读取此时此刻最大的问题就是还是基于物理磁盘文件在做这个事情能否基于内存进一步的去提升我们的整体的写入性能和读取的性能
rocketmq确实就是基于内存来提升了commitlog 他的写入性能如果不基于内存映射机制哪怕是仅仅使用磁盘顺序写入写入性能也不是最优的
基于MappedFilemapping文件内存映射机制 实现了一个把你的消息数据写入由直接写入磁盘变成写入到内存里然后由操作系统的异步刷磁盘线程把内存里的消息通过磁盘顺序写来刷到磁盘里去
此时的消息写入就既用到了内存映射又用到了磁盘顺序写 关于PageCache与内存映射
PageCache
首先要认识到一点因为磁盘和内存之间速率的差异以及程序的局部性原理linux引入了PageCache的概念所以就算是传统的普通IO模式中依然是有PageCache的存在的传统IO下数据也是由缺页中断调起先从磁盘进入PageCache然后用户程序空间再通过read()系统调用把数据从PageCache复制到用户空间中来 内存映射
核心的一点是是磁盘文件对应的PageCache与用户虚拟地址空间之间的映射内存映射指的不是PageCache与磁盘文件之间的映射。PageCache与磁盘文件之间的映射是linux在引入PageCache这个概念时就自己帮我们做好了
当磁盘文件对应的PageCache与用户虚拟地址空间完成了一对一映射后程序员通过直接操作虚拟地址空间就实现了操作磁盘文件对应的PageCache的效果。原来的传统io模式还需要把PageCache中的内容拷贝到用户空间完成修改后再通过系统调用write()来把修改的内容写回PageCache
总结一下就是有了内存映射后少了一次PageCache拷贝到用户空间的操作程序员可以在用户虚拟地址空间来操作虚拟地址来达到直接操作内存中的PageCache的效果 如果后台异步线程在将commitlog写入到ConsumeQueue的时候宕机了是不是要记录一下已经写入ConsumeQueue队列的最大commitlog偏移量然后在在系统恢复的时候才能将没有写完的commitlog继续异步写入ConsumeQueue队列
管理员(2022-9-24): 是的是会记录commitlog maxoffset 015_Broker数据丢失场景以及解决方案
1 、 Broker 写入性能 优 化 1 CommitLog 写入磁 盘 文件 顺 序写 磁 盘 文件 -page cache 内存映射 直接就是 顺 序写内存
如果希望数据是 0 丢 失的 只能改成同步刷盘 磁盘顺序写
如果希望牺牲一点数据丢失性 换取高吞吐高性能保持默认的异步刷盘就可以了 2 ConsumeQueue 是异步写入的 这块 性能影响就不大了
第一种情况 Broker rocketmq 就是用 java 开 发出来的中间件系 统 启 动 之后就是一个 jvm 进 程 Broker 作为 一个 jvm 进 程 突然崩溃 掉了 仅仅只是说是一个 jvm 进程没了而已 page cache 里的数据是 os 管理的 概率比较高一些
第二种情况 Broker jvm 进程所在的服务器服 务器自己本身故障宕机了os 、虚拟机、物理机、硬件层面的故障此时就会导致你之前写入到 page cache 里的数据丢失 这种情况发生的概率很低很低很低但不是说不会发生机器故障 也是有的小概率极端情况下数据丢失一定要考虑到位 016_Page Cache 内存高并发读写问题分析
牺牲一丢丢的数据可能丢失的问题 换来的是写内存提升性能和吞吐量
page cache 内存数据可能会在高并发高吞吐的 读写竞争之下因为异步刷盘场景下producer刚刚才把大量的消息写入到PageCache又立马有大量的消息读请求此时的消费者会直接从PageCache中读取消息不用去磁盘读
此时就会出现一个经典的问题rocketmq里异常 broker busy broker 过于繁忙导致你的一些操作可能会阻塞住或执行失败。因为此时os的page cache数据被竞争争用的太频繁了 太激烈了高并发和高吞吐的场景之下的
如何解决上述的PageCache被竞争争用太频繁的问题transientStorePool机制 017_基于jvm offheap 的内存读写分离机制
transientStorePoolEnabled 机制 瞬时存储池启用 机制 你可以 选择 去开启他 如果你要真 是 压 力大到了
老师有个疑问rocketmq也采用的这样的双缓冲的刷盘机制但是是采用的一个单独的后台线程定时每10ms刷一次缓冲中的数据到磁盘而不是像我们这里是随机的某一个客户端写入线程来执行真正的刷盘动作。hadoop的刷盘逻辑可能会造成大量写入线程的周期性卡顿而rocketmq的实现方式好像就不存在这个问题
管理员(23小时前): 1、Hadoop的随机客户端写入线程刷盘机制 Hadoop采用这种机制在写入数据时由随机的客户端写入线程执行实际的刷盘动作将数据刷入磁盘。 这种方式的优点是可以充分利用系统的并行性不会受到单一后台线程的限制可以实现高并发写入。 缺点是可能会出现周期性的卡顿尤其当有大量写入线程在同一时间刷盘时可能会导致短暂的写入延迟。 2、RocketMQ的后台线程定时刷盘机制 RocketMQ采用一个单独的后台线程定时刷盘将缓冲中的数据定期刷入磁盘通常是每隔一定时间如10ms执行一次刷盘操作。 这种方式的优点是可以避免周期性卡顿因为刷盘动作是由单独的后台线程负责不会受到其他写入线程的影响。 缺点是在刷盘时间间隔内数据可能会在内存中暂存有一定的数据丢失风险。 针对同学你的疑问为什么Hadoop的刷盘逻辑可能会造成大量写入线程的周期性卡顿而RocketMQ的实现方式不存在这个问题 并发度与刷盘频率的平衡 Hadoop的随机客户端写入线程刷盘机制可能会造成卡顿主要是因为并发度较高时多个写入线程同时刷盘会导致竞争和延迟。在Hadoop中需要平衡并发度和刷盘频率避免过多的写入线程同时刷盘。 业务特点和数据丢失风险 RocketMQ选择定时刷盘的方式可能基于其业务特点和对数据丢失的容忍程度。在消息中间件场景下短暂的数据暂存可能是可以接受的而定期刷盘可以降低系统的整体压力。
出现了我 刚 才所 说 的那个 问题 之后就可以实现一个内存级别 的 读 写分离模式
一般来说 在一个服 务 器上部署了一个系 统 之后 这个系统作为一个 jvm 进 程会运行再操 作系 统 上 内存一般来说分成三种一种 jvm heap 内存 jvm 管理堆内存第二种是 offheap 内存 jvm 堆外的内存 第三种就是 page cache 是 os 管理的
开启这个机制后就变成了一个“两级缓存”的机制第一级缓存是堆外内存第二级缓存就是PageCache。此时消息是先写入堆外内存就返回消息写入成功了然后有后台线程每隔一段时间将堆外内存中的消息一次性刷到PageCache中然后进入PageCache中的脏页就由内核自己的后台线程默认每隔500ms刷一次脏页到磁盘中去
此时就实现了一种读写分离的效果
写是先写入堆外内存然后隔段时间统一刷入PageCache一次就降低了针对PageCache的频繁的写压力将写压力转给了堆外内存
读是直接去读PageCache因为PageCache没有了频繁的写压力所以此时去频繁的读也不会产生激烈的读写竞争 018_jvm offheappage cache 数据丢失问题
系统设计里凡事皆有利弊没有什么事情是十全十美的不可能的
为了解决一个问题往往会引入一个新的问题 开发一个系统 遇到了一个技术问题为了解决他引入了一个新的技术 可是新的技术 引入了之后他自己本身又会产生新的问题 所以这个就是一个非常的 问题
为了解决高并发高吞吐之下对 page cache 竞 争 读写导致的问题引入了 jvm offheap 做了两 级缓 存 实现了内存级别的读写分离 解决掉了 对 一 块 内存空间的写和读竞争的问题搞定了 数据丢失的风险会大大的提高
数据丢失主要是分为两种情况
1 、 broker jvm 进程崩溃宕机 jvm 进程可能会意外退出或者自己正常的关闭 broker jvm 进 程再重新启动和打开他 此时 jvm offheap中的数据也会丢失
2 、 broker 所在的服务器可能会有一个崩溃的 问题此时jvm offheap和PageCache中的数据都会丢失
没有一个技术方案是完美的你只能去抓当前场 景里的主要矛盾是什么
你金融级的数据数据绝对不能丢失的你可能要牺牲性能和吞吐量直接让你的数据每一次写入都直接刷盘
大部分的普通情况数据可以丢一下下丢一点数据是在服务器宕机极端场景下才会丢几百毫秒的数据这个时候就保持默认就可以了写 page cache 同时异步的刷 盘
默认情况下出现了高并发竞争 page cache 的问题时开启高阶机制 jvm offheap 容忍一定的 jvm 崩溃丢失一点数据核心原理就是通过使用多级缓存的读写分离来抗高并发读写和高吞吐
这个机制懂原理就好实际中一般不开启 019_ConsumeQueue 异步写入失败恢复机制
一次消息的真正写入成功需要写入CommitLog和写入ConsumeQueue都成功。写入CommitLog和写入ConsumeQueue是两个独立的步骤两者之间是异步执行的
写入到MappingFile映射文件中的数据只要linux系统不宕机这部分数据迟早会通过linux的内核后台线程刷去磁盘CommitLog文件中也就是说这部分数据不会丢
写入到ConsumeQueue的数据则不同有另外的一个负责写ConsumeQueue的后台线程当监听到CommitLog文件有新消息写入时则会将该条消息在CommitLog文件中的全局物理偏移、消息大小size、还有消息的哈希码三个作为一个消息索引条目写入ConsumeQueue
场景一
消息写入CommitLog文件成功后后台线程还没没监听到这条消息linux服务器就宕机了此后让linux服务器重启以后后台线程仍然可以去监听写入CommitLog文件但还没写入ConsumeQueue的消息继续后续写入动作
场景二
消息写入CommitLog文件成功后且后台线程监听到这条消息准备开始异步写入ConsumeQueue时 linux服务器就宕机了此后让linux服务器重启以后后台线程有一套比对机制比对写入CommitLog文件但还没写入ConsumeQueue的消息继续后续写入动作
至于这个比对机制我还没在源码中见过后续在看这个很重要数据异步双写的一致性兜底策略
总结一下就是只要成功写入到CommitLog文件中的消息都能保证成功写入ConsumeQueue中 020_Broker写入与读取流程性能优化总结
物理存储结构主要是为了优化写入和读取 都是为了写入、存储 、 读取 这三块去做一个设计的写入尽可能高吞吐高并发如何让存储可以有效的进行数据结构组织服务于我们的写入和读取 如何让高并发的读取可以有效的进行broker主要要做到的一些事情
1 、 写入优化 1 默认就是直接写入 os page cache 里 mappedfile 机制来实现的把磁盘文件映射成一 块内存写文件 写内存就直接返回成功了内存级顺序写入亮点就是基于 os page cache 来写入数据 如果 broker jvm 进程崩溃高概率事件 了是不会导致os page cache 的数据丢失的服务器崩溃的极端场景才会导致几百毫秒内写入的数据会丢失一般来说不会发生 2 对于 ConsumeQueue 和 IndexFile 写入 是异步写入的这个也是性能提升的一个点 但是只要数据在 commit log 里没丢失哪怕是异步写入没有成功broker jvm 就崩也不能溃了 但是broker jvm重启和恢复了此也时基于 commit log 数据都可以恢复consume queue 的数据
2 、 存储结构 1 ConsumeQueue 存储结构是经过了极大的 优化设计的物理存储结构设计极 为 的精 巧的每个消息在 ConsumeQueue 里存储的都是定 长的 20 字 节 每个文件也是定长的是30w 个消息定长则能很好的topic目录 - 多个 queue目录 - 多个磁盘文件每个磁盘文件一 样 大的都是5.72MB 2 CommitLog 他的物理存储结构也是精心 设计的他也是每个文件默认就 1 GB 满了以 后就写下一个文件 文件名就是每条消息在所有的 commitlog 里都有一个总的物理偏移量 每 个文件的第一条消息他的总物理偏移量 就是文件的名称 每个 commitog 他的起始消息的总物理偏移量通过文件名就可以看出来了
3 、 读取优化
1 根据消息 逻辑 offset 偏移量 类 似于 这 个queue 里的第几个消息定位到你的 ConsumeQueue 的磁盘文件比如第30w到60w个消息索引条目就在第2个磁盘文件中在磁盘文件里就可以去根据你的逻辑上的偏移量 就可以去计算出在ConsumeQueue 的磁盘文件中的物理偏移量 通过这第一次定位就找到这条消息索引条目 通过条目就可以找到消息在 commitlog 里的全局物理偏移量 通过全局物理偏移量再第二次定位就可以把真正的消息读取出来了
2 高并发的对page cache 进行读写竞争的时候 broker busy transientStorePool 机制 开启之后就会启用 jvm offheap 内存内存级的读写分离 021_Broker基于Pull模式的主从复制原理
push 模式 pull 模式
push 模式 - 我 们业务 系 统 的 producer 往 broker 主 节 点里写入数据之后 直接由 broker 主 节 点主 动 的把数据 push 发 送到从 节 点里面去
pull 模式 - producer 仅仅是往 broker 主节点里写入数据此时主节点是不会主动的把数据推送到从节点里去是等待从节点反过来发送 pull 拉取数据的请求然后主节点收到了拉取数据的请求之后才会把数据发送给从节点 022_Broker层面到底如何做到数据0丢失
在这套主从同步机制之下我们如何做到数据0 丢 失如果broker主节点改成同步刷盘机制之后 每一次写入必须把数据写入到物理磁盘的层面 此时写入才会返回broker jvm 进程崩溃 数据 100% 不会丢 broker 部署所在的服务器突然宕机了断 电 此时数据 页也不会丢失了 因 为数据在物理磁盘上第三种极端中的极端中的多年难得一遇的场景物理磁盘坏了 没法恢复数据
仅仅是依赖 broker 主节点同步刷盘 还是会存在物理磁盘损坏导致数据丢失主节点崩溃了以后 此时 如果你的数据 还没来得及同步给从 节点 此时主节点崩溃了从节点还没同步到这 个数据此时你从从节点里读取数据是读不到的这也是一个问题 短时间内如果主节点没有恢复从节点也是读不到数据的
此时broker层面100%稳妥的办法就是改成同步刷盘 同步复制但是这种写入性能太差一般不使用 023_数据0丢失与写入高并发的tradeoff 99% 99.9% 99.99% 99.999% - 无限的逼近与 100% 无限的把 损 失的概率降低到接近 于 0% 这是我们能做到的事情
反复的跟大家讲的反复权衡tradeoff 你要的到底是什么你想要什么你能失去什么任何事情技术上的事情系统设计上的事情没有1 00% 的完美当你要解决一个问题的引入了一堆的技术此时连带引入对应的新的问题
如果你真想实现数据在 broker 层 面的 0 丢 失 确实写入成功的数据几乎不会丢失 但是写入性能写入吞吐量狂跌不止性能估 计 会 调 几个数量级 一次消息写入 broker 异步刷 盘 异步复制 只要写入 page cache 就可以返回了 几 ms 、 几十 ms - 同步刷盘 几十 ms 、 几百 ms 同步复制 等待从节点发送 pull 请 求 物理磁盘读数据通过网络把拉取数据返回从节 点把数据写入磁盘从节点下一次 pull 请求快 则几百 ms慢一两 s 都有这种可能
几百 ms 打底 一两 s 两三秒 s 都有 这 种可能吞吐量和并发就会咔咔掉 单位时间里能 完成的发送消息的请求数量原来是每秒可以上万 现在是每秒只能几百1000 都有可能
金融级场景钱有关的才要做到这么严苛 一点 问题不能出 场景生产者发送消息到brokerbroker同步刷盘了此时在等待从节点的拉取响应。如果此时从节点在没有拉到最新写入主节点的数据就挂了。那么此时broker如何响应给生产者呢如果响应失败的话实际上消息在主broker上已经写入了还没有写入从节点而已如果生产者重试是否消息就会重复了
管理员(2022-8-7): 首先要看rocketmq的架构是什么现在都是raft高可用的架构方式是主写点写完之后去同步其他follower节点等待大多数节点响应后返回给client端broker上有条消息但是broker这条数据不是commit的是uncommit的状态这块你可以看下raft写log的过程uncommit的数据是无效的消费者是消费不到的响应失败后producer端需要自己重试来发送 024_RocketMQ 4.5.0 以前的读写分离模式
默认情况下rocketmq 是不倾向于主动让你直接 长期进行读写分离的而是倾向于写和读都是在主节点来进行的从节点主要是用于进行数据复制和同步实现热备份万一主节点挂了此时 作为一个备选才会去从从节点那里读取数据
为什么像上述一样设计下一讲会给大家来进行分析
如果说主节点过于繁忙积压了大量的消息处 理不过来了写和读积压的消息数量超过了他本地内存的40% 这是自己定的一个比例 - 主 节点太繁忙了 - 在你发起一次拉取消息请求之后他会通知你下一次拉取消息的 broker id 是谁
如果又过了一段时间从节点发现自己本地的消息积压量小于自己的物理内存30%说明拉取很顺利很快速此时从节点又会给消费者拉取线程返回一个brokerId0也就是说让消费者又漂移回主节点进行消息拉取 025_RocketMQ为什么采取惰性读写分离模式
读写分离主从漂移模式。赛车一般是玩漂移我们也是一个 专业 的技术术语 ip漂移主从机器对外提供一个完整的服务你在访问的时候 有的时候访问主有的时候访问从此时主从之 间的给你访问的 ip 漂移 一会儿漂移到主 一会儿漂移到从
彻底的读写分离对于从节点的数据读取备用备选什么时候会在从节点进行消费和读取呢 如果说主节点过于繁忙了积压的没消费的消息太多了都占比达到了自己物理内存 的 40% 了 cpu 负载可能很高大量的读写线程并发运行 机器运行效率可能都降低了来不及处理这么多的 请 求了
消费请求漂移到从节点去出现了一个请求漂移
主节点如果崩溃了毫无疑问的只能从从节点去进行消费了
在从节点消费的非常好消息的积压数量很快就下降到了从节点物理内存的30% 占比以内 就说 明你此时消费一切良好又会让你漂移回主节点去了惰性读写分离懒惰、不情愿的、不主动 的、偶尔的才会让你去从节点进行消费
rocketmq作为一个 mq还算是支持了可以从从 节点去进行消费和读取kafka 也是一个 mq topic - 不是很多queue 而是很多的partition 不同的节点组成了 leader 和 follower 主从结构去进行数据复制 默认也是不会让你去从节点去 读取
MQ 作为一个特殊的中间件系统他要维护你的每个 consumer对一个queue的消费进度
如果有一主三从的broker集群如果消费者一会儿去主读一会儿又去三个从里面各自读取一会儿因为此时没有统一的一个消费进度管理的位置消费进度都各自为政就很不好办。像这种broker集群的消费进度一定要维护到一个统一的地方进行集中式的存储跟管理突然想起zk就是一个元数据集中式存储与管理好组件
一般从会每隔10s把自己当前的消费进度等元数据信息同步给主一次以便当发生了主从读写分离后主能够同步的更新当前的消费进度
就是因为消费进度不好统一管理的问题所以RocketMQ没有选择一上来就主张读写分离 Broker高可用
026_Broker数据与服务是否都实现高可用了
RocketMQ 4.5.0之前主节点崩溃之后是没有高可用的主从切换机制的主从机制在 4.5.0 之前仅仅是用来进行热备份的让主节点数据在从节 点也有一份主节点崩了之后这个时候就全靠 从节点提供有限的数据和服务了
服务的高可用broker提供的服务就两个一个是写、一个是 读 写数据读数据。
从节点就是从节点是不能写入数据的只能作 为一个备胎一样的感觉偶尔主节点实在是忙不 过来或者是挂掉的时候从节点可以顶上
主从漂移 惰性读写分离机制 如果主节点崩溃 了以后producer 就会全部是写失败了 但是consumer 任可以继续去找从节点进行消费从节 点之前同步到了多少数据就只能提供这些数据的继续消费行为
rocketmq 按照上面这一套实现原理 实现broker的服务高可用了吗 数据和服务都没实现高可用
服务高可用这块就只能提供有限的读写是不行了 不能写就导致没新的数据进来已有的数据消费完了也就完了读也只是有限的读
数据高可用数据可能有些最新数据都没来得及同步给从从而从的数据也不是完整的只能说 是大部分的数据不丢失而已主从同步实现了热备份、热同步主节点全崩的时候 从节点保留了大部分的数据
rocketmq 在 4.5.0 之后做一个最新的架构改造主从同步 主从切 换 高可用机制 数据 和服务 都是高可用 027_Broker 数据与服务高可用的理想化设计
rocketmq以前老版本的单纯的主从复制实现 了数据不丢效果不是特别好你的一个数据分片也就是一个broker集群broker主节点挂掉了以后从节点都没法接管主节点的工作
理想情况下应该是一个什么样的设计呢
主的数据写入后先直接进page cache 就可以了但是主从同步如果你要实现主从数据强一致的同步。
如果采用 pull 模式必然会导致主必 须去等待从过来 pull 拉取数据时效性一般来说 比较差 。主从数据强一致写入主节点主节点直接进 page cache 就算成功了到此为止写内存速度还是很快的
此时最好是采取一个push的模式 主直接把你的这条数据push 同步给从节点此时客户端的写请求只要等待一次从节点的push成功就可以了 。对于从节点同样也是采取了异步刷 盘的策略从收到了一条消息之后直接也是写入 page cahce 就可以返回这时就说本次push 同步成功了 ps
主从同步同步调用从节点如果网络问题导致失败主节点会返回生产者失败当大多数节点不满足的时候会告诉生产者失败 028_Broker基于raft协议的主从架构设计
Raft协议使用过半机制和选举周期来避免脑裂 Zab协议使用过半机制和全局唯一的Zxid来避免脑裂 场景如果主有123455条消息两台从从1只有消息1234从2只有消息125这时主挂了从1成为新的主消费者此时找从1拿消息不就拿不到消息5了吗 主往从1同步消息3 4成功同步消息5失败 主往从2同步消息 3 4失败同步消息5成功
管理员(18分钟前): 同学你好首先RocketMQ 的主从架构是为了实现消息的高可用性和容错性而设计的。当主节点挂掉后会从从节点中选举出新的主节点确保消息服务的持续可用。然而在主从切换的过程中可能会出现消息同步不一致的情况这取决于各个节点的同步进度和状态。 在同学你提到的情况中主节点有消息 1-5从节点1有消息 1-4从节点2有消息 1、2、5。如果主节点挂掉从节点1成为新的主节点消费者此时从从节点1拉取消息将只能拉取到消息 1-4而无法获取消息 5。 解决这个问题的方法通常是依赖于 RocketMQ 的消息复制机制和同步策略 消息复制机制 RocketMQ 默认的消息复制机制是支持多副本的。在这种情况下消息被存储在多个节点上确保了消息的冗余性和可靠性。当主节点切换时新的主节点会尽力同步副本使得各个副本最终保持一致。但是在切换过程中可能会存在少量的不一致性。 同步策略 RocketMQ 提供了同步策略来控制消息的同步行为。这些策略包括同步刷盘、同步主从等。你可以调整同步策略的参数以权衡消息的可靠性和性能。 为了尽量避免消息同步不一致的情况你可以考虑以下几点 1: 配置适当的消息复制因子使得主从节点数目足够即使在切换过程中也能够保持足够的消息冗余。 2: 配置合适的同步策略根据你的业务需求来平衡消息的可靠性和性能。 3: 在设计应用程序时考虑处理消息重复和消息丢失的情况以保证数据的完整性和一致性。 问题1也就是说从1在切换为新主后第一件会做的事情是去其他从拉取消息来让各方的数据对齐
问题2配置适当的同步因子我理解就是增加从机器的数量 从机器越多主往从需要同步的也就越多同步给某一台从或某几台从失败的概率不就更增大了
问题3配合合适的同步策略使用了raft协议高可用后不是默认同步n/2 1台机器就返回写入成功了吗难道n/2 1这个比例还能更改吗
管理员(1小时前):
问题1是的当从节点1成为新的主节点时它会尝试与其他从节点进行消息同步以使各方的数据尽可能对齐。这个过程可能涉及消息的复制、同步和确认以确保消息的冗余性和一致性。
问题2配置适当的消息复制因子并不是指增加从机器的数量。消息复制因子是指每条消息在集群中被复制的次数也就是消息副本数。默认情况下RocketMQ 会将消息复制到多个节点上以提供冗余和可靠性。增加消息复制因子会增加磁盘和网络负载但可以提高数据的冗余性。从机器的数量与消息复制因子不完全一致你可以根据业务需求和性能考虑来调整消息复制因子。
问题3RocketMQ 使用 Raft 协议来实现高可用性的主从架构确实默认情况下需要将消息同步到大多数节点才会返回写入成功。在一个有 n 个节点的集群中写入成功需要同步到 n/2 1 个节点。这是为了确保在大多数节点可用的情况下数据的一致性和可靠性。 029_raft 协议的leader选举算法介绍
raft 协议就是一套针对分布式系统多台机器进 行Leader 选举 主从同步复制 主从切换定义的一套算法和方法论
具体思想怎么实现的要看开源的 raft 框架或者是自己动手实现一套 raft 协议算法
具体原理是
每个follower都会给自己设置一个1 50ms~300ms之间的随机的倒计时时间也就是 说有的follower可能会倒计时150ms有的可能是 160ms有的可能是240ms大家的时间一般是不一样的
肯定有一个follower 倒计时时间是最少的他是最先完成倒计的 也就是第一个完成倒计时的follower 此时这个follower就会苏醒过来并把自己的身份转变为candidate成为一个leader候 选人他会开始想要竞选成为一个leader就需要大家一起来给它投票他自己也可以投票他当然会投票给他自己 但这是不够的他还需要得到别人的认可这个很关键 他会发送请求给其他的节点进行拉票的动作他把拉票的请求发送给另外2个还是follower状态的节点他们还在 进行倒计时呢
follower节点此时收到一个拉票的请求之后如果他之前没有给别人投过票此时收到了一个拉票请求就会把自己的票投给你如果他要是之前投票给别人了此时他就拒绝你的拉票。
当前的两个follower他们肯定没有投过票因为 他们之前一直在倒计时这两个follower会把票都投给我们的第一个苏醒过来的candidate
candidate完成拉票之后因为那两个follower都把票投给了他他此时发现自己的得票数已经超 过了半数quorum了n/21此时它就顺理成章成为leader
大多数follower比如说他之前倒计时250ms 此时还有50ms就要倒计时完毕了但是收到了一个人说自己是leader 该follower就会重置自己的倒计时计时器重新从 250ms 开始进行计时这就是leader维持自己的地位的手段只要有它在让两个follower永远没有机会成为candidate 030_Broker基于状态机实现的leader选举
状态机就是状态模式的一个运用
状态设计模式你的系统可以维护多个状态State多个State之间可以进行切换每次切换到一个新的state之后执行的行为是不同的行为是跟state绑定在一起的
状态设计模式 - 状态机 - state machine 就是跟状态设计模式是差不多的可以维护多个state 状态不同的状态可以对应不同的行为
rocketmqbroker在实现leader选举的时候采取的就是一个状态机制来实现的 031_Leader选举状态机实现细节全分析 broker1 2 3
如果1和3同时结束倒计时都像2发出了拉票2把票给了1则1成为leader3重新开始倒计时
如果1 2 3都同时结束倒计时三个人各自都向对面两个人发出了拉票那么此轮就三个人都只有各自给自己投的一票则1 2 3都重新开始倒计时进入下一轮投票 032_Broker基于DLedger的数据写入流程
基于raft协议思想指导的leader选举的机制都已 经搞定了数据是如何进行写入的 raft 协议之下只有 leader 可以对外提供服务对外提供写和读的服务follower是不对外提供服务的他们仅仅是进行数据复制和同步只有在leader故障的 时候follower们才会完成重新的leader选举 以便继续对外提供服务
kafka、zookeeper其实是如出一辙的如果说你要是一边写leader一边从follower去读raft协议 之下过半写成功机制此时如果允许对 follower 发起一个读的话可能会有一个问题可能会读 到那个还没复制成功数据的follower从而导致broker集群对外看到的数据视图是不一致的
为了解决数据写和读的一致的问题kafka、zk、rocketmq raft实现都是写和读针对 leader就可以了写成功了再去读一定是可以读到一致的数据 033_Broker引入DLedger后的存储兼容设计
消费的时候读数据只要 leader 写入成功了此时就可以在leader读取到呢 仅仅是写入 leader的page cache本次写入是并没有宣告成功的还需要等待是否超过半数节点写入成功 此 时写入并没有成功就不应该让你消费的时候读 取到leader写成功的数据这里大家一定要注意这 个点一定要等到半数写成功返回这条消息写入已经成功了做到这一点才可以让在leader上的数据被消费到读取到
消息数据被调整为了dledger 的日志格式 针对这 个日志格式此时应该如何来进行兼容
dledger写入的日志格式的数据写入 commitlog 里面去的是有header 和 body 两个部分 header 这里是包含了很多的 header头字段每个头字段 4 个字节或者几个字节 和长度不固定的 body体
原始的commitlog存储的一条一条的数据会把原始的commitlog里的数据一个数据条目给他放到dledger里面的body里面去header一堆头字段 body commitlog 原始数据 不 就可以了 吗
把dledger数据存储结构和原始的 commitlog 做了一个兼容和整合两块数据就集成到一块儿去了 commitlog 里是每一条数据都有 headerbody 此时你如果准备存入ConsumeQueue的消息索引条目中的消息全局物理偏移量还是从header开始去计算就不对了在ConsumeQueue里放入的每个消息索引条目的offset偏移量是 commitlog 里一条数据的body起始的物理偏移量 034_BrokerController主从元数据同步
元数据、消息写入和读取都是基于broker leader 来进行的
topic路由信息topic在当前broker组里放了几个 queue集群里会有很多的 topic 每个不同的topic都会在当前broker里放他们自己所属的queue 类 似 这样 的 topic 路由数据
消费进度数据各个consumer group对同一个queue的不同消费进度数据此时都是存储在主节点上的 需要从节点起一个定时任务每隔60s去主节点拉取一次
从节点的BrokerController#initiallize()方法内会通过延时单线程线程池起一个每60s执行一次的定时任务定时任务中会判断broker是Master则打印一下主从的diff如果是slave就会执行SlaveSynchronize#syncAll()方法来同步四种数据Topic数据就是当前topic在当前master broker上有几个队列之类的信息因为一个topic的queue可能分布在多台不同的master broker上、消费进度信息比如TEST_01_TOPIC这个topic在当前master broker中有4个queue那么同步给从节点的消费进度信息可能就是 TEST_01_TOPICtrade_system_consumer_group0:1371:1282:1453:115 。 这样的不同消费组对于同一个topic下四个不同队列的消费进度
从节点起一个定时任务去主节点拉取四种不同类型的元数据也就意味着从节点会通过netty向主节点发送四次request_code不同的请求主节点侧会有一个请求入口统一接收这四种request_code不同的请求每个request_code都对应有一套不同的逻辑处理然后分发路由给不同的逻辑处理。四种不同类型的数据就对应着主节点这边有四种不同类型的XxxxxManager的数据管理器来管理着这四种不同类型的数据
从节点拉取到主节点返回的元数据以后比如拉取到返回的topic信息后首先要做的就是比对返回的数据的DataVersion与自己本地缓存的数据版本是否一致如果不一致才会执行更新本地缓存数据 Consumer端
036_Consumer端队列负载均衡分配机制
topic是有一堆的queue且分布在不同的broker上的 consumer group 是有多个 consumer 要把多个queue 分配给多个consumer每个 consumer都会分配到一部分的queue
这个queue-consumer的分配关系谁来负责负责分配的角色他就可以根据一定的算法把 queue分配个我们的consumer topic里的有多少queue信息从哪里获取 如何知道一个consumer group 里到底有多少个 consumer
每个consumer都会去获取到 topic有多少个queue同时还会去获取consumer group里有多少个 consumer 然后每个consumer自己都会按照相同的算法去做一次分配
每个Consumer会向所有的broker进行注册从而每个broker就都知道一个consumer group的所有 consumer有哪些
RebalanceService 拉取 topic queue 信息 拉取 consumer group 根据算法分配 queue 确 认 自己要拉取哪些 queue 平均分配算法、 轮询 分配算法、 一致性 hash 、 配置化、 机房分配 037_Consumer 消息拉取的挂起机制分析
q1 q2 q3 q4 q5 q6 q7 q8 两个broker 组8个queue2个consumer完成了queue-consumer的分配之后当前的 consumer也就知道自己应该负责的queue是哪些了
如果没有开启 consumer 拉取消息的long polling 长轮询机制默认情况下就是 short polling 机制
短轮询机制默认情况下他会挂起 1s 通过shortPollingMillis 参数可以去进行控制挂起时长如果过来未拉取到消息也挂起1s后再次检查是否有满足条件的消息此时不管有没有消息都会返回 如果开启了long polling 长轮询机制 此时又分push和pull两种情况
push模式会挂起 每隔 5s 检查 一直到 15s 都没有拉到消息此时才会超时返回 长轮询
pull模式挂起超时时间20s
不同模式的参数控制的时间不太一 样
PS:
关于push模式的长轮询挂起 每隔 5s 检查分别是两个不同的线程当消费者消息拉取线程来到broker后发现没有符合条件的消息则会进去挂起状态然后broker端会另外起一个后台轮询线程每隔 5s 检查一次有没有符合条件的消息如果有则唤醒前面挂起的消费者消息拉取线程、如果没有则继续等待下一个5s 038_Consumer的处理队列映射与并发消费
messageQueue和processQueue是一对一对应的关系当前consumer分配到消费4个messageQueue也自己本地内存中就会建立4个一一对应的processQueue
MessageQueue是一个虚拟的概念。在Broker中一个topic下有许多个MessageQueue每个MessageQueue都会有一系列的ConsumeQueue文件这个ConsumeQueue文件存储的是一条消息索引条目中存储的原始消息对应在CommitLog文件中的offset偏移量即一个全局物理位置
从MessageQueue拉回来的消息会存储到本地的processQueue中本地消费成功则会将该条消息从processQueue中中删除 039_Consumer处理成功后的消费进度管理
两级异步刷写线程才能让消费端传过来给到broker的消费进度刷入broker的磁盘processQueue中删除并把该消息对应的位点写入consumer本地的内存进度管理此时异步线程还未来得及把该进度刷给broker的内存端consumer就宕机了此时就会引起重复消费 040_Consumer消息重复消费原理剖析
consumer 消息重复消费是一个可能很经常的一个事情 mq consumer都要实现一套严格的分布式锁和幂等性检查保障的机制和体系
消费进度的两级刷盘机制如果消费端的listener刚成功处理一条消息返回SUCCESS把该消息从 consumer重复消费具体代码怎么落地呢分布式锁是使用redisson?
管理员(2022-8-5): 是的,一般上游消息里面有一个messageid,然后证据这个id做消息的幂等,或者通过 redis redisson做分布式锁来保证幂等(重复消费) 041_Consumer处理失败时的延迟消费机制
处理失败的消息或者处理过程中抛出异常并没有捕获的场景会返回一个RECONSUME_LATER此时也会删除processQueue中对应的该条消息并且此时也会给broker返回一个ack此时broker会做几个动作
会将原消息整体包装起来外层换成RETRY_XXX_TOPIC然后把封装后的消息写入commitlog中并且此时会有18个延迟队列分别对应不同的延迟等级首先把包装后的消息丢入第一级延迟队列每一级延迟队列都对应有一个定时任务扫描发现自己负责的队列中有消息到达延迟时间后就把包装消息从commitlog中取出来然后换成原始的业务topic成为原始消息此时把这个恢复出来的原始消息又丢进commitlog并会重新被reput到ConsumeQueue中最后消费者消息拉取线程就又可以把这条消息从broker的ConsumeQueue中拉回到本地processQueue中进行针对该条消息的重试消费 042_Consumer Group变动时的重负载机制
consumer group内增减consumer 都会引起重平衡
比如当前就一台broker内部有4个queue有一个consumer group内部有2个consumer按照平均分配算法consumer1分配queue1和2consumer2分配queue3和4当consumer1挂掉后consumer2就会去接管queue1和2也就是说consumer2的消息拉取线程会同时负责拉取4个队列的消息同时因为该台broker内部保存了queue1和2当前的消费进度所以consumer2可以接着这个进度往后进行消费
从这里就能看出重平衡可能引起重复消费consumer1消费了10条queue1的消息但是还未将消费进度同步给broker就挂了引起重平衡后续consumer2又会再次消费这10条queue1的消息