RocketMQ中Producer訊息的傳送

鬆餅人發表於2019-08-02

上篇部落格介紹過Producer的啟動,這裡涉及到相關內容就不再累贅了 【RocketMQ中Producer的啟動原始碼分析】

 

Producer傳送訊息,首先需要生成Message例項:

 1 public class Message implements Serializable {
 2     private static final long serialVersionUID = 8445773977080406428L;
 3 
 4     private String topic;
 5     private int flag;
 6     private Map<String, String> properties;
 7     private byte[] body;
 8     private String transactionId;
 9 
10      public Message() {}
11 
12     public Message(String topic, byte[] body) {
13         this(topic, "", "", 0, body, true);
14     }
15 
16     public Message(String topic, String tags, byte[] body) {
17         this(topic, tags, "", 0, body, true);
18     }
19     
20     public Message(String topic, String tags, String keys, byte[] body) {
21         this(topic, tags, keys, 0, body, true);
22     }
23     
24     public Message(String topic, String tags, String keys, int flag, byte[] body, boolean waitStoreMsgOK) {
25         this.topic = topic;
26         this.flag = flag;
27         this.body = body;
28 
29         if (tags != null && tags.length() > 0)
30             this.setTags(tags);
31 
32         if (keys != null && keys.length() > 0)
33             this.setKeys(keys);
34 
35         this.setWaitStoreMsgOK(waitStoreMsgOK);
36     }
37 
38     public void setTags(String tags) {
39         this.putProperty(MessageConst.PROPERTY_TAGS, tags);
40     }
41 
42     public void setKeys(String keys) {
43         this.putProperty(MessageConst.PROPERTY_KEYS, keys);
44     }
45 
46     public void setWaitStoreMsgOK(boolean waitStoreMsgOK) {
47         this.putProperty(MessageConst.PROPERTY_WAIT_STORE_MSG_OK, Boolean.toString(waitStoreMsgOK));
48     }
49 
50     void putProperty(final String name, final String value) {
51         if (null == this.properties) {
52             this.properties = new HashMap<String, String>();
53         }
54 
55         this.properties.put(name, value);
56     }
57 
58     public void putUserProperty(final String name, final String value) {
59         if (MessageConst.STRING_HASH_SET.contains(name)) {
60             throw new RuntimeException(String.format(
61                 "The Property<%s> is used by system, input another please", name));
62         }
63 
64         if (value == null || value.trim().isEmpty()
65             || name == null || name.trim().isEmpty()) {
66             throw new IllegalArgumentException(
67                 "The name or value of property can not be null or blank string!"
68             );
69         }
70 
71         this.putProperty(name, value);
72     }
73     
74 }


其中properties中存放需要配置的屬性,由MessageConst規定其key:

 1 public class MessageConst {
 2     public static final String PROPERTY_KEYS = "KEYS";
 3     public static final String PROPERTY_TAGS = "TAGS";
 4     public static final String PROPERTY_WAIT_STORE_MSG_OK = "WAIT";
 5     public static final String PROPERTY_DELAY_TIME_LEVEL = "DELAY";
 6     public static final String PROPERTY_RETRY_TOPIC = "RETRY_TOPIC";
 7     public static final String PROPERTY_REAL_TOPIC = "REAL_TOPIC";
 8     public static final String PROPERTY_REAL_QUEUE_ID = "REAL_QID";
 9     public static final String PROPERTY_TRANSACTION_PREPARED = "TRAN_MSG";
10     public static final String PROPERTY_PRODUCER_GROUP = "PGROUP";
11     public static final String PROPERTY_MIN_OFFSET = "MIN_OFFSET";
12     public static final String PROPERTY_MAX_OFFSET = "MAX_OFFSET";
13     public static final String PROPERTY_BUYER_ID = "BUYER_ID";
14     public static final String PROPERTY_ORIGIN_MESSAGE_ID = "ORIGIN_MESSAGE_ID";
15     public static final String PROPERTY_TRANSFER_FLAG = "TRANSFER_FLAG";
16     public static final String PROPERTY_CORRECTION_FLAG = "CORRECTION_FLAG";
17     public static final String PROPERTY_MQ2_FLAG = "MQ2_FLAG";
18     public static final String PROPERTY_RECONSUME_TIME = "RECONSUME_TIME";
19     public static final String PROPERTY_MSG_REGION = "MSG_REGION";
20     public static final String PROPERTY_TRACE_SWITCH = "TRACE_ON";
21     public static final String PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX = "UNIQ_KEY";
22     public static final String PROPERTY_MAX_RECONSUME_TIMES = "MAX_RECONSUME_TIMES";
23     public static final String PROPERTY_CONSUME_START_TIMESTAMP = "CONSUME_START_TIME";
24     public static final String PROPERTY_TRANSACTION_PREPARED_QUEUE_OFFSET = "TRAN_PREPARED_QUEUE_OFFSET";
25     public static final String PROPERTY_TRANSACTION_CHECK_TIMES = "TRANSACTION_CHECK_TIMES";
26     public static final String PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS = "CHECK_IMMUNITY_TIME_IN_SECONDS";
27 }


在建立完Message後,通過DefaultMQProducer的send方法對訊息進行傳送

Producer支援三種模式的訊息傳送,由CommunicationMode列舉規定:

1 public enum CommunicationMode {
2     SYNC,
3     ASYNC,
4     ONEWAY,
5 }

分別代表:同步、非同步以及單向傳送
其中同步和非同步是根據不同引數型別的send方法來決定的

只要send方法中帶有SendCallback引數,都代表著非同步傳送,否則就是同步,SendCallback提供了非同步傳送的回滾事件響應:

1 public interface SendCallback {
2     void onSuccess(final SendResult sendResult);
3 
4     void onException(final Throwable e);
5 }


而單向傳送需要使用sendOneway方法

 

無論使用哪種方式,最後都是通過呼叫DefaultMQProducer包裝的defaultMQProducerImpl的sendDefaultImpl方法


DefaultMQProducerImpl的sendDefaultImpl方法:

  1 private SendResult sendDefaultImpl(
  2         Message msg,
  3         final CommunicationMode communicationMode,
  4         final SendCallback sendCallback,
  5         final long timeout
  6     ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
  7     this.makeSureStateOK();
  8     Validators.checkMessage(msg, this.defaultMQProducer);
  9 
 10     final long invokeID = random.nextLong();
 11     long beginTimestampFirst = System.currentTimeMillis();
 12     long beginTimestampPrev = beginTimestampFirst;
 13     long endTimestamp = beginTimestampFirst;
 14     TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
 15     if (topicPublishInfo != null && topicPublishInfo.ok()) {
 16         boolean callTimeout = false;
 17         MessageQueue mq = null;
 18         Exception exception = null;
 19         SendResult sendResult = null;
 20         int timesTotal = communicationMode == CommunicationMode.SYNC ? 1 + this.defaultMQProducer.getRetryTimesWhenSendFailed() : 1;
 21         int times = 0;
 22         String[] brokersSent = new String[timesTotal];
 23         for (; times < timesTotal; times++) {
 24             String lastBrokerName = null == mq ? null : mq.getBrokerName();
 25             MessageQueue mqSelected = this.selectOneMessageQueue(topicPublishInfo, lastBrokerName);
 26             if (mqSelected != null) {
 27                 mq = mqSelected;
 28                 brokersSent[times] = mq.getBrokerName();
 29                 try {
 30                     beginTimestampPrev = System.currentTimeMillis();
 31                     long costTime = beginTimestampPrev - beginTimestampFirst;
 32                     if (timeout < costTime) {
 33                         callTimeout = true;
 34                         break;
 35                     }
 36 
 37                     sendResult = this.sendKernelImpl(msg, mq, communicationMode, sendCallback, topicPublishInfo, timeout - costTime);
 38                     endTimestamp = System.currentTimeMillis();
 39                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
 40                     switch (communicationMode) {
 41                         case ASYNC:
 42                             return null;
 43                         case ONEWAY:
 44                             return null;
 45                         case SYNC:
 46                             if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
 47                                 if (this.defaultMQProducer.isRetryAnotherBrokerWhenNotStoreOK()) {
 48                                     continue;
 49                                 }
 50                             }
 51 
 52                             return sendResult;
 53                         default:
 54                             break;
 55                     }
 56                 } catch (RemotingException e) {
 57                     endTimestamp = System.currentTimeMillis();
 58                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
 59                     log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
 60                     log.warn(msg.toString());
 61                     exception = e;
 62                     continue;
 63                 } catch (MQClientException e) {
 64                     endTimestamp = System.currentTimeMillis();
 65                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
 66                     log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
 67                     log.warn(msg.toString());
 68                     exception = e;
 69                     continue;
 70                 } catch (MQBrokerException e) {
 71                     endTimestamp = System.currentTimeMillis();
 72                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, true);
 73                     log.warn(String.format("sendKernelImpl exception, resend at once, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
 74                     log.warn(msg.toString());
 75                     exception = e;
 76                     switch (e.getResponseCode()) {
 77                         case ResponseCode.TOPIC_NOT_EXIST:
 78                         case ResponseCode.SERVICE_NOT_AVAILABLE:
 79                         case ResponseCode.SYSTEM_ERROR:
 80                         case ResponseCode.NO_PERMISSION:
 81                         case ResponseCode.NO_BUYER_ID:
 82                         case ResponseCode.NOT_IN_CURRENT_UNIT:
 83                             continue;
 84                         default:
 85                             if (sendResult != null) {
 86                                 return sendResult;
 87                             }
 88 
 89                             throw e;
 90                     }
 91                 } catch (InterruptedException e) {
 92                     endTimestamp = System.currentTimeMillis();
 93                     this.updateFaultItem(mq.getBrokerName(), endTimestamp - beginTimestampPrev, false);
 94                     log.warn(String.format("sendKernelImpl exception, throw exception, InvokeID: %s, RT: %sms, Broker: %s", invokeID, endTimestamp - beginTimestampPrev, mq), e);
 95                     log.warn(msg.toString());
 96 
 97                     log.warn("sendKernelImpl exception", e);
 98                     log.warn(msg.toString());
 99                     throw e;
100                 }
101             } else {
102                 break;
103             }
104         }
105 
106         if (sendResult != null) {
107             return sendResult;
108         }
109 
110         String info = String.format("Send [%d] times, still failed, cost [%d]ms, Topic: %s, BrokersSent: %s",
111             times,
112             System.currentTimeMillis() - beginTimestampFirst,
113             msg.getTopic(),
114             Arrays.toString(brokersSent));
115 
116         info += FAQUrl.suggestTodo(FAQUrl.SEND_MSG_FAILED);
117 
118         MQClientException mqClientException = new MQClientException(info, exception);
119         if (callTimeout) {
120             throw new RemotingTooMuchRequestException("sendDefaultImpl call timeout");
121         }
122 
123         if (exception instanceof MQBrokerException) {
124             mqClientException.setResponseCode(((MQBrokerException) exception).getResponseCode());
125         } else if (exception instanceof RemotingConnectException) {
126             mqClientException.setResponseCode(ClientErrorCode.CONNECT_BROKER_EXCEPTION);
127         } else if (exception instanceof RemotingTimeoutException) {
128             mqClientException.setResponseCode(ClientErrorCode.ACCESS_BROKER_TIMEOUT);
129         } else if (exception instanceof MQClientException) {
130             mqClientException.setResponseCode(ClientErrorCode.BROKER_NOT_EXIST_EXCEPTION);
131         }
132 
133         throw mqClientException;
134     }
135 
136     List<String> nsList = this.getmQClientFactory().getMQClientAPIImpl().getNameServerAddressList();
137     if (null == nsList || nsList.isEmpty()) {
138         throw new MQClientException(
139             "No name server address, please set it." + FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL), null).setResponseCode(ClientErrorCode.NO_NAME_SERVER_EXCEPTION);
140     }
141 
142     throw new MQClientException("No route info of this topic, " + msg.getTopic() + FAQUrl.suggestTodo(FAQUrl.NO_TOPIC_ROUTE_INFO),
143         null).setResponseCode(ClientErrorCode.NOT_FOUND_TOPIC_EXCEPTION);
144 }

