RocketMQ - 生產者啟動流程

VipSoft發表於2023-02-20

生產者啟動流程

image
DefaultMQProducer是RocketMQ中預設的生產者實現

核心屬性:

namesrvAddr: 繼承自 ClientConfig,表示 RocketMQ 叢集的Namesrv 地址,如果是多個則用分號分開。比如:127.0.0.1:9876;127.0.0.2:9876。

clientIP: 使用的客戶端程式所在機器的 IP地址。支援 IPv4和IPv6,IPv4 排除了本地的環回地址(127.0.xxx.xxx)和私有內網地址(192.168.xxx.xxx)。這裡需要注意的是,如果 Client 執行在Docker 容器中,獲取的 IP 地址是容器所在的 IP 地址,而非宿主機的IP地址。

instanceName: 例項名,每個例項都需要取唯一的名字,因為有時我們會在同一個機器上部署多個程式程式,如果名字有重複就會導致啟動失敗。

vipChannelEnabled: 這是一個 boolean 值,表示是否開啟 VIP通道。VIP 通道和非VIP通道的區別是:在通訊過程中使用的埠號不同。

clientCallbackExecutorThreads: 客戶端回撥執行緒數。該參數列示 Netty 通訊層回撥執行緒的個數 ,預設值 Runtime.getRuntime().availableProcessors()表示當前CPU的有效個數。

pollNameServerInterval: 獲取 Topic 路由資訊的間隔時長,單位為 ms,預設為30 000ms。

heartbeatBrokerInterval: 與Broker心跳間隔的時長,單位為 ms,預設為30 000ms。

defaultMQProducerImpl: 預設生產者的實現類,其中封裝了Broker的各種API(啟動及關閉生產者的介面)。如果你想自己實現一個生產者,可以新增一個新的實現,保持DefaultMQProducer對外介面不變,使用者完全沒有感知。

producerGroup: 生產者組名,這是一個必須傳遞的引數。RocketMQ-way表示同一個生產者組中的生產者例項行為需要一致。

sendMsgTimeout: 傳送超時時間,單位為ms。

compressMsgBodyOverHowmuch: 訊息體的容量上限,超過該上限時訊息體會透過ZIP進行壓縮,該值預設為4MB。

retryTimesWhenSendFailed: 同步傳送失敗後重試的次數。預設為2次,也就是說,一共有3次傳送機會。

retryTimesWhenSendAsyncFailed: 非同步傳送失敗後重試的次數。預設為2次。非同步重試是有條件的重試,並不是每次傳送失敗後都重試 。 原始碼可以檢視 org.apache.rocketmq.client.impl.MQClientAPIImpl.sendMessageAsync()方法 。 每次傳送失敗丟擲異常後 , 透過執行onExceptionImpl()方法來決定什麼場景進行重試

核心方法

start():這是啟動整個生產者例項的入口,主要負責校驗生產者的配置引數是否正確,並啟動通訊通道、各種定時計劃任務、Pull服務、Rebalance服務、註冊生產者到Broker等操作。

shutdown(): 關閉本地已註冊的生產者,關閉已註冊到Broker的客戶端。

fetchPublishMessageQueues(Topic): 獲取一個Topic有哪些Queue。在傳送訊息、Pull訊息時都需要呼叫。

send(Message msg): 同步傳送普通訊息。

send(Message msg,long timeout): 同步傳送普通訊息(超時設定)。

send(Message msg,SendCallback sendCallback): 非同步傳送普通訊息。

send(Message msg , SendCallback sendCallback , long timeout): 非同步傳送普通訊息(超時設定)。

sendOneway(Message msg): 傳送單向訊息。只負責傳送訊息,不管傳送結果。

send(Message msg,MessageQueue mq): 同步向指定佇列傳送訊息。

send(Message msg,MessageQueue mq,long timeout): 同步向指定佇列傳送訊息(超時設定)。同步向指定佇列傳送訊息時,如果只有一個傳送執行緒,在傳送到某個指定佇列中時,這個指定佇列中的訊息是有順序的,那麼就按照傳送
時間排序;如果某個Topic的佇列都是這種情況,那麼我們稱該Topic的全部訊息是分割槽有序的。

send(Message msg , MessageQueue mq , SendCallback sendCallback): 非同步傳送訊息到指定佇列。

