同步发送是最常用的方式,是指消息发送方发出一条消息后,会在收到服务端同步响应之后才发下一条消息的通讯方式,可靠的同步传输被广泛应用于各种场景,如重要的通知消息、短消息通知等。
先从一段官方示例代码开始:
public class SyncProducer {
public static void main(String[] args) throws Exception {
// 初始化一个producer并设置Producer group name
DefaultMQProducer producer = new DefaultMQProducer("please_rename_unique_group_name"); //(1)
// 设置NameServer地址
producer.setNamesrvAddr("localhost:9876"); //(2)
// 启动producer
producer.start();
for (int i = 0; i < 100; i++) {
// 创建一条消息,并指定topic、tag、body等信息,tag可以理解成标签,对消息进行再归类,RocketMQ可以在消费端对tag进行过滤
Message msg = new Message("TopicTest" /* Topic */,
"TagA" /* Tag */,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET) /* Message body */
); //(3)
// 利用producer进行发送,并同步等待发送结果
SendResult sendResult = producer.send(msg); //(4)
System.out.printf("%s%n", sendResult);
}
// 一旦producer不再使用,关闭producer
producer.shutdown();
}
}
同步发送的整个代码流程如下:
消息生产的入口类是DefaultMQProducer,首先要调用start()启动生产者实例,然后调用send()发送消息,这就算结束了,消息发送完成。是不是非常简单,接下来,我们来窥探一下它的内部实现原理。
继承ClientConfig,实现MQProducer接口(定义了,生产者所有的功能实现),这个类是对外给客户使用的,实际的内部实现都在DefaultMQProducerImpl这个类中,稍后会讲到。下面是继承关系图(偷个懒,网上找的)
// 默认构造函数
public DefaultMQProducer() {
// 指定默认的生产者组为:DEFAULT_PRODUCER
this(null, MixAll.DEFAULT_PRODUCER_GROUP, null);
}
/**
* 指定 RPC hook 的构造函数
*
* @param rpcHook RPC hook 用于每次执行远程处理命令.
*/
public DefaultMQProducer(RPCHook rpcHook) {
this(null, MixAll.DEFAULT_PRODUCER_GROUP, rpcHook);
}
// 指定生产者组的构造函数
public DefaultMQProducer(final String producerGroup) {
this(null, producerGroup, null);
}
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook, boolean enableMsgTrace,
final String customizedTraceTopic) {
this.producerGroup = producerGroup; // 指定生产者组
// 内部消息生产者实现类,详情看章节`2.1.2`
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
//是否客户端开启消息追踪
if (enableMsgTrace) {
try {
// 下面逻辑都是涉及消息追踪的,这个留待后续章节讲
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
dispatcher.setHostProducer(this.defaultMQProducerImpl);
traceDispatcher = dispatcher;
this.defaultMQProducerImpl.registerSendMessageHook(
new SendMessageTraceHookImpl(traceDispatcher));
} catch (Throwable e) {
log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
}
}
}
// 指定命名空间和生者组的构造函数
public DefaultMQProducer(final String namespace, final String producerGroup) {
this(namespace, producerGroup, null);
}
// 指定 rpcHook 和生者组的构造函数
public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) {
this(null, producerGroup, rpcHook);
}
// 指定 rpcHook、生者组和命名空间的构造函数
public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook) {
this.namespace = namespace; // 命名空间
this.producerGroup = producerGroup; // 生产者组
// 内部消息生产者实现类,详情看章节`2.1.2`
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
}
// 指定生产者组和消息追踪的构造函数
public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace) {
this(null, producerGroup, null, enableMsgTrace, null);
}
// 指定生产者组、消息追踪和追踪topic的构造函数
public DefaultMQProducer(final String producerGroup, boolean enableMsgTrace, final String customizedTraceTopic) {
this(null, producerGroup, null, enableMsgTrace, customizedTraceTopic);
}
public DefaultMQProducer(final String namespace, final String producerGroup, RPCHook rpcHook,
boolean enableMsgTrace, final String customizedTraceTopic) {
this.namespace = namespace; // 命名空间
this.producerGroup = producerGroup; // 生产者组
// 内部消息生产者实现类,详情看章节`2.1.2`
defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook);
//是否客户端开启消息追踪
if (enableMsgTrace) {
try {
// 下面逻辑都是涉及消息追踪的,这个留待后续章节讲
AsyncTraceDispatcher dispatcher = new AsyncTraceDispatcher(customizedTraceTopic, rpcHook);
dispatcher.setHostProducer(this.getDefaultMQProducerImpl());
traceDispatcher = dispatcher;
this.getDefaultMQProducerImpl().registerSendMessageHook(
new SendMessageTraceHookImpl(traceDispatcher));
} catch (Throwable e) {
log.error("system mqtrace hook init failed ,maybe can't send msg trace data");
}
}
}
public void start() throws MQClientException {
// 将生产者组改成:namespace + '%' + producerGroup,当然里面还有一些针对重试和死信消息的处理,有兴趣的读者可以进去看看
this.setProducerGroup(withNamespace(this.producerGroup));
this.defaultMQProducerImpl.start(); //DefaultMQProducerImpl具体实现启动逻辑,详情看章节`2.1.2`
if (null != traceDispatcher) {
try {
// 启动消息追踪
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
public SendResult send( Message msg)
throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
/*消费的校验,主要校验以下信息:
1.topic判空、格式、长度(255)、是否为TBW102,只要1条不合格,校验失败
2.body消息体判空,如果为空,抛出异常
3.body消息体长度判断:默认4M,这个值可以修改,但是建议不要改,超过抛出异常
*/
Validators.checkMessage(msg, this);
//如果有命名空间的,那么要在原topic前增加命名空间的前缀:namespace + '%' + topic
msg.setTopic(withNamespace(msg.getTopic()));
//DefaultMQProducerImpl具体实现启动逻辑,详情看章节`2.1.2`
return this.defaultMQProducerImpl.send(msg);
}
public void start(final boolean startFactory) throws MQClientException {
// 服务状态:CREATE_JUST--刚创建、RUNNING--运行中、SHUTDOWN_ALREADY--服务关闭、START_FAILED--启动失败
switch (this.serviceState) {
case CREATE_JUST: //创建还未启动
this.serviceState = ServiceState.START_FAILED;
//group命名检查,主要检查空、格式、长度,并且不能是默认生产者组名
this.checkConfig();
//判断group名字与CLIENT_INNER_PRODUCER不等,证明不是内部group,而是用户自定义的group
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();//将实例进程id设置成实例名
}
//获取或创建(详情见`3.2.1`)MQClientInstance实例,该实例通过单例模式获取,这个类很重要,同时涵盖了生产者和消费者的操作,详情见3.3
this.mQClientFactory = MQClientManager.getInstance()
.getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook);
//将当前生产者实例对象与group名绑定,并注册到MQClientInstance对象,方便后续使用时通过group取出,见`3.3.1`
boolean registerOK = mQClientFactory
.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
if (!registerOK) {
//重复注册失败,恢复状态,并抛出异常
this.serviceState = ServiceState.CREATE_JUST;
throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup()
+ "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL),
null);
}
//创建topic发布消息对象TopicPublishInfo,并存入topicPublishInfoTable map容器中
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo