RocketMQ中通過DefaultMQProducer建立Producer
DefaultMQProducer定義如下:
1 public class DefaultMQProducer extends ClientConfig implements MQProducer { 2 protected final transient DefaultMQProducerImpl defaultMQProducerImpl; 3 4 private String createTopicKey = MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC = "TBW102"; 5 6 private volatile int defaultTopicQueueNums = 4; 7 8 private int sendMsgTimeout = 3000; 9 10 private int compressMsgBodyOverHowmuch = 1024 * 4; 11 12 private int retryTimesWhenSendFailed = 2; 13 14 private int retryTimesWhenSendAsyncFailed = 2; 15 16 private boolean retryAnotherBrokerWhenNotStoreOK = false; 17 18 private int maxMessageSize = 1024 * 1024 * 4; // 4M 19 }
其中defaultMQProducerImpl成員是Producer的具體實現,其餘的一些成員是對一些引數的設定:
createTopicKey:是一個Topic值,在建立時使用,後面會說明
defaultTopicQueueNums :預設的Topic佇列個數
sendMsgTimeout:傳送訊息超時時間
compressMsgBodyOverHowmuch:訊息容量限制,超過需要進行壓縮
retryTimesWhenSendFailed:同步訊息傳送失敗的允許重發次數
retryTimesWhenSendAsyncFailed:非同步訊息傳送失敗的允許重發次數
retryAnotherBrokerWhenNotStoreOK:是否允許傳送給Broker失敗後,重新選擇Broker傳送
maxMessageSize:訊息最大大小
這些屬性可以通過DefaultMQProducer提供的get、set方法進行相應操作
常用的構造方法如下:
1 public DefaultMQProducer() { 2 this(MixAll.DEFAULT_PRODUCER_GROUP, null); 3 } 4 5 public DefaultMQProducer(final String producerGroup) { 6 this(producerGroup, null); 7 } 8 9 public DefaultMQProducer(final String producerGroup, RPCHook rpcHook) { 10 this.producerGroup = producerGroup; 11 defaultMQProducerImpl = new DefaultMQProducerImpl(this, rpcHook); 12 }
DefaultMQProducer繼承自ClientConfig,首先會設定ClientConfig提供的更底層的引數配置:
1 public class ClientConfig { 2 public static final String SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY = "com.rocketmq.sendMessageWithVIPChannel"; 3 4 private String namesrvAddr = System.getProperty(MixAll.NAMESRV_ADDR_PROPERTY, System.getenv(MixAll.NAMESRV_ADDR_ENV)); 5 6 private String clientIP = RemotingUtil.getLocalAddress(); 7 8 private String instanceName = System.getProperty("rocketmq.client.name", "DEFAULT"); 9 10 private int clientCallbackExecutorThreads = Runtime.getRuntime().availableProcessors(); 11 12 private int pollNameServerInterval = 1000 * 30; 13 14 private int heartbeatBrokerInterval = 1000 * 30; 15 16 private boolean vipChannelEnabled = Boolean.parseBoolean(System.getProperty(SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY, "true")); 17 }
其中namesrvAddr是非常重要的成員,其儲存著名稱伺服器(Name Server)的地址,在一開始構造時會根據系統屬性進行設定,若是沒有設定系統屬性就是null,則需要在後面通過set方法進行設定
clientIP:Producer端的本地IP
instanceName:Producer的例項名稱
pollNameServerInterval :輪詢NameServer的時間間隔
heartbeatBrokerInterval :向Broker傳送心跳包的時間間隔
SEND_MESSAGE_WITH_VIP_CHANNEL_PROPERTY 和vipChannelEnabled:決定是否使用VIP通道,即高優先順序
回到DefaultMQProducer的構造方法,其會建立DefaultMQProducerImpl例項
1 private final Random random = new Random(); 2 private final ConcurrentMap<String/* topic */, TopicPublishInfo> topicPublishInfoTable = 3 new ConcurrentHashMap<String, TopicPublishInfo>(); 4 private final ArrayList<SendMessageHook> sendMessageHookList = new ArrayList<SendMessageHook>(); 5 private final RPCHook rpcHook; 6 protected BlockingQueue<Runnable> checkRequestQueue; 7 protected ExecutorService checkExecutor; 8 private ServiceState serviceState = ServiceState.CREATE_JUST; 9 private MQClientInstance mQClientFactory; 10 private ArrayList<CheckForbiddenHook> checkForbiddenHookList = new ArrayList<CheckForbiddenHook>(); 11 private int zipCompressLevel = Integer.parseInt(System.getProperty(MixAll.MESSAGE_COMPRESS_LEVEL, "5")); 12 private MQFaultStrategy mqFaultStrategy = new MQFaultStrategy(); 13 private final BlockingQueue<Runnable> asyncSenderThreadPoolQueue; 14 private final ExecutorService defaultAsyncSenderExecutor; 15 private ExecutorService asyncSenderExecutor; 16 17 public DefaultMQProducerImpl(final DefaultMQProducer defaultMQProducer, RPCHook rpcHook) { 18 this.defaultMQProducer = defaultMQProducer; 19 this.rpcHook = rpcHook; 20 21 this.asyncSenderThreadPoolQueue = new LinkedBlockingQueue<Runnable>(50000); 22 this.defaultAsyncSenderExecutor = new ThreadPoolExecutor( 23 Runtime.getRuntime().availableProcessors(), 24 Runtime.getRuntime().availableProcessors(), 25 1000 * 60, 26 TimeUnit.MILLISECONDS, 27 this.asyncSenderThreadPoolQueue, 28 new ThreadFactory() { 29 private AtomicInteger threadIndex = new AtomicInteger(0); 30 31 @Override 32 public Thread newThread(Runnable r) { 33 return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet()); 34 } 35 }); 36 }
在構造方法中會建立一個執行緒池,用來處理非同步訊息的傳送
其中有一個topicPublishInfoTable成員很重要,是一個map,儲存了不同top和訊息佇列之間的對映,在後面詳細介紹
DefaultMQProducer建立完成後,接著來看DefaultMQProducer的start方法:
1 public void start() throws MQClientException { 2 this.defaultMQProducerImpl.start(); 3 if (null != traceDispatcher) { 4 try { 5 traceDispatcher.start(this.getNamesrvAddr()); 6 } catch (MQClientException e) { 7 log.warn("trace dispatcher start failed ", e); 8 } 9 } 10 }
首先交給了defaultMQProducerImpl的start方法去處理
defaultMQProducerImpl的start方法:
1 public void start() throws MQClientException { 2 this.start(true); 3 } 4 5 public void start(final boolean startFactory) throws MQClientException { 6 switch (this.serviceState) { 7 case CREATE_JUST: 8 this.serviceState = ServiceState.START_FAILED; 9 10 this.checkConfig(); 11 12 if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) { 13 this.defaultMQProducer.changeInstanceNameToPID(); 14 } 15 16 this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook); 17 18 boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this); 19 if (!registerOK) { 20 this.serviceState = ServiceState.CREATE_JUST; 21 throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() 22 + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), 23 null); 24 } 25 26 this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo()); 27 28 if (startFactory) { 29 mQClientFactory.start(); 30 } 31 32 log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(), 33 this.defaultMQProducer.isSendMessageWithVIPChannel()); 34 this.serviceState = ServiceState.RUNNING; 35 break; 36 case RUNNING: 37 case START_FAILED: 38 case SHUTDOWN_ALREADY: 39 throw new MQClientException("The producer service state not OK, maybe started once, " 40 + this.serviceState 41 + FAQUrl.suggestTodo(FAQUrl.CLIENT_SERVICE_NOT_OK), 42 null); 43 default: 44 break; 45 } 46 47 this.mQClientFactory.sendHeartbeatToAllBrokerWithLock(); 48 }
在一開始DefaultMQProducerImpl例項化的時候,serviceState初始化為CREATE_JUST狀態,這是一個列舉值,一共有如下幾種狀態:
1 public enum ServiceState { 2 CREATE_JUST, 3 RUNNING, 4 SHUTDOWN_ALREADY, 5 START_FAILED; 6 7 private ServiceState() { 8 } 9 }
這幾個狀態值很容易理解,在後面MQClientInstance中還會使用到
回到start方法,根據serviceState進行判斷,只有當是CREATE_JUST狀態時正常執行,防止在其他狀態下錯誤呼叫start
直接看到CREATE_JUST的case部分:
1 this.serviceState = ServiceState.START_FAILED; 2 3 this.checkConfig(); 4 5 if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) { 6 this.defaultMQProducer.changeInstanceNameToPID(); 7 } 8 9 this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer, rpcHook); 10 11 boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this); 12 if (!registerOK) { 13 this.serviceState = ServiceState.CREATE_JUST; 14 throw new MQClientException("The producer group[" + this.defaultMQProducer.getProducerGroup() 15 + "] has been created before, specify another name please." + FAQUrl.suggestTodo(FAQUrl.GROUP_NAME_DUPLICATE_URL), 16 null); 17 } 18 19 this.topicPublishInfoTable.put(this.defaultMQProducer.getCreateTopicKey(), new TopicPublishInfo()); 20 21 if (startFactory) { 22 mQClientFactory.start(); 23 } 24 25 log.info("the producer [{}] start OK. sendMessageWithVIPChannel={}", this.defaultMQProducer.getProducerGroup(), 26 this.defaultMQProducer.isSendMessageWithVIPChannel()); 27 this.serviceState = ServiceState.RUNNING; 28 break;
首先更改serviceState狀態為START_FAILED,防止中途的失敗
checkConfig方法是用來進行ProducerGroup命名檢查:
1 private void checkConfig() throws MQClientException { 2 Validators.checkGroup(this.defaultMQProducer.getProducerGroup()); 3 4 if (null == this.defaultMQProducer.getProducerGroup()) { 5 throw new MQClientException("producerGroup is null", null); 6 } 7 8 if (this.defaultMQProducer.getProducerGroup().equals(MixAll.DEFAULT_PRODUCER_GROUP)) { 9 throw new MQClientException("producerGroup can not equal " + MixAll.DEFAULT_PRODUCER_GROUP + ", please specify another one.", 10 null); 11 } 12 }
主要是檢查命名的合法性,以及防止和預設的producerGroup生產者組名DEFAULT_PRODUCER_GROUP產生衝突
1 public static final String DEFAULT_PRODUCER_GROUP = "DEFAULT_PRODUCER";
接下來例項化mQClientFactory,這其實是生產者客戶端的例項,其中MQClientManager採用單例模式,getInstance是獲取MQClientManager的單例,根據ClientConfig的型別,通過getAndCreateMQClientInstance方法例項化不同屬性的生產者客戶端
MQClientManager:
1 public class MQClientManager { 2 private final static InternalLogger log = ClientLogger.getLog(); 3 private static MQClientManager instance = new MQClientManager(); 4 private AtomicInteger factoryIndexGenerator = new AtomicInteger(); 5 private ConcurrentMap<String/* clientId */, MQClientInstance> factoryTable = 6 new ConcurrentHashMap<String, MQClientInstance>(); 7 8 private MQClientManager() { 9 } 10 11 public static MQClientManager getInstance() { 12 return instance; 13 } 14 }
其中factoryTable是所有生產者客戶端例項的map快取,factoryIndexGenerator 是建立的每個客戶端例項的流水號
getAndCreateMQClientInstance方法:
1 public MQClientInstance getAndCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) { 2 String clientId = clientConfig.buildMQClientId(); 3 MQClientInstance instance = this.factoryTable.get(clientId); 4 if (null == instance) { 5 instance = 6 new MQClientInstance(clientConfig.cloneClientConfig(), 7 this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); 8 MQClientInstance prev = this.factoryTable.putIfAbsent(clientId, instance); 9 if (prev != null) { 10 instance = prev; 11 log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); 12 } else { 13 log.info("Created new MQClientInstance for clientId:[{}]", clientId); 14 } 15 } 16 17 return instance; 18 }
首先通過buildMQClientId方法建立clientId:
1 public String buildMQClientId() { 2 StringBuilder sb = new StringBuilder(); 3 sb.append(this.getClientIP()); 4 5 sb.append("@"); 6 sb.append(this.getInstanceName()); 7 if (!UtilAll.isBlank(this.unitName)) { 8 sb.append("@"); 9 sb.append(this.unitName); 10 } 11 12 return sb.toString(); 13 }
clientId主要由生產者客戶端的ip地址以及例項名稱,根據unitName的有無,附加unitName
通過生成的clientId,在factoryTable快取中先去獲取是否建立過客戶端例項
若是沒有獲取到,就需要例項化一個MQClientInstance
這裡在例項化MQClientInstance時,並沒有直接傳入clientConfig,而是通過cloneClientConfig方法複製了一份,來保證安全性:
1 public ClientConfig cloneClientConfig() { 2 ClientConfig cc = new ClientConfig(); 3 cc.namesrvAddr = namesrvAddr; 4 cc.clientIP = clientIP; 5 cc.instanceName = instanceName; 6 cc.clientCallbackExecutorThreads = clientCallbackExecutorThreads; 7 cc.pollNameServerInterval = pollNameServerInterval; 8 cc.heartbeatBrokerInterval = heartbeatBrokerInterval; 9 cc.persistConsumerOffsetInterval = persistConsumerOffsetInterval; 10 cc.unitMode = unitMode; 11 cc.unitName = unitName; 12 cc.vipChannelEnabled = vipChannelEnabled; 13 cc.useTLS = useTLS; 14 cc.language = language; 15 return cc; 16 }
建立MQClientInstance例項:
1 public MQClientInstance(ClientConfig clientConfig, int instanceIndex, String clientId, RPCHook rpcHook) { 2 this.clientConfig = clientConfig; 3 this.instanceIndex = instanceIndex; 4 this.nettyClientConfig = new NettyClientConfig(); 5 this.nettyClientConfig.setClientCallbackExecutorThreads(clientConfig.getClientCallbackExecutorThreads()); 6 this.nettyClientConfig.setUseTLS(clientConfig.isUseTLS()); 7 this.clientRemotingProcessor = new ClientRemotingProcessor(this); 8 this.mQClientAPIImpl = new MQClientAPIImpl(this.nettyClientConfig, this.clientRemotingProcessor, rpcHook, clientConfig); 9 10 if (this.clientConfig.getNamesrvAddr() != null) { 11 this.mQClientAPIImpl.updateNameServerAddressList(this.clientConfig.getNamesrvAddr()); 12 log.info("user specified name server address: {}", this.clientConfig.getNamesrvAddr()); 13 } 14 15 this.clientId = clientId; 16 17 this.mQAdminImpl = new MQAdminImpl(this); 18 19 this.pullMessageService = new PullMessageService(this); 20 21 this.rebalanceService = new RebalanceService(this); 22 23 this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP); 24 this.defaultMQProducer.resetClientConfig(clientConfig); 25 26 this.consumerStatsManager = new ConsumerStatsManager(this.scheduledExecutorService); 27 28 log.info("Created a new client Instance, InstanceIndex:{}, ClientID:{}, ClientConfig:{}, ClientVersion:{}, SerializerType:{}", 29 this.instanceIndex, 30 this.clientId, 31 this.clientConfig, 32 MQVersion.getVersionDesc(MQVersion.CURRENT_VERSION), RemotingCommand.getSerializeTypeConfigInThisServer()); 33 }
可以看到MQClientInstance的構造方法建立了很多東西,就不一一說明,主要說幾個重要的
其中nettyClientConfig,就很清楚的說明了RocketMQ通過Netty來進行網路之間的I/O,其儲存了對Netty的一些配置
clientRemotingProcessor,用來進行訊息的處理
mQClientAPIImpl則是一個非常重要的部分,直接例項化了一個MQClientAPIImpl物件:
1 public MQClientAPIImpl(final NettyClientConfig nettyClientConfig, 2 final ClientRemotingProcessor clientRemotingProcessor, 3 RPCHook rpcHook, final ClientConfig clientConfig) { 4 this.clientConfig = clientConfig; 5 topAddressing = new TopAddressing(MixAll.getWSAddr(), clientConfig.getUnitName()); 6 this.remotingClient = new NettyRemotingClient(nettyClientConfig, null); 7 this.clientRemotingProcessor = clientRemotingProcessor; 8 9 this.remotingClient.registerRPCHook(rpcHook); 10 this.remotingClient.registerProcessor(RequestCode.CHECK_TRANSACTION_STATE, this.clientRemotingProcessor, null); 11 12 this.remotingClient.registerProcessor(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, this.clientRemotingProcessor, null); 13 14 this.remotingClient.registerProcessor(RequestCode.RESET_CONSUMER_CLIENT_OFFSET, this.clientRemotingProcessor, null); 15 16 this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT, this.clientRemotingProcessor, null); 17 18 this.remotingClient.registerProcessor(RequestCode.GET_CONSUMER_RUNNING_INFO, this.clientRemotingProcessor, null); 19 20 this.remotingClient.registerProcessor(RequestCode.CONSUME_MESSAGE_DIRECTLY, this.clientRemotingProcessor, null); 21 }
可以看到在這個構造方法裡,首先建立了一個TopAddressing,用於以後的名稱服務的定址,其預設地址是:
1 http://jmenv.tbsite.net:8080/rocketmq/nsaddr
需要通過系統屬性來完成更改
接著建立了一個NettyRemotingClient,這個就是實實在在的Netty客戶端
1 private final Bootstrap bootstrap = new Bootstrap(); 2 // 名稱服務列表 3 private final AtomicReference<List<String>> namesrvAddrList = new AtomicReference<List<String>>(); 4 5 public NettyRemotingClient(final NettyClientConfig nettyClientConfig, 6 final ChannelEventListener channelEventListener) { 7 super(nettyClientConfig.getClientOnewaySemaphoreValue(), nettyClientConfig.getClientAsyncSemaphoreValue()); 8 this.nettyClientConfig = nettyClientConfig; 9 this.channelEventListener = channelEventListener; 10 11 int publicThreadNums = nettyClientConfig.getClientCallbackExecutorThreads(); 12 if (publicThreadNums <= 0) { 13 publicThreadNums = 4; 14 } 15 16 this.publicExecutor = Executors.newFixedThreadPool(publicThreadNums, new ThreadFactory() { 17 private AtomicInteger threadIndex = new AtomicInteger(0); 18 19 @Override 20 public Thread newThread(Runnable r) { 21 return new Thread(r, "NettyClientPublicExecutor_" + this.threadIndex.incrementAndGet()); 22 } 23 }); 24 25 this.eventLoopGroupWorker = new NioEventLoopGroup(1, new ThreadFactory() { 26 private AtomicInteger threadIndex = new AtomicInteger(0); 27 28 @Override 29 public Thread newThread(Runnable r) { 30 return new Thread(r, String.format("NettyClientSelector_%d", this.threadIndex.incrementAndGet())); 31 } 32 }); 33 34 if (nettyClientConfig.isUseTLS()) { 35 try { 36 sslContext = TlsHelper.buildSslContext(true); 37 log.info("SSL enabled for client"); 38 } catch (IOException e) { 39 log.error("Failed to create SSLContext", e); 40 } catch (CertificateException e) { 41 log.error("Failed to create SSLContext", e); 42 throw new RuntimeException("Failed to create SSLContext", e); 43 } 44 } 45 }
此時Netty的客戶端僅僅完成了對Bootstrap的初始化,以及對NioEventLoopGroup的設定和初始化
回到MQClientInstance的構造方法,在完成MQClientAPIImpl的建立後,會根據clientConfig的getNamesrvAddr判斷是否設定了namesrvAddr名稱服務地址,若是設定了,需要通過mQClientAPIImpl的updateNameServerAddressList方法,完成對名稱服務地址的更新:
MQClientAPIImpl的updateNameServerAddressList方法:
1 public void updateNameServerAddressList(final String addrs) { 2 String[] addrArray = addrs.split(";"); 3 List<String> list = Arrays.asList(addrArray); 4 this.remotingClient.updateNameServerAddressList(list); 5 }
由於名稱服務可以是叢集的方式,所以在這裡用“;”進行分割,得到所有的名稱服務地址,再由remotingClient進行更新,而此時的remotingClient也就是剛才建立的NettyRemotingClient
NettyRemotingClient的updateNameServerAddressList方法:
1 public void updateNameServerAddressList(List<String> addrs) { 2 List<String> old = this.namesrvAddrList.get(); 3 boolean update = false; 4 5 if (!addrs.isEmpty()) { 6 if (null == old) { 7 update = true; 8 } else if (addrs.size() != old.size()) { 9 update = true; 10 } else { 11 for (int i = 0; i < addrs.size() && !update; i++) { 12 if (!old.contains(addrs.get(i))) { 13 update = true; 14 } 15 } 16 } 17 18 if (update) { 19 Collections.shuffle(addrs); 20 log.info("name server address updated. NEW : {} , OLD: {}", addrs, old); 21 this.namesrvAddrList.set(addrs); 22 } 23 } 24 }
這裡邏輯比較簡單,完成了名稱服務列表的更新
回到MQClientInstance的構造方法,做完以上操作後,又在後面建立了MQAdminImpl、PullMessageService、RebalanceService、ConsumerStatsManager以及一個新的DefaultMQProducer,關於這幾個在後面出現時再介紹
回到MQClientManager的getAndCreateMQClientInstance方法,在完成MQClientInstance的建立後,將其放入快取中
再回到DefaultMQProducerImpl的start方法,在建立完MQClientInstance後,呼叫registerProducer方法
MQClientInstance的registerProducer方法:
1 public boolean registerProducer(final String group, final DefaultMQProducerImpl producer) { 2 if (null == group || null == producer) { 3 return false; 4 } 5 6 MQProducerInner prev = this.producerTable.putIfAbsent(group, producer); 7 if (prev != null) { 8 log.warn("the producer group[{}] exist already.", group); 9 return false; 10 } 11 12 return true; 13 }
在MQClientInstance初始化時,會建立producerTable 、consumerTable 、topicRouteTable 、brokerAddrTable 這幾個比較重要的map
1 private final ConcurrentMap<String/* group */, MQProducerInner> producerTable = new ConcurrentHashMap<String, MQProducerInner>(); 2 private final ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>(); 3 private final ConcurrentMap<String/* Topic */, TopicRouteData> topicRouteTable = new ConcurrentHashMap<String, TopicRouteData>(); 4 private final ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>> brokerAddrTable = 5 new ConcurrentHashMap<String, HashMap<Long, String>>();
其中MQProducerInner是介面,DefaultMQProducerImpl是其實現類,完成了以group組名稱為鍵值的DefaultMQProducerImpl的關聯
在這裡就是根據group,進行DefaultMQProducerImpl的快取,MQConsumerInner同理
topicRouteTable 則記錄與Topic對應的Broker以及訊息佇列資訊
brokerAddrTable則記錄與Broker Name對應的Broker的地址列表
還是回到start方法,在完成registerProducer方法後,根據返回值registerOK,判斷接下來的操作
若是失敗,將serviceState置為CREATE_JUST,並報出異常,方便下一次的正常start
若是成功,則先需要向topicPublishInfoTable中新增一條鍵值為createTopicKey("TBW102")的TopicPublishInfo記錄
TopicPublishInfo:
1 public class TopicPublishInfo { 2 private boolean orderTopic = false; 3 private boolean haveTopicRouterInfo = false; 4 private List<MessageQueue> messageQueueList = new ArrayList<MessageQueue>(); 5 private volatile ThreadLocalIndex sendWhichQueue = new ThreadLocalIndex(); 6 }
其中messageQueueList存放訊息佇列MessageQueue,sendWhichQueue 是用來獲取sendWhichQueue中的下標,也就是當前所要傳送的具體的訊息佇列
MessageQueue:
1 public class MessageQueue implements Comparable<MessageQueue>, Serializable { 2 private static final long serialVersionUID = 6191200464116433425L; 3 private String topic; 4 private String brokerName; 5 private int queueId; 6 7 public MessageQueue() { 8 } 9 10 public MessageQueue(String topic, String brokerName, int queueId) { 11 this.topic = topic; 12 this.brokerName = brokerName; 13 this.queueId = queueId; 14 } 15 }
可以看到這是一個簡單的pojo,其封裝了topic,brokerName以及queueId
ThreadLocalIndex :
1 public class ThreadLocalIndex { 2 private final ThreadLocal<Integer> threadLocalIndex = new ThreadLocal<Integer>(); 3 private final Random random = new Random(); 4 5 public int getAndIncrement() { 6 Integer index = this.threadLocalIndex.get(); 7 if (null == index) { 8 index = Math.abs(random.nextInt()); 9 if (index < 0) 10 index = 0; 11 this.threadLocalIndex.set(index); 12 } 13 14 index = Math.abs(index + 1); 15 if (index < 0) 16 index = 0; 17 18 this.threadLocalIndex.set(index); 19 return index; 20 } 21 22 @Override 23 public String toString() { 24 return "ThreadLocalIndex{" + 25 "threadLocalIndex=" + threadLocalIndex.get() + 26 '}'; 27 } 28 }
通過ThreadLocal,賦予每個執行緒一個隨機值,後面會根據這個隨機值通過和messageQueueList的length取餘運算,選取一個MessageQueue ,進而選取一條真正的訊息佇列進行訊息傳送
再次回到DefaultMQProducerImpl的start方法,在完成createTopicKey的Topic的記錄新增後,根據startFactory判斷是否需要呼叫mQClientFactory的start方法,這裡預設startFactory是true,就需要呼叫mQClientFactory的start方法:
MQClientInstance的start方法:
1 public void start() throws MQClientException { 2 synchronized (this) { 3 switch (this.serviceState) { 4 case CREATE_JUST: 5 this.serviceState = ServiceState.START_FAILED; 6 // If not specified,looking address from name server 7 if (null == this.clientConfig.getNamesrvAddr()) { 8 this.mQClientAPIImpl.fetchNameServerAddr(); 9 } 10 // Start request-response channel 11 this.mQClientAPIImpl.start(); 12 // Start various schedule tasks 13 this.startScheduledTask(); 14 // Start pull service 15 this.pullMessageService.start(); 16 // Start rebalance service 17 this.rebalanceService.start(); 18 // Start push service 19 this.defaultMQProducer.getDefaultMQProducerImpl().start(false); 20 log.info("the client factory [{}] start OK", this.clientId); 21 this.serviceState = ServiceState.RUNNING; 22 break; 23 case RUNNING: 24 break; 25 case SHUTDOWN_ALREADY: 26 break; 27 case START_FAILED: 28 throw new MQClientException("The Factory object[" + this.getClientId() + "] has been created before, and failed.", null); 29 default: 30 break; 31 } 32 } 33 }
MQClientInstance在建立時其serviceState狀態也是CREATE_JUST
這裡首先檢查名稱服務地址是否設定,若是沒有設定,則通過MQClientAPIImpl的fetchNameServerAddr方法,嘗試自動獲取名稱服務
MQClientAPIImpl的fetchNameServerAddr方法:
1 public String fetchNameServerAddr() { 2 try { 3 String addrs = this.topAddressing.fetchNSAddr(); 4 if (addrs != null) { 5 if (!addrs.equals(this.nameSrvAddr)) { 6 log.info("name server address changed, old=" + this.nameSrvAddr + ", new=" + addrs); 7 this.updateNameServerAddressList(addrs); 8 this.nameSrvAddr = addrs; 9 return nameSrvAddr; 10 } 11 } 12 } catch (Exception e) { 13 log.error("fetchNameServerAddr Exception", e); 14 } 15 return nameSrvAddr; 16 }
這裡首先根據topAddressing的fetchNSAddr方法獲取名稱服務地址,若是獲取到了,則判斷是否需要更新名稱服務列表以及原來的nameSrvAddr
topAddressing在前面說過,MQClientAPIImpl構造方法中,建立TopAddressing例項
TopAddressing的fetchNSAddr方法:
1 public final String fetchNSAddr() { 2 return fetchNSAddr(true, 3000); 3 } 4 5 public final String fetchNSAddr(boolean verbose, long timeoutMills) { 6 String url = this.wsAddr; 7 try { 8 if (!UtilAll.isBlank(this.unitName)) { 9 url = url + "-" + this.unitName + "?nofix=1"; 10 } 11 HttpTinyClient.HttpResult result = HttpTinyClient.httpGet(url, null, null, "UTF-8", timeoutMills); 12 if (200 == result.code) { 13 String responseStr = result.content; 14 if (responseStr != null) { 15 return clearNewLine(responseStr); 16 } else { 17 log.error("fetch nameserver address is null"); 18 } 19 } else { 20 log.error("fetch nameserver address failed. statusCode=" + result.code); 21 } 22 } catch (IOException e) { 23 if (verbose) { 24 log.error("fetch name server address exception", e); 25 } 26 } 27 28 if (verbose) { 29 String errorMsg = 30 "connect to " + url + " failed, maybe the domain name " + MixAll.getWSAddr() + " not bind in /etc/hosts"; 31 errorMsg += FAQUrl.suggestTodo(FAQUrl.NAME_SERVER_ADDR_NOT_EXIST_URL); 32 33 log.warn(errorMsg); 34 } 35 return null; 36 }
首先根據wsAddr和unitName建立url,其中wsAddr在前面說過,預設是http://jmenv.tbsite.net:8080/rocketmq/nsaddr,需要通過系統屬性來更改
然後通過HttpTinyClient的httpGet方法建立連線,進行GET請求,獲取名稱地址
HttpTinyClient的httpGet方法:
1 static public HttpResult httpGet(String url, List<String> headers, List<String> paramValues, 2 String encoding, long readTimeoutMs) throws IOException { 3 String encodedContent = encodingParams(paramValues, encoding); 4 url += (null == encodedContent) ? "" : ("?" + encodedContent); 5 6 HttpURLConnection conn = null; 7 try { 8 conn = (HttpURLConnection) new URL(url).openConnection(); 9 conn.setRequestMethod("GET"); 10 conn.setConnectTimeout((int) readTimeoutMs); 11 conn.setReadTimeout((int) readTimeoutMs); 12 setHeaders(conn, headers, encoding); 13 14 conn.connect(); 15 int respCode = conn.getResponseCode(); 16 String resp = null; 17 18 if (HttpURLConnection.HTTP_OK == respCode) { 19 resp = IOTinyUtils.toString(conn.getInputStream(), encoding); 20 } else { 21 resp = IOTinyUtils.toString(conn.getErrorStream(), encoding); 22 } 23 return new HttpResult(respCode, resp); 24 } finally { 25 if (conn != null) { 26 conn.disconnect(); 27 } 28 } 29 }
這裡就通過了JDK原生的HttpURLConnection ,完成了一次指定url的GET請求,返回請求資料,將請求到的資料以及狀態碼封裝為HttpResult,返回給上一級呼叫,也就是TopAddressing的fetchNSAddr方法中,再呼叫clearNewLine方法,將狀態碼為200的資料處理(清除不必要的空客、換行、回車),得到名稱地址,最後回到fetchNameServerAddr方法中,完成名稱服務列表的更新,至此自動獲取名稱服務結束
回到MQClientInstance的start方法中:
在確定有名稱服務的情況下,首先呼叫mQClientAPIImpl的start方法:
MQClientAPIImpl的start方法:
1 public void start() { 2 this.remotingClient.start(); 3 }
這裡實際上呼叫了前面所建立的Nettt客戶端的start方法:
NettyRemotingClient的start方法:
1 public void start() { 2 this.defaultEventExecutorGroup = new DefaultEventExecutorGroup( 3 nettyClientConfig.getClientWorkerThreads(), 4 new ThreadFactory() { 5 6 private AtomicInteger threadIndex = new AtomicInteger(0); 7 8 @Override 9 public Thread newThread(Runnable r) { 10 return new Thread(r, "NettyClientWorkerThread_" + this.threadIndex.incrementAndGet()); 11 } 12 }); 13 14 Bootstrap handler = this.bootstrap.group(this.eventLoopGroupWorker).channel(NioSocketChannel.class) 15 .option(ChannelOption.TCP_NODELAY, true) 16 .option(ChannelOption.SO_KEEPALIVE, false) 17 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis()) 18 .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize()) 19 .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize()) 20 .handler(new ChannelInitializer<SocketChannel>() { 21 @Override 22 public void initChannel(SocketChannel ch) throws Exception { 23 ChannelPipeline pipeline = ch.pipeline(); 24 if (nettyClientConfig.isUseTLS()) { 25 if (null != sslContext) { 26 pipeline.addFirst(defaultEventExecutorGroup, "sslHandler", sslContext.newHandler(ch.alloc())); 27 log.info("Prepend SSL handler"); 28 } else { 29 log.warn("Connections are insecure as SSLContext is null!"); 30 } 31 } 32 pipeline.addLast( 33 defaultEventExecutorGroup, 34 new NettyEncoder(), 35 new NettyDecoder(), 36 new IdleStateHandler(0, 0, nettyClientConfig.getClientChannelMaxIdleTimeSeconds()), 37 new NettyConnectManageHandler(), 38 new NettyClientHandler()); 39 } 40 }); 41 42 this.timer.scheduleAtFixedRate(new TimerTask() { 43 @Override 44 public void run() { 45 try { 46 NettyRemotingClient.this.scanResponseTable(); 47 } catch (Throwable e) { 48 log.error("scanResponseTable exception", e); 49 } 50 } 51 }, 1000 * 3, 1000); 52 53 if (this.channelEventListener != null) { 54 this.nettyEventExecutor.start(); 55 } 56 }
這裡完成了Bootstrap對前面建立的EventLoopGroup以及handler的繫結
在完成mQClientAPIImpl的start方法後,呼叫startScheduledTask方法,啟動定時任務
startScheduledTask方法:
1 private void startScheduledTask() { 2 if (null == this.clientConfig.getNamesrvAddr()) { 3 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 4 5 @Override 6 public void run() { 7 try { 8 MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr(); 9 } catch (Exception e) { 10 log.error("ScheduledTask fetchNameServerAddr exception", e); 11 } 12 } 13 }, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS); 14 } 15 16 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 17 18 @Override 19 public void run() { 20 try { 21 MQClientInstance.this.updateTopicRouteInfoFromNameServer(); 22 } catch (Exception e) { 23 log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e); 24 } 25 } 26 }, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS); 27 28 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 29 30 @Override 31 public void run() { 32 try { 33 MQClientInstance.this.cleanOfflineBroker(); 34 MQClientInstance.this.sendHeartbeatToAllBrokerWithLock(); 35 } catch (Exception e) { 36 log.error("ScheduledTask sendHeartbeatToAllBroker exception", e); 37 } 38 } 39 }, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS); 40 41 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 42 43 @Override 44 public void run() { 45 try { 46 MQClientInstance.this.persistAllConsumerOffset(); 47 } catch (Exception e) { 48 log.error("ScheduledTask persistAllConsumerOffset exception", e); 49 } 50 } 51 }, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS); 52 53 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 54 55 @Override 56 public void run() { 57 try { 58 MQClientInstance.this.adjustThreadPool(); 59 } catch (Exception e) { 60 log.error("ScheduledTask adjustThreadPool exception", e); 61 } 62 } 63 }, 1, 1, TimeUnit.MINUTES); 64 }
可以看到,一共設定了五個定時任務
①若是名稱服務地址namesrvAddr不存在,則呼叫前面的fetchNameServerAddr方法,定時更新名稱服務
②通過updateTopicRouteInfoFromNameServer方法定時更新Topic所對應的路由資訊:
1 public void updateTopicRouteInfoFromNameServer() { 2 Set<String> topicList = new HashSet<String>(); 3 4 // Consumer 5 { 6 Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator(); 7 while (it.hasNext()) { 8 Entry<String, MQConsumerInner> entry = it.next(); 9 MQConsumerInner impl = entry.getValue(); 10 if (impl != null) { 11 Set<SubscriptionData> subList = impl.subscriptions(); 12 if (subList != null) { 13 for (SubscriptionData subData : subList) { 14 topicList.add(subData.getTopic()); 15 } 16 } 17 } 18 } 19 } 20 21 // Producer 22 { 23 Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator(); 24 while (it.hasNext()) { 25 Entry<String, MQProducerInner> entry = it.next(); 26 MQProducerInner impl = entry.getValue(); 27 if (impl != null) { 28 Set<String> lst = impl.getPublishTopicList(); 29 topicList.addAll(lst); 30 } 31 } 32 } 33 34 for (String topic : topicList) { 35 this.updateTopicRouteInfoFromNameServer(topic); 36 } 37 }
將所有Consumer和Producer的Topic封裝在topicList,交給updateTopicRouteInfoFromNameServer呼叫
updateTopicRouteInfoFromNameServer方法:
1 public boolean updateTopicRouteInfoFromNameServer(final String topic) { 2 return updateTopicRouteInfoFromNameServer(topic, false, null); 3 } 4 5 public boolean updateTopicRouteInfoFromNameServer(final String topic, boolean isDefault, 6 DefaultMQProducer defaultMQProducer) { 7 try { 8 if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) { 9 try { 10 TopicRouteData topicRouteData; 11 if (isDefault && defaultMQProducer != null) { 12 topicRouteData = this.mQClientAPIImpl.getDefaultTopicRouteInfoFromNameServer(defaultMQProducer.getCreateTopicKey(), 13 1000 * 3); 14 if (topicRouteData != null) { 15 for (QueueData data : topicRouteData.getQueueDatas()) { 16 int queueNums = Math.min(defaultMQProducer.getDefaultTopicQueueNums(), data.getReadQueueNums()); 17 data.setReadQueueNums(queueNums); 18 data.setWriteQueueNums(queueNums); 19 } 20 } 21 } else { 22 topicRouteData = this.mQClientAPIImpl.getTopicRouteInfoFromNameServer(topic, 1000 * 3); 23 } 24 if (topicRouteData != null) { 25 TopicRouteData old = this.topicRouteTable.get(topic); 26 boolean changed = topicRouteDataIsChange(old, topicRouteData); 27 if (!changed) { 28 changed = this.isNeedUpdateTopicRouteInfo(topic); 29 } else { 30 log.info("the topic[{}] route info changed, old[{}] ,new[{}]", topic, old, topicRouteData); 31 } 32 33 if (changed) { 34 TopicRouteData cloneTopicRouteData = topicRouteData.cloneTopicRouteData(); 35 36 for (BrokerData bd : topicRouteData.getBrokerDatas()) { 37 this.brokerAddrTable.put(bd.getBrokerName(), bd.getBrokerAddrs()); 38 } 39 40 // Update Pub info 41 { 42 TopicPublishInfo publishInfo = topicRouteData2TopicPublishInfo(topic, topicRouteData); 43 publishInfo.setHaveTopicRouterInfo(true); 44 Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator(); 45 while (it.hasNext()) { 46 Entry<String, MQProducerInner> entry = it.next(); 47 MQProducerInner impl = entry.getValue(); 48 if (impl != null) { 49 impl.updateTopicPublishInfo(topic, publishInfo); 50 } 51 } 52 } 53 54 // Update sub info 55 { 56 Set<MessageQueue> subscribeInfo = topicRouteData2TopicSubscribeInfo(topic, topicRouteData); 57 Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator(); 58 while (it.hasNext()) { 59 Entry<String, MQConsumerInner> entry = it.next(); 60 MQConsumerInner impl = entry.getValue(); 61 if (impl != null) { 62 impl.updateTopicSubscribeInfo(topic, subscribeInfo); 63 } 64 } 65 } 66 log.info("topicRouteTable.put. Topic = {}, TopicRouteData[{}]", topic, cloneTopicRouteData); 67 this.topicRouteTable.put(topic, cloneTopicRouteData); 68 return true; 69 } 70 } else { 71 log.warn("updateTopicRouteInfoFromNameServer, getTopicRouteInfoFromNameServer return null, Topic: {}", topic); 72 } 73 } catch (Exception e) { 74 if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX) && !topic.equals(MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC)) { 75 log.warn("updateTopicRouteInfoFromNameServer Exception", e); 76 } 77 } finally { 78 this.lockNamesrv.unlock(); 79 } 80 } else { 81 log.warn("updateTopicRouteInfoFromNameServer tryLock timeout {}ms", LOCK_TIMEOUT_MILLIS); 82 } 83 } catch (InterruptedException e) { 84 log.warn("updateTopicRouteInfoFromNameServer Exception", e); 85 } 86 87 return false; 88 }
這裡首先由mQClientAPIImpl的getTopicRouteInfoFromNameServer方法,從名稱伺服器上獲取其Topic所對應的路由資訊
其中Topic的路由資訊由TopicRouteData進行封裝:
1 public class TopicRouteData extends RemotingSerializable { 2 private String orderTopicConf; 3 private List<QueueData> queueDatas; 4 private List<BrokerData> brokerDatas; 5 private HashMap<String/* brokerAddr */, List<String>/* Filter Server */> filterServerTable; 6 }
QueueData:
1 public class QueueData implements Comparable<QueueData> { 2 private String brokerName; 3 private int readQueueNums; 4 private int writeQueueNums; 5 private int perm; 6 private int topicSynFlag; 7 }
BrokerData:
1 public class BrokerData implements Comparable<BrokerData> { 2 private String cluster; 3 private String brokerName; 4 private HashMap<Long/* brokerId */, String/* broker address */> brokerAddrs; 5 }
getTopicRouteInfoFromNameServer方法:
1 public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis) 2 throws RemotingException, MQClientException, InterruptedException { 3 return getTopicRouteInfoFromNameServer(topic, timeoutMillis, true); 4 } 5 6 public TopicRouteData getTopicRouteInfoFromNameServer(final String topic, final long timeoutMillis, 7 boolean allowTopicNotExist) throws MQClientException, InterruptedException, RemotingTimeoutException, RemotingSendRequestException, RemotingConnectException { 8 GetRouteInfoRequestHeader requestHeader = new GetRouteInfoRequestHeader(); 9 requestHeader.setTopic(topic); 10 11 RemotingCommand request = RemotingCommand.createRequestCommand(RequestCode.GET_ROUTEINTO_BY_TOPIC, requestHeader); 12 13 RemotingCommand response = this.remotingClient.invokeSync(null, request, timeoutMillis); 14 assert response != null; 15 switch (response.getCode()) { 16 case ResponseCode.TOPIC_NOT_EXIST: { 17 if (allowTopicNotExist && !topic.equals(MixAll.AUTO_CREATE_TOPIC_KEY_TOPIC)) { 18 log.warn("get Topic [{}] RouteInfoFromNameServer is not exist value", topic); 19 } 20 21 break; 22 } 23 case ResponseCode.SUCCESS: { 24 byte[] body = response.getBody(); 25 if (body != null) { 26 return TopicRouteData.decode(body, TopicRouteData.class); 27 } 28 } 29 default: 30 break; 31 } 32 33 throw new MQClientException(response.getCode(), response.getRemark()); 34 }
這裡主要通過remotingClient即Netty客戶端的invokeSync方法向名稱伺服器傳送封裝好的request請求來獲取response
通過名稱伺服器尋找與Topic相關的Broker有關路由資訊,將這些資訊作為response返回,在這裡接收到進行處理,封裝成TopicRouteData
在invokeSync方法中採用懶載入的方式,嘗試獲取已經建立好連線的Channel,若是沒有,則需要通過bootstrap的connect方法先建立連線產生ChannelFuture,進而獲取並快取Channel
回到updateTopicRouteInfoFromNameServer,通過名稱伺服器獲取到了有關Topic的路由資訊,呼叫topicRouteDataIsChange方法和原來topicRouteTable儲存的路由資訊進行比較
topicRouteDataIsChange方法:
1 private boolean topicRouteDataIsChange(TopicRouteData olddata, TopicRouteData nowdata) { 2 if (olddata == null || nowdata == null) 3 return true; 4 TopicRouteData old = olddata.cloneTopicRouteData(); 5 TopicRouteData now = nowdata.cloneTopicRouteData(); 6 Collections.sort(old.getQueueDatas()); 7 Collections.sort(old.getBrokerDatas()); 8 Collections.sort(now.getQueueDatas()); 9 Collections.sort(now.getBrokerDatas()); 10 return !old.equals(now); 11 }
若是沒有發生改變,任然要呼叫isNeedUpdateTopicRouteInfo方法檢查是否有需要更新
isNeedUpdateTopicRouteInfo方法:
1 private boolean isNeedUpdateTopicRouteInfo(final String topic) { 2 boolean result = false; 3 { 4 Iterator<Entry<String, MQProducerInner>> it = this.producerTable.entrySet().iterator(); 5 while (it.hasNext() && !result) { 6 Entry<String, MQProducerInner> entry = it.next(); 7 MQProducerInner impl = entry.getValue(); 8 if (impl != null) { 9 result = impl.isPublishTopicNeedUpdate(topic); 10 } 11 } 12 } 13 14 { 15 Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator(); 16 while (it.hasNext() && !result) { 17 Entry<String, MQConsumerInner> entry = it.next(); 18 MQConsumerInner impl = entry.getValue(); 19 if (impl != null) { 20 result = impl.isSubscribeTopicNeedUpdate(topic); 21 } 22 } 23 } 24 25 return result; 26 }
分別對所有的消費者和生產者進行檢查是否有需要更新有關該Topic的路由資訊
當存在需要跟新的情況時,在updateTopicRouteInfoFromNameServer中
首先從topicRouteData中取出BrokerData,即Broker的路由資訊,進行更新
再根據topicRouteData從中獲取消費者生產者的訊息路由資訊,分別進行更新
③定時清除離線的Broker,以及向當前線上的Broker傳送心跳包
cleanOfflineBroker清除離線的Broker:
1 private void cleanOfflineBroker() { 2 try { 3 if (this.lockNamesrv.tryLock(LOCK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) 4 try { 5 ConcurrentHashMap<String, HashMap<Long, String>> updatedTable = new ConcurrentHashMap<String, HashMap<Long, String>>(); 6 7 Iterator<Entry<String, HashMap<Long, String>>> itBrokerTable = this.brokerAddrTable.entrySet().iterator(); 8 while (itBrokerTable.hasNext()) { 9 Entry<String, HashMap<Long, String>> entry = itBrokerTable.next(); 10 String brokerName = entry.getKey(); 11 HashMap<Long, String> oneTable = entry.getValue(); 12 13 HashMap<Long, String> cloneAddrTable = new HashMap<Long, String>(); 14 cloneAddrTable.putAll(oneTable); 15 16 Iterator<Entry<Long, String>> it = cloneAddrTable.entrySet().iterator(); 17 while (it.hasNext()) { 18 Entry<Long, String> ee = it.next(); 19 String addr = ee.getValue(); 20 if (!this.isBrokerAddrExistInTopicRouteTable(addr)) { 21 it.remove(); 22 log.info("the broker addr[{} {}] is offline, remove it", brokerName, addr); 23 } 24 } 25 26 if (cloneAddrTable.isEmpty()) { 27 itBrokerTable.remove(); 28 log.info("the broker[{}] name's host is offline, remove it", brokerName); 29 } else { 30 updatedTable.put(brokerName, cloneAddrTable); 31 } 32 } 33 34 if (!updatedTable.isEmpty()) { 35 this.brokerAddrTable.putAll(updatedTable); 36 } 37 } finally { 38 this.lockNamesrv.unlock(); 39 } 40 } catch (InterruptedException e) { 41 log.warn("cleanOfflineBroker Exception", e); 42 } 43 }
這裡的brokerAddrTable是會通過②中的定時任務來更新,遍歷其中的所有Broker資訊,通過isBrokerAddrExistInTopicRouteTable方法,進行檢查:
1 private boolean isBrokerAddrExistInTopicRouteTable(final String addr) { 2 Iterator<Entry<String, TopicRouteData>> it = this.topicRouteTable.entrySet().iterator(); 3 while (it.hasNext()) { 4 Entry<String, TopicRouteData> entry = it.next(); 5 TopicRouteData topicRouteData = entry.getValue(); 6 List<BrokerData> bds = topicRouteData.getBrokerDatas(); 7 for (BrokerData bd : bds) { 8 if (bd.getBrokerAddrs() != null) { 9 boolean exist = bd.getBrokerAddrs().containsValue(addr); 10 if (exist) 11 return true; 12 } 13 } 14 } 15 16 return false; 17 }
通過比對topicRouteTable中的所有TopicRouteData儲存的BrokerAddrs來判斷,若是Broker不存在,需要進行清除,進而更新brokerAddrTable
sendHeartbeatToAllBrokerWithLock定時向Broker傳送心跳包:
1 public void sendHeartbeatToAllBrokerWithLock() { 2 if (this.lockHeartbeat.tryLock()) { 3 try { 4 this.sendHeartbeatToAllBroker(); 5 this.uploadFilterClassSource(); 6 } catch (final Exception e) { 7 log.error("sendHeartbeatToAllBroker exception", e); 8 } finally { 9 this.lockHeartbeat.unlock(); 10 } 11 } else { 12 log.warn("lock heartBeat, but failed."); 13 } 14 }
這一部分就不詳細介紹,主要還是通過Netty客戶端完成心跳包的傳送
④定時持久化消費者佇列的消費進度,這個在分析消費者時再詳細說明
⑤定時調整消費者端的執行緒池的大小,還是在分析消費者時再詳細說明
startScheduledTask建立的五個定時任務結束,回到MQClientInstance的start方法
接著開啟pullMessageService服務,為消費者拉取訊息
然後開啟rebalanceService服務,用來均衡訊息佇列
這兩個服務在有關消費者時再介紹
接著通過:
1 this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
開啟push service服務
其中defaultMQProducer是在前面MQClientInstance構造方法中建立的
1 this.defaultMQProducer = new DefaultMQProducer(MixAll.CLIENT_INNER_PRODUCER_GROUP);
只不過他呼叫的start方法,引數為false,也就是沒有呼叫mQClientFactory的start方法
後續會介紹其用途
到這DefaultMQProducerImpl的start方法已經基本完畢,只不過在最後,會通過mQClientFactory的sendHeartbeatToAllBrokerWithLock方法,給所有Broker傳送一次心跳包
到此,Producer的啟動結束