源码分析RocketMQ消息轨迹

}

从上述代码可以看出其关键点是在创建DefaultMQProducer时指定开启消息轨迹跟踪。我们不妨浏览一下DefaultMQProducer与启用消息轨迹相关的构造函数:

public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace)

public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic)

参数如下:

  • String producerGroup

生产者所属组名。

  • boolean enableMsgTrace

是否开启跟踪消息轨迹,默认为false。

  • String customizedTraceTopic

如果开启消息轨迹跟踪,用来存储消息轨迹数据所属的主题名称,默认为:RMQ_SYS_TRACE_TOPIC。

1.1 DefaultMQProducer构造函数

public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,final String customizedTraceTopic) { // @1

this.producerGroup = producerGroup;

defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);

//if client open the message trace feature

if (enableMsgTrace) { // @2

try {

AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);

dispatcher.setHostProducer(this.getDefaultMQProducerImpl());

traceDispatcher = dispatcher;

this.getDefaultMQProducerImpl().registerSendMessageHook(

new SendMessageTraceHookImpl(traceDispatcher)); // @3

} catch (Throwable e) {

log.error(“system mqtrace hook init failed ,maybe can’t send msg trace data”);

}

}

}

代码@1:首先介绍一下其局部变量。

  • String producerGroup

生产者所属组。

  • RPCHook rpcHook

生产者发送钩子函数。

  • boolean enableMsgTrace

是否开启消息轨迹跟踪。

  • String customizedTraceTopic

定制用于存储消息轨迹的数据。

代码@2:用来构建AsyncTraceDispatcher,看其名:异步转发消息轨迹数据,稍后重点关注。

代码@3:构建SendMessageTraceHookImpl对象,并使用AsyncTraceDispatcher用来异步转发。

1.2 SendMessageTraceHookImpl钩子函数

1.2.1 SendMessageTraceHookImpl类图

在这里插入图片描述

  1. SendMessageHook

消息发送钩子函数,用于在消息发送之前、发送之后执行一定的业务逻辑,是记录消息轨迹的最佳扩展点。

  1. TraceDispatcher

消息轨迹转发处理器,其默认实现类AsyncTraceDispatcher,异步实现消息轨迹数据的发送。下面对其属性做一个简单的介绍:

  • int queueSize

异步转发,队列长度,默认为2048,当前版本不能修改。

  • int batchSize

批量消息条数,消息轨迹一次消息发送请求包含的数据条数,默认为100,当前版本不能修改。

  • int maxMsgSize

消息轨迹一次发送的最大消息大小,默认为128K,当前版本不能修改。

  • DefaultMQProducer traceProducer

用来发送消息轨迹的消息发送者。

  • ThreadPoolExecutor traceExecuter

线程池,用来异步执行消息发送。

  • AtomicLong discardCount

记录丢弃的消息个数。

  • Thread worker

woker线程,主要负责从追加队列中获取一批待发送的消息轨迹数据,提交到线程池中执行。

  • ArrayBlockingQueue< TraceContext> traceContextQueue

消息轨迹TraceContext队列,用来存放待发送到服务端的消息。

  • ArrayBlockingQueue< Runnable> appenderQueue

线程池内部队列,默认长度1024。

  • DefaultMQPushConsumerImpl hostConsumer

消费者信息,记录消息消费时的轨迹信息。

  • String traceTopicName

用于跟踪消息轨迹的topic名称。

1.2.2 源码分析SendMessageTraceHookImpl
1.2.2.1 sendMessageBefore

public void sendMessageBefore(SendMessageContext context) {

//if it is message trace data,then it doesn’t recorded

if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName())) { // @1

return;

}

//build the context content of TuxeTraceContext

TraceContext tuxeContext = new TraceContext();

tuxeContext.setTraceBeans(new ArrayList(1));

context.setMqTraceContext(tuxeContext);

tuxeContext.setTraceType(TraceType.Pub);