其中CommunicationMode引數會根據呼叫的API進行如上所說進行傳送型別的設定
而SendCallback引數,只有當使用非同步傳送的API時才不是null


首先呼叫makeSureStateOK方法,檢查Producer是否啟動:

1 private void makeSureStateOK() throws MQClientException {
2     if (this.serviceState != ServiceState.RUNNING) {
3         throw new MQClientException("The producer service state not OK, "
4             + this.serviceState
5             + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK),
6             null);
7     }
8 }

serviceState 在上一篇部落格中介紹過了


在檢查完Producer的狀態後,還需要通過Validators的checkTopic方法驗證Message的合法性:

 1 public static void checkTopic(String topic) throws MQClientException {
 2     if (UtilAll.isBlank(topic)) {
 3         throw new MQClientException("The specified topic is blank", null);
 4     }
 5 
 6     if (!regularExpressionMatcher(topic, PATTERN)) {
 7         throw new MQClientException(String.format(
 8             "The specified topic[%s] contains illegal characters, allowing only %s", topic,
 9             VALID_PATTERN_STR), null);
10     }
11 
12     if (topic.length() > CHARACTER_MAX_LENGTH) {
13         throw new MQClientException("The specified topic is longer than topic max length 255.", null);
14     }
15 
16     //whether the same with system reserved keyword
17     if (topic.equals(MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC)) {
18         throw new MQClientException(
19             String.format("The topic[%s] is conflict with AUTO_CREATE_TOPIC_KEY_TOPIC.", topic), null);
20     }
21 }

驗證完畢後,記錄開始時間戳,預示著傳送的真正開始


接著呼叫tryToFindTopicPublishInfo,根據Topic獲取路由資訊
tryToFindTopicPublishInfo方法:

 1 private TopicPublishInfo tryToFindTopicPublishInfo(final String topic) {
 2     TopicPublishInfo topicPublishInfo = this.topicPublishInfoTable.get(topic);
 3     if (null == topicPublishInfo || !topicPublishInfo.ok()) {
 4         this.topicPublishInfoTable.putIfAbsent(topic, new TopicPublishInfo());
 5         this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic);
 6         topicPublishInfo = this.topicPublishInfoTable.get(topic);
 7     }
 8 
 9     if (topicPublishInfo.isHaveTopicRouterInfo() || topicPublishInfo.ok()) {
10         return topicPublishInfo;
11     } else {
12         this.mQClientFactory.updateTopicRouteInfoFromNameServer(topic, true, this.defaultMQProducer);
13         topicPublishInfo = this.topicPublishInfoTable.get(topic);
14         return topicPublishInfo;
15     }
16 }

在Producer啟動中已經介紹過了topicPublishInfoTable,是一張記錄有關Topic的路由資訊的map,先嚐試獲取是否有存在的TopicPublishInfo
若是不存在,或者訊息佇列不可用(ok不成立):

1 public boolean ok() {
2     return null != this.messageQueueList && !this.messageQueueList.isEmpty();
3 }

ok用來驗證該路由上的訊息佇列是否可用

需要建立一個新的TopicPublishInfo放在map中,然後呼叫updateTopicRouteInfoFromNameServer來更新路由資訊,updateTopicRouteInfoFromNameServer在上一篇說過,在定時任務中會使用,這裡就是為了及時更新

若是存在,且有路由資訊訊息佇列可用,則直接返回topicPublishInfo
否則還需要呼叫updateTopicRouteInfoFromNameServer來進行一次更新


回到sendDefaultImpl,在取得到路由資訊後,現設定callTimeout超時響應為false,用於處理髮送超時
接著根據傳送方式CommunicationMode,計算如果傳送失敗,允許重發的次數,這裡是針對同步傳送,預設1+2共三次,其他兩種模式只允許傳送一次

根據傳送次數,建立一個記錄BrokerName的陣列,再由傳送次數進行for迴圈

首先根據topicPublishInfo和lastBrokerName呼叫selectOneMessageQueue選取指定的訊息佇列,是由TopicPublishInfo的selectOneMessageQueue方法實現的:

 1 public MessageQueue selectOneMessageQueue(final String lastBrokerName) {
 2     if (lastBrokerName == null) {
 3         return selectOneMessageQueue();
 4     } else {
 5         int index = this.sendWhichQueue.getAndIncrement();
 6         for (int i = 0; i < this.messageQueueList.size(); i++) {
 7             int pos = Math.abs(index++) % this.messageQueueList.size();
 8             if (pos < 0)
 9                 pos = 0;
10             MessageQueue mq = this.messageQueueList.get(pos);
11             if (!mq.getBrokerName().equals(lastBrokerName)) {
12                 return mq;
13             }
14         }
15         return selectOneMessageQueue();
16     }
17 }
18 
19 public MessageQueue selectOneMessageQueue() {
20     int index = this.sendWhichQueue.getAndIncrement();
21     int pos = Math.abs(index) % this.messageQueueList.size();
22     if (pos < 0)
23         pos = 0;
24     return this.messageQueueList.get(pos);
25 }