send(Message msg , MessageQueue mq , SendCallback sendCallback,long timeout): 非同步傳送訊息到指定佇列(超時設定)。

send(Message msg,MessageQueueSelector selector,Object arg,SendCallback sendCallback): 自定義訊息傳送到指定佇列。透過實現MessageQueueSelector介面來選擇將訊息傳送到哪個佇列。

send(Collection<Message>msgs): 批次傳送訊息。

核心管理介面

createTopic(String key , String newTopic , int queueNum): 建立Topic。
viewMessage(String offsetMsgId): 根據訊息id查詢訊息內容。

啟動流程

生產者啟動的流程比消費者啟動的流程更加簡單,一般使用者使用DefaultMQProducer的建構函式構造一個生產者例項,並設定各種引數。比如Namesrv地址、生產者組名等,呼叫start()方法啟動生產者例項,start()方法呼叫了生產者預設實現類的start()方法啟動,這裡我們主要講實現類的start()方法內部是怎麼實現的,
image
第一步: 透過 switch-case 判斷當前生產者的服務狀態,建立時預設狀態是CREATE_JUST。設定預設啟動狀態為啟動失敗。
第二步: 執行 DefaultMQProducerImpl.checkConfig()方法。校驗生產者例項設定的各種引數。比如生產者組名是否為空、是否滿足命名規則、長度是否滿足等。
第三步: 執行 DefaultMQProducer.changeInstanceNameToPID()方法。校驗instancename,如果是預設名字則將其修改為程式id。
第四步: 執行 MQClientManager.getOrCreateMQClientInstance()方法。根據生產者組名獲取或者初始化一個

MQClientInstance例項與 clientId是一一對應的,而clientId是由clientIP、instanceName 及 unitName 構成的。因此,為了減少客戶端的使用資源,如果將所有的 instanceName和 unitName設定為同樣的值,就會只建立一個 MQClientInstance例項

ClientConfig.java

public String buildMQClientId() {
    StringBuilder sb = new StringBuilder();
    sb.append(this.getClientIP());
    sb.append("@");
    sb.append(this.getInstanceName());
    if (!UtilAll.isBlank(this.unitName)) {
        sb.append("@");
        sb.append(this.unitName);
    }
    if (enableStreamRequestType) {
        sb.append("@");
        sb.append(RequestType.STREAM);
    }
    return sb.toString();
}

MQClientInstance 例項的功能是管理本例項中全部生產者與消費者的生產和消費行為 。
org.apache.rocketmq.client.impl.factory.MQClientInstance

類的核心屬性

public class MQClientInstance { 
    ...
    private final ClientConfig clientConfig;
    private final int instanceIndex;
    private final String clientId;
    private final long bootTimestamp = System.currentTimeMillis();
    //當前client例項的全部生產者的內部例項。
    private final ConcurrentMap<String/* group */, MQProducerInner> producerTable = new ConcurrentHashMap<String, MQProducerInner>();
    //當前client例項的全部消費者的內部例項。
    private final ConcurrentMap<String/* group */, MQConsumerInner> consumerTable = new ConcurrentHashMap<String, MQConsumerInner>();
    //當前client例項的全部管理例項。
    private final ConcurrentMap<String/* group */, MQAdminExtInner> adminExtTable = new ConcurrentHashMap<String, MQAdminExtInner>();
    private final NettyClientConfig nettyClientConfig;
    //其實每個client也是一個Netty Server,也會支援Broker訪問,這裡實現了全部client支援的介面
    private final MQClientAPIImpl mQClientAPIImpl;
    //管理介面的本地實現類
    private final MQAdminImpl mQAdminImpl;
    //當前生產者、消費者中全部Topic的本地快取路由資訊
    private final ConcurrentMap<String/* Topic */, TopicRouteData> topicRouteTable = new ConcurrentHashMap<String, TopicRouteData>();
    private final Lock lockNamesrv = new ReentrantLock();
    private final Lock lockHeartbeat = new ReentrantLock();
    private final ConcurrentMap<String/* Broker Name */, HashMap<Long/* brokerId */, String/* address */>> brokerAddrTable =
        new ConcurrentHashMap<String, HashMap<Long, String>>();
    private final ConcurrentMap<String/* Broker Name */, HashMap<String/* address */, Integer>> brokerVersionTable =
        new ConcurrentHashMap<String, HashMap<String, Integer>>();
    //本地定時任務,比如定期獲取當前 Namesrv 地址、定期同步Namesrv資訊、定期更新Topic路由資訊、定期傳送心跳資訊給Broker、定期清理已下線的Broker、
    //定期持久化消費位點、定期調整消費執行緒數(這部分原始碼被官方刪除了)
    private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r, "MQClientFactoryScheduledThread");
        }
    });
    //請求的處理器,從處理方法 processRequest() 中我們可以知道目前支援哪些功能介面
    private final ClientRemotingProcessor clientRemotingProcessor;
    //Pull服務
    private final PullMessageService pullMessageService;
    //重新平衡服務。定期執行重新平衡方法this.mqClientFactory.doRebalance()。
    //這裡的 mqClientFactory 就是 MQClientInstance 例項,透過依次呼叫MQClientInstance中儲存
    //的消費者例項的doRebalance()方法,來感知訂閱關係的變化、叢集變化等,以達到重新平衡。
    private final RebalanceService rebalanceService;
    private final DefaultMQProducer defaultMQProducer;
    //消費監控 。 比如拉取 RT(Response Time,響應時間)、拉取TPS(Transactions Per Second,每秒處理訊息數)、消費RT等都可以統計。
    private final ConsumerStatsManager consumerStatsManager;
 }