tuxeContext.setGroupName(context.getProducerGroup()); // @2

//build the data bean object of message trace

TraceBean traceBean = new TraceBean(); // @3

traceBean.setTopic(context.getMessage().getTopic());

traceBean.setTags(context.getMessage().getTags());

traceBean.setKeys(context.getMessage().getKeys());

traceBean.setStoreHost(context.getBrokerAddr());

traceBean.setBodyLength(context.getMessage().getBody().length);

traceBean.setMsgType(context.getMsgType());

tuxeContext.getTraceBeans().add(traceBean);

}

代码@1:如果topic主题为消息轨迹的Topic,直接返回。

代码@2:在消息发送上下文中,设置用来跟踪消息轨迹的上下环境,里面主要包含一个TraceBean集合、追踪类型(TraceType.Pub)与生产者所属的组。

代码@3:构建一条跟踪消息,用TraceBean来表示,记录原消息的topic、tags、keys、发送到broker地址、消息体长度等消息。

从上文看出,sendMessageBefore主要的用途就是在消息发送的时候,先准备一部分消息跟踪日志,存储在发送上下文环境中,此时并不会发送消息轨迹数据。

1.2.2.2 sendMessageAfter

public void sendMessageAfter(SendMessageContext context) {

//if it is message trace data,then it doesn’t recorded

if (context == null || context.getMessage().getTopic().startsWith(((AsyncTraceDispatcher) localDispatcher).getTraceTopicName()) // @1

|| context.getMqTraceContext() == null) {

return;

}

if (context.getSendResult() == null) {

return;

}

if (context.getSendResult().getRegionId() == null

|| !context.getSendResult().isTraceOn()) {

// if switch is false,skip it

return;

}

TraceContext tuxeContext = (TraceContext) context.getMqTraceContext();

TraceBean traceBean = tuxeContext.getTraceBeans().get(0); // @2

int costTime = (int) ((System.currentTimeMillis() - tuxeContext.getTimeStamp()) / tuxeContext.getTraceBeans().size()); // @3

tuxeContext.setCostTime(costTime); // @4

if (context.getSendResult().getSendStatus().equals(SendStatus.SEND_OK)) {

tuxeContext.setSuccess(true);

} else {

tuxeContext.setSuccess(false);

}

tuxeContext.setRegionId(context.getSendResult().getRegionId());

traceBean.setMsgId(context.getSendResult().getMsgId());

traceBean.setOffsetMsgId(context.getSendResult().getOffsetMsgId());

traceBean.setStoreTime(tuxeContext.getTimeStamp() + costTime / 2);

localDispatcher.append(tuxeContext); // @5

}

代码@1:如果topic主题为消息轨迹的Topic,直接返回。

代码@2:从MqTraceContext中获取跟踪的TraceBean,虽然设计成List结构体,但在消息发送场景,这里的数据永远只有一条,及时是批量发送也不例外。

代码@3:获取消息发送到收到响应结果的耗时。

代码@4:设置costTime(耗时)、success(是否发送成功)、regionId(发送到broker所在的分区)、msgId(消息ID,全局唯一)、offsetMsgId(消息物理偏移量,如果是批量消息,则是最后一条消息的物理偏移量)、storeTime,这里使用的是(客户端发送时间 + 二分之一的耗时)来表示消息的存储时间,这里是一个估值。

代码@5:将需要跟踪的信息通过TraceDispatcher转发到Broker服务器。其代码如下:

public boolean append(final Object ctx) {

boolean result = traceContextQueue.offer((TraceContext) ctx);

if (!result) {

log.info(“buffer full” + discardCount.incrementAndGet() + " ,context is " + ctx);

}

return result;

}

这里一个非常关键的点是offer方法的使用,当队列无法容纳新的元素时会立即返回false,并不会阻塞。

接下来将目光转向TraceDispatcher的实现。

1.3 TraceDispatcher实现原理