當lastBrokerName等於null,使用selectOneMessageQueue的無參方法,其中sendWhichQueue我在上一篇介紹過,不同執行緒通過getAndIncrement獲得到的index是一個隨機值
根據這個index對messageQueueList取餘,來獲取在list中的下標,根據這個下標在messageQueueList中選取一個MessageQueue
由於不同的MessageQueue有不同的路由資訊,所裡在這裡其實是為了負載均衡,保證每次傳送能傳送給不同的broker

若是lastBrokerName不等於null,還是和上面相似,只不過當選取到了MessageQueue時,要和lastBrokerName比較,當不想同時,才返回,同樣也是為了保證不向同一broker重複傳送來保證負載均衡

回到sendDefaultImpl,在選取完MessageQueue後,記錄BrokerName,在計算是否達到超時事件,當這些成功後需要呼叫sendKernelImpl來完成真正的傳送:
sendKernelImpl方法:

  1 private SendResult sendKernelImpl(final Message msg,
  2                                       final MessageQueue mq,
  3                                       final CommunicationMode communicationMode,
  4                                       final SendCallback sendCallback,
  5                                       final TopicPublishInfo topicPublishInfo,
  6                                       final long timeout) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
  7     long beginStartTime = System.currentTimeMillis();
  8     String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
  9     if (null == brokerAddr) {
 10         tryToFindTopicPublishInfo(mq.getTopic());
 11         brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(mq.getBrokerName());
 12     }
 13 
 14     SendMessageContext context = null;
 15     if (brokerAddr != null) {
 16         brokerAddr = MixAll.brokerVIPChannel(this.defaultMQProducer.isSendMessageWithVIPChannel(), brokerAddr);
 17 
 18         byte[] prevBody = msg.getBody();
 19         try {
 20             //for MessageBatch,ID has been set in the generating process
 21             if (!(msg instanceof MessageBatch)) {
 22                 MessageClientIDSetter.setUniqID(msg);
 23             }
 24 
 25             int sysFlag = 0;
 26             boolean msgBodyCompressed = false;
 27             if (this.tryToCompressMessage(msg)) {
 28                 sysFlag |= MessageSysFlag.COMPRESSED_FLAG;
 29                 msgBodyCompressed = true;
 30             }
 31 
 32             final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
 33             if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {
 34                 sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;
 35             }
 36 
 37             if (hasCheckForbiddenHook()) {
 38                 CheckForbiddenContext checkForbiddenContext = new CheckForbiddenContext();
 39                 checkForbiddenContext.setNameSrvAddr(this.defaultMQProducer.getNamesrvAddr());
 40                 checkForbiddenContext.setGroup(this.defaultMQProducer.getProducerGroup());
 41                 checkForbiddenContext.setCommunicationMode(communicationMode);
 42                 checkForbiddenContext.setBrokerAddr(brokerAddr);
 43                 checkForbiddenContext.setMessage(msg);
 44                 checkForbiddenContext.setMq(mq);
 45                 checkForbiddenContext.setUnitMode(this.isUnitMode());
 46                 this.executeCheckForbiddenHook(checkForbiddenContext);
 47             }
 48 
 49             if (this.hasSendMessageHook()) {
 50                 context = new SendMessageContext();
 51                 context.setProducer(this);
 52                 context.setProducerGroup(this.defaultMQProducer.getProducerGroup());
 53                 context.setCommunicationMode(communicationMode);
 54                 context.setBornHost(this.defaultMQProducer.getClientIP());
 55                 context.setBrokerAddr(brokerAddr);
 56                 context.setMessage(msg);
 57                 context.setMq(mq);
 58                 String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);
 59                 if (isTrans != null && isTrans.equals("true")) {
 60                     context.setMsgType(MessageType.Trans_Msg_Half);
 61                 }
 62 
 63                 if (msg.getProperty("__STARTDELIVERTIME") != null || msg.getProperty(MessageConst.PROPERTY_DELAY_TIME_LEVEL) != null) {
 64                     context.setMsgType(MessageType.Delay_Msg);
 65                 }
 66                 this.executeSendMessageHookBefore(context);
 67             }
 68 
 69             SendMessageRequestHeader requestHeader = new SendMessageRequestHeader();
 70             requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
 71             requestHeader.setTopic(msg.getTopic());
 72             requestHeader.setDefaultTopic(this.defaultMQProducer.getCreateTopicKey());
 73             requestHeader.setDefaultTopicQueueNums(this.defaultMQProducer.getDefaultTopicQueueNums());
 74             requestHeader.setQueueId(mq.getQueueId());
 75             requestHeader.setSysFlag(sysFlag);
 76             requestHeader.setBornTimestamp(System.currentTimeMillis());
 77             requestHeader.setFlag(msg.getFlag());
 78             requestHeader.setProperties(MessageDecoder.messageProperties2String(msg.getProperties()));
 79             requestHeader.setReconsumeTimes(0);
 80             requestHeader.setUnitMode(this.isUnitMode());
 81             requestHeader.setBatch(msg instanceof MessageBatch);
 82             if (requestHeader.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
 83                 String reconsumeTimes = MessageAccessor.getReconsumeTime(msg);
 84                 if (reconsumeTimes != null) {
 85                     requestHeader.setReconsumeTimes(Integer.valueOf(reconsumeTimes));
 86                     MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_RECONSUME_TIME);
 87                 }
 88 
 89                 String maxReconsumeTimes = MessageAccessor.getMaxReconsumeTimes(msg);
 90                 if (maxReconsumeTimes != null) {
 91                     requestHeader.setMaxReconsumeTimes(Integer.valueOf(maxReconsumeTimes));
 92                     MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_MAX_RECONSUME_TIMES);
 93                 }
 94             }
 95 
 96             SendResult sendResult = null;
 97             switch (communicationMode) {
 98                 case ASYNC:
 99                     Message tmpMessage = msg;
100                     if (msgBodyCompressed) {
101                         //If msg body was compressed, msgbody should be reset using prevBody.
102                         //Clone new message using commpressed message body and recover origin massage.
103                         //Fix bug:https://github.com/apache/rocketmq-externals/issues/66
104                         tmpMessage = MessageAccessor.cloneMessage(msg);
105                         msg.setBody(prevBody);
106                     }
107                     long costTimeAsync = System.currentTimeMillis() - beginStartTime;
108                     if (timeout < costTimeAsync) {
109                         throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
110                     }
111                     sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
112                         brokerAddr,
113                         mq.getBrokerName(),
114                         tmpMessage,
115                         requestHeader,
116                         timeout - costTimeAsync,
117                         communicationMode,
118                         sendCallback,
119                         topicPublishInfo,
120                         this.mQClientFactory,
121                         this.defaultMQProducer.getRetryTimesWhenSendAsyncFailed(),
122                         context,
123                         this);
124                     break;
125                 case ONEWAY:
126                 case SYNC:
127                     long costTimeSync = System.currentTimeMillis() - beginStartTime;
128                     if (timeout < costTimeSync) {
129                         throw new RemotingTooMuchRequestException("sendKernelImpl call timeout");
130                     }
131                     sendResult = this.mQClientFactory.getMQClientAPIImpl().sendMessage(
132                         brokerAddr,
133                         mq.getBrokerName(),
134                         msg,
135                         requestHeader,
136                         timeout - costTimeSync,
137                         communicationMode,
138                         context,
139                         this);
140                     break;
141                 default:
142                     assert false;
143                     break;
144             }
145 
146             if (this.hasSendMessageHook()) {
147                 context.setSendResult(sendResult);
148                 this.executeSendMessageHookAfter(context);
149             }
150 
151             return sendResult;
152         } catch (RemotingException e) {
153             if (this.hasSendMessageHook()) {
154                 context.setException(e);
155                 this.executeSendMessageHookAfter(context);
156             }
157             throw e;
158         } catch (MQBrokerException e) {
159             if (this.hasSendMessageHook()) {
160                 context.setException(e);
161                 this.executeSendMessageHookAfter(context);
162             }
163             throw e;
164         } catch (InterruptedException e) {
165             if (this.hasSendMessageHook()) {
166                 context.setException(e);
167                 this.executeSendMessageHookAfter(context);
168             }
169             throw e;
170         } finally {
171             msg.setBody(prevBody);
172         }
173     }
174 
175     throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
176 }

