架构原理
RocketMQ 整体架构流程简述(生产者→NameServer→Broker→消费者)
每个Broker在创建好后会向所有NameServer汇报自己的Topic路由信息即 Broker地址、Topic配置、都写队列路由信息,每个NameServer都会以Topic为分类整理Broker汇报的路由信息。
客户端(Productor/Consumer)会定时从NameServer获取路由信息来更新本地Topic下的Broker信息;如果某一个Broker访问失败,则会重新查询NameServer更新路由信息。以上整个链路搭建完成。
Productor根据路由Topic信息选择一个WriteMessageQueue,将消息发送到对应Broker,Broker收到消息后写入CommitLog,再根据CommitLog构键ConsumerQueue和IndexFile,完成持久化。
ConsumerGroup下的每个Consumer会根据Rebalance分配到若干个ReadMessageQueue,从Broker拉取消息进行消费。消费成功后,向Broker提交消费结构更新消费点位,确认该消息为正常消费。若消费失败则会重试,每一次重试的间隔时间都会更长,直到16次重试机会全部耗尽,则会加入死信队列(DLQ),待人工处理后补偿消费。
NameServer 为什么设计成无状态?
RocketMQ的NameServer采用无状态而非Raft、Paxos、Zookeeper那样的主从选举,是因为NameServer只是缓存Broker的信息,而Broker真正的信息在他们各自的手中,所以RocketMQ没有采用中心化思想,要求所有NameServer都要确保Broker数据的强一致性,而是分为主动和被动两种方案尽可能维护NameServer中缓存的Broker信息的正确性。
主动:Broker每间隔30秒(默认)向所有NameServer发送 路由注册 + 心跳请求 来更新NameServer中缓存的Broker信息。而客户端(Productor/Consumer)并不是一直使用首次从某一NameServer查询到的Broker信息一直使用,默认10秒会清空客户端的Broker缓存信息,重新轮询查询其他NameServer来更新客户端的Broker缓存信息。
被动:Productor/Consumer在与NameServer中查询到的Broker进行连接发送消息时,若是出现了失败,Productor/Consumer还是会轮询其他NameServer查询Broker信息更新到本地客户端,至于先前缓存不一致的NameServer则会由 30秒间隔的 路由注册 + 心跳请求来更新。
RocketMQ权衡了引入Raft、Paxos、Zookeeper带来的复杂度 和 NameServer最终一致性的设计会造成的短时间内Broker使用不均衡 ,最后仍然决定使用后者,认为短时间内的Broker使用不均衡在后续NamerServer更新完缓存的Broker信息,最终仍然可以较为均衡的使用所有Broker,而额外算法的引入则会大大增加维护和开发难度。
Broker 如何同步 Topic 路由信息?
RokcetMQ不会同步路由消息,而是每个Broker维护自身的Topic配置,并在启动及运行期间通过registerBroker定期向所有NameServer上报自己的Topic配置信息,再由各个NameServer统一汇总整理每个Topic下的Broker。后续客户端即可定时从NameServer查询指定Topic路由消息并且缓存在客户端本地。
RocketMQ 队列机制,Topic 默认多少队列?读写队列区别?
Productor发布的消息并不是直接发送到Topic下。而是Topic下的MessageQueue,一个Topic默认创建4个MessageQueue,分别为4个读队列和4个写队列,即一个队列分别有读写功能,而非8个队列,4个读队列和4个写队列。Productor发布消息到指定Topic->Broker下的写队列,Consumer读取指定Topic->Broker下的读队列消息进行消费。
若指定创建8个写队列4个读队列,则会创建8个队列,着8个队列全都有写功能,但只有其中4个有读功能。即 QueueNums=max(readQueueNums.writeQueueNums).