TraceDispatcher,用于客户端消息轨迹数据转发到Broker,其默认实现类:AsyncTraceDispatcher。

1.3.1 TraceDispatcher构造函数

public AsyncTraceDispatcher(String traceTopicName, RPCHook rpcHook) throws MQClientException {

// queueSize is greater than or equal to the n power of 2 of value

this.queueSize = 2048;

this.batchSize = 100;

this.maxMsgSize = 128000;

this.discardCount = new AtomicLong(0L);

this.traceContextQueue = new ArrayBlockingQueue(1024);

this.appenderQueue = new ArrayBlockingQueue(queueSize);

if (!UtilAll.isBlank(traceTopicName)) {

this.traceTopicName = traceTopicName;

} else {

this.traceTopicName = MixAll.RMQ_SYS_TRACE_TOPIC;

} // @1

this.traceExecuter = new ThreadPoolExecutor(// :

10, //

20, //

1000 * 60, //

TimeUnit.MILLISECONDS, //

this.appenderQueue, //

new ThreadFactoryImpl(“MQTraceSendThread_”));

traceProducer = getAndCreateTraceProducer(rpcHook); // @2

}

代码@1:初始化核心属性,该版本这些值都是“固化”的,用户无法修改。

  • queueSize

队列长度,默认为2048,异步线程池能够积压的消息轨迹数量。

  • batchSize

一次向Broker批量发送的消息条数,默认为100.

  • maxMsgSize

向Broker汇报消息轨迹时,消息体的总大小不能超过该值,默认为128k。

  • discardCount

整个运行过程中,丢弃的消息轨迹数据,这里要说明一点的是,如果消息TPS发送过大,异步转发线程处理不过来时,会主动丢弃消息轨迹数据。

  • traceContextQueue

traceContext积压队列,客户端(消息发送、消息消费者)在收到处理结果后,将消息轨迹提交到噶队列中,则会立即返回。

  • appenderQueue

提交到Broker线程池中队列。

  • traceTopicName

用于接收消息轨迹的Topic,默认为RMQ_SYS_TRANS_HALF_TOPIC。

  • traceExecuter

用于发送到Broker服务的异步线程池,核心线程数默认为10,最大线程池为20,队列堆积长度2048,线程名称:MQTraceSendThread_。、

  • traceProducer

发送消息轨迹的Producer。

代码@2:调用getAndCreateTraceProducer方法创建用于发送消息轨迹的Producer(消息发送者),下面详细介绍一下其实现。

1.3.2 getAndCreateTraceProducer详解

private DefaultMQProducer getAndCreateTraceProducer(RPCHook rpcHook) {

DefaultMQProducer traceProducerInstance = this.traceProducer;

if (traceProducerInstance == null) { //@1

traceProducerInstance = new DefaultMQProducer(rpcHook);

traceProducerInstance.setProducerGroup(TraceConstants.GROUP_NAME);

traceProducerInstance.setSendMsgTimeout(5000);

traceProducerInstance.setVipChannelEnabled(false);

// The max size of message is 128K

traceProducerInstance.setMaxMessageSize(maxMsgSize - 10 * 1000);

}

return traceProducerInstance;

}

代码@1:如果还未建立发送者,则创建用于发送消息轨迹的消息发送者,其GroupName为:_INNER_TRACE_PRODUCER,消息发送超时时间5s,最大允许发送消息大小118K。

1.3.3 start

public void start(String nameSrvAddr) throws MQClientException {

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

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

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

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

img

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

中高级Java开发面试高频考点题笔记300道.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

架构进阶面试专题及架构学习笔记脑图

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
g" style=“zoom: 33%;” />

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

[外链图片转存中…(img-sv1KssI1-1712486919140)]

中高级Java开发面试高频考点题笔记300道.pdf

[外链图片转存中…(img-JFxOyzL0-1712486919140)]

架构进阶面试专题及架构学习笔记脑图

[外链图片转存中…(img-FIqWxxGc-1712486919141)]

Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值