先記錄開始時間beginStartTime,為可能的超時做準備
然後根據BrokerName來獲取對應的Broker地址
findBrokerAddressInPublish方法:

1 public String findBrokerAddressInPublish(final String brokerName) {
2     HashMap<Long/* brokerId */, String/* address */> map = this.brokerAddrTable.get(brokerName);
3     if (map != null && !map.isEmpty()) {
4         return map.get(MixAll.MASTER_ID);
5     }
6 
7     return null;
8 }

根據brokerName在brokerAddrTable表中進行查詢

若是沒有找到還是通過tryToFindTopicPublishInfo來進行更新,然後再通過findBrokerAddressInPublish重新查詢

再往後,如果設定了VIP(高優先順序佇列)通道,那麼這裡將根據brokerAddr獲取VIP通道的的地址:
MixAll的brokerVIPChannel方法:

1 public static String brokerVIPChannel(final boolean isChange, final String brokerAddr) {
2     if (isChange) {
3         String[] ipAndPort = brokerAddr.split(":");
4         String brokerAddrNew = ipAndPort[0] + ":" + (Integer.parseInt(ipAndPort[1]) - 2);
5         return brokerAddrNew;
6     } else {
7         return brokerAddr;
8     }
9 }

VIP通道的地址計算很簡單,只是將埠號減去2

在設定完後,就是一大堆的配置了

這裡定義了一個sysFlag的整型值,表示訊息的型別,有如下取值:

1 public class MessageSysFlag {
2     public final static int COMPRESSED_FLAG = 0x1;
3     public final static int MULTI_TAGS_FLAG = 0x1 << 1;
4     public final static int TRANSACTION_NOT_TYPE = 0;
5     public final static int TRANSACTION_PREPARED_TYPE = 0x1 << 2;
6     public final static int TRANSACTION_COMMIT_TYPE = 0x2 << 2;
7     public final static int TRANSACTION_ROLLBACK_TYPE = 0x3 << 2;
8 }


還定義了一個msgBodyCompressed,表示訊息是否經過壓縮,tryToCompressMessage判斷並對訊息進行壓縮:
tryToCompressMessage方法:

 1 private boolean tryToCompressMessage(final Message msg) {
 2     if (msg instanceof MessageBatch) {
 3         //batch dose not support compressing right now
 4         return false;
 5     }
 6     byte[] body = msg.getBody();
 7     if (body != null) {
 8         if (body.length >= this.defaultMQProducer.getCompressMsgBodyOverHowmuch()) {
 9             try {
10                 byte[] data = UtilAll.compress(body, zipCompressLevel);
11                 if (data != null) {
12                     msg.setBody(data);
13                     return true;
14                 }
15             } catch (IOException e) {
16                 log.error("tryToCompressMessage exception", e);
17                 log.warn(msg.toString());
18             }
19         }
20     }
21 
22     return false;
23 }

