开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情
前言
通过上一篇文章「日拱一卒」带你探索RocketMQ消息发送(二),我们主要学习了 RocketMQ 的消息发送重试机制、故障规避机制来保障消息发送的高可用;再简单看了下消息实体类 Message。今天,我们来承接上一篇文章,来开始学习下 RocketMQ 消息发送的源码吧!
RocketMQ 消息结构与构造方法
RocketMQ 消息实体类是 Message,它位于 org.apache.rocketmq.common.message 包之下,主要具备的属性有:
- String topic : MQ 消息所属的主题
- int flag:消息标记
- Map properties:扩展属性
- byte[] body: 消息体
public class Message implements Serializable {
private static final long serialVersionUID = 8445773977080406428L;
private String topic;
private int flag;
private Map<String, String> properties;
private byte[] body;
private String transactionId;
}
根据使用场景的不同,它有五个构造方法
public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
this.topic = topic;
this.flag = flag;
this.body = body;
if (tags != null && tags.length() > 0) {
this.setTags(tags);
}
if (keys != null && keys.length() > 0) {
this.setKeys(keys);
}
this.setWaitStoreMsgOK(waitStoreMsgOK);
}
除了无参构造方法外,其他的三个方法都是复用了全属性构造方法。其中,有几个参数需要来解释一下:
- tags:消息的Tag,主要用于消息的过滤
- keys:消息索引键,RocketMQ 主要根据这些Key来快速检索消息
- waitStoreMsgOK:RocketMQ 将消息发送出去后,是否要等待消息存储完成后再返回;从源码可以看出来,这个值是建议使用 true 的,这样可以使消息不容易丢失。
RocketMQ 生产者大揭秘
终于要到了本篇文章的重头戏,RocketMQ 生产者能够发送消息,究竟背后运行的原理是什么?
RocketMQ 生产者的代码实现都在 client 模块中,要具体看它的源码,大家可以看看这个包下的内容:
RocketMQ 发送消息 Demo
在 RocketMQ 提供的源码中,我们可以看到有对应的生产者发送消息的 Demo,大家有条件地话可以试着在本地跑一下,我这里截取了一些需要重点去理解的源码,如下:
public class Producer {
public static void main(String[] args) throws MQClientException, InterruptedException {
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
producer.start();
for (int i = 0; i < 1000; i++) {
try {
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
);
SendResult sendResult = producer.send(msg);
producer.sendOneway(msg);
SendResult sendResult = producer.send(msg);
System.out.printf("%s%n", sendResult);
producer.send(msg, new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
// do something
}
@Override
public void onException(Throwable e) {
// do something
}
});
System.out.printf("%s%n", sendResult);
} catch (Exception e) {
e.printStackTrace();
Thread.sleep(1000);
}
}
producer.shutdown();
}
}
根据这个 Demo,我们知道,要发送一个消息的步骤如下:
- 首先需要初始化一个 DefaultMQProducer 对象,调用该对象的 start 方法启动生产者
- 然后初始化一个 Message 对象
- 再调用 DefaultMQProducer 对象的 send 方法,将 Message 对象作为入参,将消息发送出去。根据 send 的不同重载,可以分为同步发送和异步发送,异步发送时需要实现它的回调方法;此外,还有 sendOneway 这个单向发送的方式
- 完成消息发送后,为了及时地释放资源,不占用过多的内存,需要调用 DefaultMQProducer 对象的 shutdown 方法来关闭生产者
Demo 还是比较好理解的,接下来我们着重了解下 DefaultMQProducer 对象。
认识 DefaultMQProducer
public class DefaultMQProducer extends ClientConfig implements MQProducer {}
public interface MQProducer extends MQAdmin{}
public interface MQAdmin {}
DefaultMQProducer 类继承自 ClientConfig 类,实现了 MQProducer 接口,而 MQProducer 接口又继承自 MQAdmin 接口。
DefaultMQProducer 类的全属性构造方法为:
public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook,
boolean enableMsgTrace, final String customizedTraceTopic) {
this.namespace = namespace;
this.producerGroup = producerGroup;
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
//if client open the message trace feature
if (enableMsgTrace) {
try {
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(producerGroup, TraceDispatcher.Type.PRODUCE, customizedTraceTopic, rpcHook);
dispatcher.setHostProducer(this.defaultMQProducerImpl);
traceDispatcher = dispatcher;
this.defaultMQProducerImpl.registerSendMessageHook(
new SendMessageTraceHookImpl(traceDispatcher));
this.defaultMQProducerImpl.registerEndTransactionHook(
new EndTransactionTraceHookImpl(traceDispatcher));
} catch (Throwable e) {
log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
}
}
}
根据这个构造方法,真正干活的类其实是 DefaultMQProducerImpl ,细心看代码的同学会发现,DefaultMQProducer 类下的方法,底层都是调用的 this.defaultMQProducerImpl 的方法,也就是 DefaultMQProducerImpl 类中的方法。构造方法首先是将命名空间、生产者所属组、RPCHook 钩子以及是否开启消息追踪等赋值到它的属性上。
DefaultMQProducer 的其他方法就不一一介绍了,我们知道怎么用就好了。
RocketMQ 生产者的启动流程
从上面的 Demo 我们知道,生产者在启动时,会调用 DefaultMQProducer 类的 start() 方法,而它的底层又是 DefaultMQProducerImpl 类的start() 方法。
一开始,主要做一些前置校验和一些赋值操作:
- 首先,会 DefaultMQProducerImpl 默认的 serviceState = CREATE_JUST,switch 分支会走到创建的流程中
- 然后,会检查 ProducerGroup 是否符合要求
- 将 Producer 的 instanceName 即实例名称改为进程ID
完成后,会创建 MQClientInstance 实例:
- 整个 JVM 实例中只存在一个的 MQClientManager 实例
- MQClientManager 维护一个 ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable,clientID 由 客户端IP + instanceName + unitName(可选)生成
- 创建 MQClientInstance 时,主要会初始化各种 Netty 网络操作对象,方便后续调用网络请求、心跳检测等
- 最后启动 MQClientInstance
小结
这篇文章涉及比较多的代码解析,涉及的类名比较多,大家慢慢消化,多看几遍源码加深印象就容易掌握了。今天我们主要学了 RocketMQ 发送消息的 Demo、简要了解了 DefaultMQProducer 的属性和方法以及在源码的基础上分析了 RocketMQ 生产者的启动流程。明天,将会更加深入地讲解 RocketMQ 消息发送的基本流程和关键链路,大家多多关注和支持~