全面剖析RocketMQ基础概念,并分析一下Producer的底层源码

答案是ConsumeQueue,Broker在写入Commit Log的同时,还会将当前这条消息在Commit Log中的Offset、消息的Size和对应的Tag的Hash写入到ConsumeQueue文件中。每个Message Queue会有相对应的ConsumeQueue文件存储在磁盘上。

和Commit Log一样,一个ConsumeQueue包含了30W条消息,每条消息的大小为20字节,所以每个ConsumeQueue文件的大小约为5.72M;当其写满了之后,会再新建一个ConsumeQueue文件继续写入。

ConsumeQueue是一种逻辑队列,更是一种索引,让Consumer来消费的时候可以快速的从磁盘文件中定位到这条消息。

看到这你可能会想,上面提到的Tag又是个什么东西?

Tag

Tag,标签,用于对同一个Topic内的消息进行分类,为什么还需要对Topic进行消息类型划分呢?

举一个极端的例子,某一个新的服务,需要去消费订单系统的MQ,但是由于业务的特殊性,只需要去消费商品类型为数码产品的订单消息,如果没有Tag,那么该Consumer就会去做判断,该订单消息是否是数码产品类,如果不是,则丢弃,如果是则进行消费。

这样一来,Consumer侧就执行了大量的无用功。引入了Tag之后,Producer在生产消息的时候会给订单打上Tag,Consumer进行消费的时候,可以配置只消费指定的Tag的消息。这样一来就不需要Consumer自己去做这个事情了,RocketMQ会帮我们实现这个过滤。

那其过滤的原理是什么?首先在Broker侧是通过消息中保存的Tag的Hash值进行过滤,然后Consumer侧在去拉取消息的时候还需要再过滤一次。

为什么在Broker过滤了,还需要在Consumer侧再过滤一次?因为Hash冲突,不同的Tag经过Hash算法之后可能会得到一样的值,所以Consumer侧在拉取消息的时候会通过字符串进行二次过滤。

Producer发送消息源码分析

流程总览

首先给出整个发送消息的大致流程,先熟悉这个流程看源码,会更加的清晰一点。

总体流程

初始化Prodcuer

还是按照下面这个例子出发。

producer使用样例

首先我们会初始化一个DefaultMQProducer,RocketMQ会给这个Producer一个默认的实现DefaultMQProducerImpl。然后producer.start()会启动一个线程池。

合法性校验

接下来就是比较核心的producer.send(msg)了,首先RocketMQ会调用checkMessage来检测发送的消息是否合法。

send消息

这些检测包含了待发送的消息是否为空,Topic是否为空、Topic是否包含了非法的字符串、Topic的长度是否超过了最大限制127,然后会去检查Body是否符合发送要求,例如msg的Body是否为空、msg的Body是否超过了最大的限制等等,这里消息的Body最大不能超过4M。

检查消息合法性源码

调用发送消息

对于msg的Topic,RocketMQ会用NameSpace将其包装一层,然后就会调用DefaultMQProducerImpl中的sendDefaultImpl默认实现,发送消息给Broker,默认的发送消息Timeout是3秒。

发送消息默认实现

发送消息中,MQ会再次调用checkMessage对消息的合法性再次进行检查,然后就会去尝试获取Topic的详细信息。

所有的Topic的信息都会存在一个叫topicPublishInfoTable的 ConcurrentHashMap中,这个Map中Key就是Topic的字符串,而Value则是TopicPublishInfo

这个TopicPublishInfo中就包含了之前在基础概念中提到的,从Broker中获取到的相应的元数据,其中就包含了关键的MessageQueue和集群元数据,其基础的结构如下。

Topic详情

messageQueueList包含了该Topic下的所有的MessageQueue,每个MessageQueue的所属Topic,每个MessageQueue所在的Broker的名称以及专属的queueId。

topicRouteData包含了该Topic下的所有的Queue、Broker相关的数据。

获取Topic详细数据

在最终发送消息前,需要获取到Topic的详情,例如像Broker地址这样的数据,Producer中是通过tryToFindTopicPublishInfo方法获取的,详细的注释我已经写在了下图中。

获取topic详情