當訊息大小大於等於compressMsgBodyOverHowmuch(預設4M)時,使用UtilAll的compress訊息進行壓縮處理:

 1 public static byte[] compress(final byte[] src, final int level) throws IOException {
 2     byte[] result = src;
 3     ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length);
 4     java.util.zip.Deflater defeater = new java.util.zip.Deflater(level);
 5     DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, defeater);
 6     try {
 7         deflaterOutputStream.write(src);
 8         deflaterOutputStream.finish();
 9         deflaterOutputStream.close();
10         result = byteArrayOutputStream.toByteArray();
11     } catch (IOException e) {
12         defeater.end();
13         throw e;
14     } finally {
15         try {
16             byteArrayOutputStream.close();
17         } catch (IOException ignored) {
18         }
19 
20         defeater.end();
21     }
22 
23     return result;
24 }

這裡採用zip的方式進行訊息壓縮

接下來,根據訊息是否是事務訊息來選擇設定sysFlag,關於事務訊息在後面部落格再說

接下來檢查是否設定了CheckForbiddenHook,若是設定了需要遍歷所有的CheckForbiddenHook,執行其 checkForbidden方法,來完成禁發

同理檢查是否設定了SendMessageHook,遍歷所有的SendMessageHook,執行其sendMessageBefore方法,在訊息傳送完畢後,會執行其sendMessageAfter方法


接著會對請求頭requestHeader進行一大堆設定,做完這些後,進入switch塊,根據不同的傳送方式做了相應檢查
最後無論是哪種傳送方式,都會呼叫MQClientAPIImpl的sendMessage方法:

 1 public SendResult sendMessage(
 2     final String addr,
 3     final String brokerName,
 4     final Message msg,
 5     final SendMessageRequestHeader requestHeader,
 6     final long timeoutMillis,
 7     final CommunicationMode communicationMode,
 8     final SendCallback sendCallback,
 9     final TopicPublishInfo topicPublishInfo,
10     final MQClientInstance instance,
11     final int retryTimesWhenSendFailed,
12     final SendMessageContext context,
13     final DefaultMQProducerImpl producer
14 ) throws RemotingException, MQBrokerException, InterruptedException {
15     long beginStartTime = System.currentTimeMillis();
16     RemotingCommand request = null;
17     if (sendSmartMsg || msg instanceof MessageBatch) {
18         SendMessageRequestHeaderV2 requestHeaderV2 = SendMessageRequestHeaderV2.createSendMessageRequestHeaderV2(requestHeader);
19         request = RemotingCommand.createRequestCommand(msg instanceof MessageBatch ? RequestCode.SEND_BATCH_MESSAGE : RequestCode.SEND_MESSAGE_V2, requestHeaderV2);
20     } else {
21         request = RemotingCommand.createRequestCommand(RequestCode.SEND_MESSAGE, requestHeader);
22     }
23 
24     request.setBody(msg.getBody());
25 
26     switch (communicationMode) {
27         case ONEWAY:
28             this.remotingClient.invokeOneway(addr, request, timeoutMillis);
29             return null;
30         case ASYNC:
31             final AtomicInteger times = new AtomicInteger();
32             long costTimeAsync = System.currentTimeMillis() - beginStartTime;
33             if (timeoutMillis < costTimeAsync) {
34                 throw new RemotingTooMuchRequestException("sendMessage call timeout");
35             }
36             this.sendMessageAsync(addr, brokerName, msg, timeoutMillis - costTimeAsync, request, sendCallback, topicPublishInfo, instance,
37                 retryTimesWhenSendFailed, times, context, producer);
38             return null;
39         case SYNC:
40             long costTimeSync = System.currentTimeMillis() - beginStartTime;
41             if (timeoutMillis < costTimeSync) {
42                 throw new RemotingTooMuchRequestException("sendMessage call timeout");
43             }
44             return this.sendMessageSync(addr, brokerName, msg, timeoutMillis - costTimeSync, request);
45         default:
46             assert false;
47             break;
48     }
49 
50     return null;
51 }

首先會根據訊息的型別,設定不同型別的請求RemotingCommand

在完成請求的封裝後,還是根據傳送方式來執行


ONEWAY方式:
會直接呼叫remotingClient即Netty客戶端的invokeOneway方法:

 1 public void invokeOneway(String addr, RemotingCommand request, long timeoutMillis) throws InterruptedException,
 2         RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
 3     final Channel channel = this.getAndCreateChannel(addr);
 4     if (channel != null && channel.isActive()) {
 5         try {
 6             doBeforeRpcHooks(addr, request);
 7             this.invokeOnewayImpl(channel, request, timeoutMillis);
 8         } catch (RemotingSendRequestException e) {
 9             log.warn("invokeOneway: send request exception, so close the channel[{}]", addr);
10             this.closeChannel(addr, channel);
11             throw e;
12         }
13     } else {
14         this.closeChannel(addr, channel);
15         throw new RemotingConnectException(addr);
16     }
17 }

首先根據broker的地址在channelTables中選取一個Channel(上一篇部落格介紹過在Netty客戶端會快取一張建立好連線的Channel的map即channelTables)

然後和前面相似,執行所有配置了的RPCHook的doBeforeRequest方法
之後執行invokeOnewayImpl方法:

 1 public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)
 2         throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
 3     request.markOnewayRPC();
 4     boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
 5     if (acquired) {
 6         final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway);
 7         try {
 8             channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
 9                 @Override
10                 public void operationComplete(ChannelFuture f) throws Exception {
11                     once.release();
12                     if (!f.isSuccess()) {
13                         log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed.");
14                     }
15                 }
16             });
17         } catch (Exception e) {
18             once.release();
19             log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed.");
20             throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
21         }
22     } else {
23         if (timeoutMillis <= 0) {
24             throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast");
25         } else {
26             String info = String.format(
27                 "invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
28                 timeoutMillis,
29                 this.semaphoreOneway.getQueueLength(),
30                 this.semaphoreOneway.availablePermits()
31             );
32             log.warn(info);
33             throw new RemotingTimeoutException(info);
34         }
35     }
36 }