類的核心方法

public class MQClientInstance {  
    //從多個Namesrv中獲取最新Topic路由資訊,更新本地快取
    public void updateTopicRouteInfoFromNameServer(){}
    //清理已經下線的Broker
    private void cleanOfflineBroker(){}
    //檢查Client是否在Broker中有效
    private void checkClientInBroker(){}
    //傳送客戶端的心跳資訊給所有的Broker。
    public void sendHeartbeatToAllBrokerWithLock() {}
    //在本地註冊一個消費者。
    public synchronized boolean registerConsumer(final String group, final MQConsumerInner consumer) {}
    //取消本地註冊的消費者。
    public synchronized void unregisterConsumer(final String group) {}
    //在本地註冊一個生產者。
    public synchronized boolean registerProducer(final String group, final DefaultMQProducerImpl producer) {}
    //取消本地註冊的生產者。
    public synchronized void unregisterProducer(final String group){}
    //註冊一個管理例項。
    public boolean registerAdminExt(final String group, final MQAdminExtInner admin)
    //立即執行一次 Rebalance。該操作是透過 RocketMQ 的一個CountDownLatch2鎖來實現的。
    public void rebalanceImmediately(){}
    //對於所有已經註冊的消費者例項 ,執行一次Rebalance。
    public void doRebalance(){}
    //在本地快取中查詢Slave Broker資訊
    public FindBrokerResult findBrokerAddressInSubscribe(
        final String brokerName,
        final long brokerId,
        final boolean onlyThisBroker
    ){}
    //在本地快取中查詢Master Broker地址。
    public String findBrokerAddressInPublish(final String brokerName){}
    //查詢消費者id列表。
    public List<String> findConsumerIdList(final String topic, final String group) {}
    //透過Topic名字查詢Broker地址。
    public String findBrokerAddrByTopic(final String topic) {}
    //重置消費位點。
    public synchronized void resetOffset(String topic, String group, Map<MessageQueue, Long> offsetTable){}
    //獲取一個訂閱關係中每個佇列的消費進度。
    public Map<MessageQueue, Long> getConsumerStatus(String topic, String group){}
    //獲取本地快取Topic路由。
    public ConcurrentMap<String, TopicRouteData> getTopicRouteTable(){}
    //直接將訊息傳送給指定的消費者消費,和正常投遞不同的是,指定了已經訂閱的消費者組中的一個,
    //而不是全部已經訂閱的消費者。一般適用於在消費訊息後,某一個消費者組想再消費一次的場景。    
    public ConsumeMessageDirectlyResult consumeMessageDirectly(final MessageExt msg,
        final String consumerGroup,
        final String brokerName) {}
    //獲取消費者的消費統計資訊。包含消費RT、消費TPS等。
    public ConsumerRunningInfo consumerRunningInfo(final String consumerGroup){}
 } 

相關文章