对于首次使用的Topic,在上面的Map肯定是不存在的。所以RocketMQ会将其加入到Map中去,并且调用方法updateTopicRouteInfoFromNameServer从NameServer处获取该Topic的元数据,将其一并写入Map。初次之外,还会将路由信息、Broker的详细信息分别放入topicRouteTablebrokerAddrTable中,这两个都是Producer维护在内存中的ConcurrentHashMap。

获取到了Topic的详细信息之后,接下来会确认一个发送的重试次数timesTotal,假设timesTotal为N,那么发送消息如果失败就会重试N次。不过当且仅当发送失败的时候才会进行重试,其余的case都不会,例如超时、或者没有选择到合适的MessageQueue。

这个重试的次数timesTotal受到参数communicationMode的影响;CommunicationMode有三个值,分别是SYNCASYNCONEWAY。RocketMQ默认的实现中,选择了SYNC同步。

计算重试次数

通过代码我们可以看到,如果是communicationModeSYNC的话,timesTotal的值为1+retryTimesWhenSendFailed,而retryTimesWhenSendFailed的值默认为2,代表在消息发送失败之后的重试次数。

这样一来,如果我们选择了SYNC的方式,Producer在发送消息的时候默认的重试次数就为3。不过当且仅当发送失败的时候才会进行重试,其余的case都不会。

MessageQueue选择机制

我们之前聊过,一个Topic的数据是分片存储在一个或者多个Broker上的,底层的存储介质为MessageQueue,之前的图中,我们没有给出Producer是如何选择具体发送到哪个MessageQueue,这里我们通过源码来看一下。

Producer中是通过selectOneMessageQueue来进行的Message Queue选择,该方法通过Topic的详细元数据和上次选择的MessageQueue所在的Broker,来决定下一个的选择。

核心的选择逻辑

核心的选择逻辑是什么呢?用大白话来说,就是选出一个index,然后将其和当前Topic的MessageQueue数量取模。这个index在首次选择的时候,肯定是没有的, RocketMQ会搞一个随机数出来。然后在该值的基础上+1,因为为了通用,在外层看来,这个index上次已经用过了,所以每次获取你都直接帮我+1就好了。

核心的选择机制

上图就是MessageQueue最核心的、最底层的原则机制了。但是由于实际的业务情况十分复杂, RocketMQ在实现中还额外的做了很多的事情。

发送故障延迟下的选择逻辑

在实际的选择过程中,会判断当前是否启用了发送延迟故障,这个由变量sendLatencyFaultEnable的值决定,其默认值是false,也就是默认是不开启的,从代码里我暂时没找到其开启的位置。

不过我们可以聊聊开启之后,会发生什么。它同样会开启for循环,次数为MessageQueue的数量,计算拿到确定的Queue之后,会通过内存的一张表faultItemTable去判断当前这个Broker是否可用,该表是每次发送消息的时候都会去更新它。

如果当前没有可用的Broker,则会触发其兜底的逻辑,再选择一个MessageQueue出来。

选择queue的源码

常规的选择逻辑

如果当前发送故障延迟没有启用,则会走常规逻辑,同样的会去for循环计算,循环中取到了MessageQueue之后会去判断是否和上次选择的MessageQueue属于同一个Broker,如果是同一个Broker,则会重新选择,直到选择到不属于同一个Broker的MessageQueue,或者直到循环结束。这也是为了将消息均匀的分发存储,防止数据倾斜。

常规逻辑下的选择逻辑

消息发送

最后就会调用Netty相关的组件,将消息发送出去了。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后:学习总结——MyBtis知识脑图(纯手绘xmind文档)

学完之后,若是想验收效果如何,其实最好的方法就是可自己去总结一下。比如我就会在学习完一个东西之后自己去手绘一份xmind文件的知识梳理大纲脑图,这样也可方便后续的复习,且都是自己的理解,相信随便瞟几眼就能迅速过完整个知识,脑补回来。下方即为我手绘的MyBtis知识脑图,由于是xmind文件,不好上传,所以小编将其以图片形式导出来传在此处,细节方面不是特别清晰。但可给感兴趣的朋友提供完整的MyBtis知识脑图原件(包括上方的面试解析xmind文档)

image

除此之外,前文所提及的Alibaba珍藏版mybatis手写文档以及一本小小的MyBatis源码分析文档——《MyBatis源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
is源码分析》等等相关的学习笔记文档,也皆可分享给认可的朋友!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值