首先對request的標誌位進行設定:

1 public void markOnewayRPC() {
2     int bits = 1 << RPC_ONEWAY;
3     this.flag |= bits;
4 }


接著會使用一個訊號量SemaphoreReleaseOnlyOnce,會保證該訊號量被釋放一次
最後呼叫Netty的writeAndFlush方法,進行request的傳送,同時設定了非同步監聽,用於成功後訊號量的釋放

由於是單向傳送,傳送完成後並沒有過多的處理

 

ASYNC方式:
呼叫sendMessageAsync方法:

 1 private void sendMessageAsync(
 2         final String addr,
 3         final String brokerName,
 4         final Message msg,
 5         final long timeoutMillis,
 6         final RemotingCommand request,
 7         final SendCallback sendCallback,
 8         final TopicPublishInfo topicPublishInfo,
 9         final MQClientInstance instance,
10         final int retryTimesWhenSendFailed,
11         final AtomicInteger times,
12         final SendMessageContext context,
13         final DefaultMQProducerImpl producer
14     ) throws InterruptedException, RemotingException {
15     this.remotingClient.invokeAsync(addr, request, timeoutMillis, new InvokeCallback() {
16         @Override
17         public void operationComplete(ResponseFuture responseFuture) {
18             RemotingCommand response = responseFuture.getResponseCommand();
19             if (null == sendCallback && response != null) {
20 
21                 try {
22                     SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
23                     if (context != null && sendResult != null) {
24                         context.setSendResult(sendResult);
25                         context.getProducer().executeSendMessageHookAfter(context);
26                     }
27                 } catch (Throwable e) {
28                 }
29 
30                 producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
31                 return;
32             }
33 
34             if (response != null) {
35                 try {
36                     SendResult sendResult = MQClientAPIImpl.this.processSendResponse(brokerName, msg, response);
37                     assert sendResult != null;
38                     if (context != null) {
39                         context.setSendResult(sendResult);
40                         context.getProducer().executeSendMessageHookAfter(context);
41                     }
42 
43                     try {
44                         sendCallback.onSuccess(sendResult);
45                     } catch (Throwable e) {
46                     }
47 
48                     producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), false);
49                 } catch (Exception e) {
50                     producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
51                     onExceptionImpl(brokerName, msg, 0L, request, sendCallback, topicPublishInfo, instance,
52                         retryTimesWhenSendFailed, times, e, context, false, producer);
53                 }
54             } else {
55                 producer.updateFaultItem(brokerName, System.currentTimeMillis() - responseFuture.getBeginTimestamp(), true);
56                 if (!responseFuture.isSendRequestOK()) {
57                     MQClientException ex = new MQClientException("send request failed", responseFuture.getCause());
58                     onExceptionImpl(brokerName, msg, 0L, request, sendCallback, topicPublishInfo, instance,
59                         retryTimesWhenSendFailed, times, ex, context, true, producer);
60                 } else if (responseFuture.isTimeout()) {
61                     MQClientException ex = new MQClientException("wait response timeout " + responseFuture.getTimeoutMillis() + "ms",
62                         responseFuture.getCause());
63                     onExceptionImpl(brokerName, msg, 0L, request, sendCallback, topicPublishInfo, instance,
64                         retryTimesWhenSendFailed, times, ex, context, true, producer);
65                 } else {
66                     MQClientException ex = new MQClientException("unknow reseaon", responseFuture.getCause());
67                     onExceptionImpl(brokerName, msg, 0L, request, sendCallback, topicPublishInfo, instance,
68                         retryTimesWhenSendFailed, times, ex, context, true, producer);
69                 }
70             }
71         }
72     });
73 }

在這裡設定了一個InvokeCallback,用於處理髮送之後的回撥


先看到invokeAsync方法:

 1 public void invokeAsync(String addr, RemotingCommand request, long timeoutMillis, InvokeCallback invokeCallback)
 2         throws InterruptedException, RemotingConnectException, RemotingTooMuchRequestException, RemotingTimeoutException,
 3         RemotingSendRequestException {
 4     long beginStartTime = System.currentTimeMillis();
 5     final Channel channel = this.getAndCreateChannel(addr);
 6     if (channel != null && channel.isActive()) {
 7         try {
 8             doBeforeRpcHooks(addr, request);
 9             long costTime = System.currentTimeMillis() - beginStartTime;
10             if (timeoutMillis < costTime) {
11                 throw new RemotingTooMuchRequestException("invokeAsync call timeout");
12             }
13             this.invokeAsyncImpl(channel, request, timeoutMillis - costTime, invokeCallback);
14         } catch (RemotingSendRequestException e) {
15             log.warn("invokeAsync: send request exception, so close the channel[{}]", addr);
16             this.closeChannel(addr, channel);
17             throw e;
18         }
19     } else {
20         this.closeChannel(addr, channel);
21         throw new RemotingConnectException(addr);
22     }
23 }

和前面ONEWAY類似,其具體實現是invokeAsyncImpl

