RocketMQ消息发送
RocketMQ 支持3 种消息发送方式: 同步 (sync)、异步(async)、单向(oneway)。
-
同步:发送者向 MQ 执行发送消息API 时,同步等待,直到消息服务器返回发送结果。
-
异步:发送者向MQ 执行发送消息API 时,指定消息发送成功后的回调函数,然后调用消息发送API 后,立即返回,消息发送者线程不阻塞,直到运行结束,消息发送成功或失败的回调任务在一个新的线程中返回。
-
单向:消息发送者向MQ 执行发送消息API 时,直接返回,不等待消息服务器的结果,也不注册回调函数,只管发,不管是否成功存储在消息服务器上。
一、Message 类
org.apache.rocketmq.common.message.Message
private String topic;
private int flag;
//存储 TAGS ,KEYS ,DELAY,WAIT 等键值数据
private Map<String, String> properties;
private byte[] body;
private String transactionId;
//TODO Message 索引键,多个用空格隔开,RocketMQ 可以根据这些 key 快速检索到消息
public void setKeys(Collection<String> keys) {
StringBuffer sb = new StringBuffer();
for (String k : keys) {
sb.append(k);
sb.append(MessageConst.KEY_SEPARATOR);
}
this.setKeys(sb.toString().trim());
}
//TODO 消息延迟界别,用于定时消息或消息重试。
public int getDelayTimeLevel() {
String t = this.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL);
if (t != null) {
return Integer.parseInt(t);
}
return 0;
}
public void setDelayTimeLevel(int level) {
this.putProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL, String.valueOf(level));
}
//TODO WaitStoreMsgOK 消息发送时是否等消息存储完成后再返回
public boolean isWaitStoreMsgOK() {
String result = this.getProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK);
if (null == result)
return true;
return Boolean.parseBoolean(result);
}
public void setWaitStoreMsgOK(boolean waitStoreMsgOK) {
this.putProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, Boolean.toString(waitStoreMsgOK));
}
二、DefaultMQProducer
(一)、核心属性
成员变量 | 解释 |
---|---|
producerGroup | 生产者所属组,消息服务器在回查事务状态时会随机选择该组中任何一个生产者发起事务回查请求。 |
createTopicKey | TopicValidator.AUTO_CREATE_TOPIC_KEY_TOPIC; 默认topic |
defaultTopicQueueNums | 默认主题在每一个Broker队列数量 |
sendMsgTimeout | 发送消息默认超时时间,默认3s |
compressMsgBodyOverHowmuch | 消息体超过该值则启用压缩 默认 4 k |
retryTimesWhenSendFailed | 同步方式发送消息重试次数,默认为 2 ,总共执行 3 次 |
retryTimesWhenSendAsyncFailed | 异步方式发送消息重试次数,默认为 2 |
retryAnotherBrokerWhenNotStoreOK | 消息重试时选择另外一个 Broker 时,是否不等待存储结果就返回,默认为false |
maxMessageSize | 允许发送的最大消息长度,默认为 4M ,该值最大值为 2^32-1 。 |
(二)、核心方法
发送方法比较多,这里不再挨个列举,方法很好识别,异步与同步的区分就是是否加上了 SendCallBack , 单向方式方法名都有Oneway标识
(三)启动流程:
启动流程是从 DefaultProducer. start() 方法开始 ,
public void start() throws MQClientException {
this.setProducerGroup(withNamespace(this.producerGroup));
//TODO DefaultMQProducerImpl.start
this.defaultMQProducerImpl.start();
if (null != traceDispatcher) {
try {
traceDispatcher.start(this.getNamesrvAddr(), this.getAccessChannel());
} catch (MQClientException e) {
log.warn("trace dispatcher start failed ", e);
}
}
}
可以看出实际的启动流程是从 DefaultMQProducerImpl.start 开始的。
this.serviceState = ServiceState.START_FAILED;
//TODO Step1 : 检查 productGroup 是否符合要求 ; 并改变生产者的 instanceName 为进程 ID。
this.checkConfig();
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID(); //并改变生产者的 instanceName 为进程 ID。
}
//TODO Step2 : 创建 MQClientInstance 实例。整个JVM 实例中只存在一个 MQClientManager 实例,维护一个 MQClienInstance 缓存表
//TODO ConcurrentMap<String /* clientId */,MQClientInstance> factoryTable = new ConcurrentMap<String,MQClientInstance>(),也就是同一个 cliendId 只会创建一个MQClienInstance。
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
//TODO Step3 : 向 MQClientInstance 注册,将当前生产者加入到 MQClientInstance 管理中,方便后续调用网络请求,进行心跳检测
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);
}
this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo());
//TODO Step4 : 启动 MQClientInstance ,如果 MQClientInstance 已经启动,则本次启动不会真正执行
if (startFactory) {
mQClientFactory.start();
}
log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(),
this.defaultMQProducer.isSendMessageWithVIPChannel());
this.serviceState = ServiceState.RUNNING;
break;