invokeAsyncImpl方法:

 1 public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
 2         final InvokeCallback invokeCallback)
 3         throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
 4     long beginStartTime = System.currentTimeMillis();
 5     final int opaque = request.getOpaque();
 6     boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
 7     if (acquired) {
 8         final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
 9         long costTime = System.currentTimeMillis() - beginStartTime;
10         if (timeoutMillis < costTime) {
11             once.release();
12             throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
13         }
14 
15         final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
16         this.responseTable.put(opaque, responseFuture);
17         try {
18             channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
19                 @Override
20                 public void operationComplete(ChannelFuture f) throws Exception {
21                     if (f.isSuccess()) {
22                         responseFuture.setSendRequestOK(true);
23                         return;
24                     }
25                     requestFail(opaque);
26                     log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
27                 }
28             });
29         } catch (Exception e) {
30             responseFuture.release();
31             log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
32             throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
33         }
34     } else {
35         if (timeoutMillis <= 0) {
36             throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
37         } else {
38             String info =
39                 String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
40                     timeoutMillis,
41                     this.semaphoreAsync.getQueueLength(),
42                     this.semaphoreAsync.availablePermits()
43                 );
44             log.warn(info);
45             throw new RemotingTimeoutException(info);
46         }
47     }
48 }

這裡會通過request的getOpaque方法獲取一個opaque值,這個值在request建立時就會被賦值,是一個自增的AtomicInteger,也就是每個request的唯一ID

之後會建立一個ResponseFuture封裝invokeCallback及channel,並將其放入responseTable中
responseTable是一個map:

1 protected final ConcurrentMap<Integer /* opaque */, ResponseFuture> responseTable =
2     new ConcurrentHashMap<Integer, ResponseFuture>(256);

其記錄了requestID對應的ResponseFuture,用於管理非同步傳送後,對接收到響應的非同步事件處理
也就是說當傳送完畢,接收到響應訊息,會通過requestID查詢到對應的ResponseFuture,進而執行剛才設定的InvokeCallback中的方法,在InvokeCallback中,會執行processSendResponse方法,完成Broker回送的響應訊息的處理,最終根據情況會執行使用者傳入的SendCallback的onSuccess或者onException方法,以此完成訊息的非同步傳送

之後的步驟和ONEWAY一樣,由Netty的writeAndFlush完成傳送

 

SYNC方式:
呼叫sendMessageSync方法:

 1 private SendResult sendMessageSync(
 2         final String addr,
 3         final String brokerName,
 4         final Message msg,
 5         final long timeoutMillis,
 6         final RemotingCommand request
 7     ) throws RemotingException, MQBrokerException, InterruptedException {
 8     RemotingCommand response = this.remotingClient.invokeSync(addr, request, timeoutMillis);
 9     assert response != null;
10     return this.processSendResponse(brokerName, msg, response);
11 }

首先呼叫Netty客戶端的invokeSync方法:

invokeSync方法:

 1 public RemotingCommand invokeSync(String addr, final RemotingCommand request, long timeoutMillis)
 2         throws InterruptedException, RemotingConnectException, RemotingSendRequestException, RemotingTimeoutException {
 3     long beginStartTime = System.currentTimeMillis();
 4     final Channel channel = this.getAndCreateChannel(addr);
 5     if (channel != null && channel.isActive()) {
 6         try {
 7             doBeforeRpcHooks(addr, request);
 8             long costTime = System.currentTimeMillis() - beginStartTime;
 9             if (timeoutMillis < costTime) {
10                 throw new RemotingTimeoutException("invokeSync call timeout");
11             }
12             RemotingCommand response = this.invokeSyncImpl(channel, request, timeoutMillis - costTime);
13             doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(channel), request, response);
14             return response;
15         } catch (RemotingSendRequestException e) {
16             log.warn("invokeSync: send request exception, so close the channel[{}]", addr);
17             this.closeChannel(addr, channel);
18             throw e;
19         } catch (RemotingTimeoutException e) {
20             if (nettyClientConfig.isClientCloseSocketIfTimeout()) {
21                 this.closeChannel(addr, channel);
22                 log.warn("invokeSync: close socket because of timeout, {}ms, {}", timeoutMillis, addr);
23             }
24             log.warn("invokeSync: wait response timeout exception, the channel[{}]", addr);
25             throw e;
26         }
27     } else {
28         this.closeChannel(addr, channel);
29         throw new RemotingConnectException(addr);
30     }
31 }

還是和前面類似的步驟

直接看到invokeSyncImpl方法:

 1 public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
 2         final long timeoutMillis)
 3         throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
 4     final int opaque = request.getOpaque();
 5 
 6     try {
 7         final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
 8         this.responseTable.put(opaque, responseFuture);
 9         final SocketAddress addr = channel.remoteAddress();
10         channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
11             @Override
12             public void operationComplete(ChannelFuture f) throws Exception {
13                 if (f.isSuccess()) {
14                     responseFuture.setSendRequestOK(true);
15                     return;
16                 } else {
17                     responseFuture.setSendRequestOK(false);
18                 }
19 
20                 responseTable.remove(opaque);
21                 responseFuture.setCause(f.cause());
22                 responseFuture.putResponse(null);
23                 log.warn("send a request command to channel <" + addr + "> failed.");
24             }
25         });
26 
27         RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
28         if (null == responseCommand) {
29             if (responseFuture.isSendRequestOK()) {
30                 throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
31                     responseFuture.getCause());
32             } else {
33                 throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
34             }
35         }
36 
37         return responseCommand;
38     } finally {
39         this.responseTable.remove(opaque);
40     }
41 }

和ASYNC基本一致,只不過在完成writeAndFlush後,使用responseFuture的waitResponse方法,在超時時間內進行等待response的回送
若是傳送失敗,則會在DefaultMQProducerImpl的sendDefaultImpl中的for迴圈繼續,直至傳送完成或者傳送此時用完

若是在超時時間內,接收到Broker的回送response,在invokeSync中會執行配置了的RPCHook的doAfterResponse方法,然後在sendMessageSync中由processSendResponse處理接收到的響應

 

到此Producer的訊息傳送結束

相關文章