解析RocketMQ的client客戶端

小飛鶴發表於2017-08-28

1.解析RocketMQ的client客戶端

參考:http://www.cnblogs.com/atliwen/p/5216849.html
1.2個核心介面,3個預設實現
  interface  MQProducer
             --- DefaultMQProducer
  interface  MQConsumer
             --- DefaultMQPushConsumer
             --- DefaultMQPullConsumer
  解析:
      DefaultMQProducer是MQProducer的唯一預設實現,
      其實現 MQProducer 介面的時候 還繼承了 ClientConfig類 (客戶端配置類),可以配置如 sendMsgTimeout超時時間,producerGroup 生產者組 最大訊息容量和是否啟用壓縮等

  關係

   兩個介面

      interface MQProducer  //生產者介面

      {
             實現該介面的只有一個 預設的 DefaultMQProducer

             DefaultMQProducer 實現 MQProducer 介面的時候 還繼承了 ClientConfig類 (客戶端配置類)
                 可以配置如  sendMsgTimeout 超時時間  producerGroup  訊息最大多少 超過多少壓縮等等



             關鍵方法 :
                  send(Message) 傳送一個訊息到MQ

                     這個方法其實是呼叫 DefaultMQProducer構造方法 建立的  defaultMQProducerImpl 類物件的 send(..)方法

                    defaultMQProducerImpl 類 才是真正傳送訊息的核心類

                    defaultMQProducerImpl.send 方法 --》 sendDefaultImpl方法

                        sendDefaultImpl --》  tryToFindTopicPublishInfo 來檢測對映 佇列是否存在 是否正常
                        {
                                 final Segment<K,V>[] segments;  這個 鍵值

                                 不存在 不正常 :
                                       建立一個 TopicPublishInfo 到 segments 對映檔案 同時 將 Topic (佇列) 資訊 更新到NameServer中
                        }

                        sendDefaultImpl --》   通過設定是失敗重複次數  和  超時時間 來從新傳送訊息

                                  詳細  for (; times < 失敗重複次數 && (結束時間 - 開始時間) < 配置的超時時間; times++)

                        sendDefaultImpl --》sendKernelImpl  裝載 配置 資訊

                                        --》sendKernelImpl --》this.mQClientFactory.getMQClientAPIImpl().sendMessage()

                                             MQClientInstance mQClientFactory  物件  是在  DefaultMQProducer start啟動方式時候初始化的

                                             MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer,rpcHook);


                                        --》                 --》sendMessage
                                                              {
                                                                  MQClientInstance  --》  MQClientAPIImpl mQClientAPIImpl

                                                                       MQClientAPIImpl.sendMessage()  --> sendMessageSync
                                                                                switch (communicationMode)  同步 非同步  單向 處理  預設是 同步
                                                              }


                                          後續返回  SendResult sendResult  改型別描述當時 傳送MQ 的最終狀態


                    Message 訊息的 Topic 不能為空


             producer.shutdown(); 關閉 shutdown來清理資源,關閉網路連線,從MetaQ伺服器上登出自己

      }


       傳送訊息負載的問題
       {

          看原始碼  是通過迴圈從 namesrv 獲取的到 Topic 路由訊息 (也就是有幾個broker 每個 broker 有幾個佇列)
          然後  記錄當前傳送過的 +1


          備註 : 佇列數量 小於 消費者數量  多餘的消費者將不起做用
       }


       關於順序訊息傳送 的問題
       {
           環境: 1 下單 2 付款 3 收貨 3個狀態 ,  普通模式下 傳送到佇列中的 是輪詢佇列 將3個訊息分別傳送到多個佇列中。

           很可能會照成出現 先消費 2 在消費 1 流程錯亂的情況  當然可以業務層處理 但是業務層處理比較麻煩


           順序消費的傳送的原理 :

                  我們自己指定 訊息將要新增的佇列

                  SendResult sendResult = producer.send(msg,
                          new MessageQueueSelector()
                          {
                              @Override
                              public MessageQueue select(List<MessageQueue> mqs,
                                      Message msg, Object arg)
                              {
                                  Integer id = (Integer) arg;
                                  int index = id % mqs.size(); // 通過取於來 講 同一個訂單號 訪入同一個佇列中
                                  // 前提是 佇列數量沒有變動
                                  return mqs.get(index);

                              }
                          }, “10001”); // orderID “10001”是傳遞給回撥方法的 自定義資料


                      List<MessageQueue> mqs 就是從namesrv 獲取的所有佇列
       }



      備註
              // 訂單 Id
              String orderId = "20034568923546";
              message.setKeys(orderId);
              // Keys
              每個訊息在業務局面的唯一標識碼   通過 topic,key 來查詢返條訊息內容,以及訊息被誰消費
              查詢的時候 非常重要



      消費者
      interface MQConsumer
      {

          // 回溯消費
          {
              mqadmin resetOffsetByTime 命令
              改方式 是通過消費的日誌來恢復的  但是隻能通過 消費的組來恢復  恢復訊息後 也只能用改組來從新消費

              -s : 時間戳的問題  可以是 毫秒 或者是從什麼時候開始

          }



          //拉取模式
          interface MQPullConsumer:
          {



          }


          // 接收模式

          長輪詢模式  一次獲取一批 訊息


          記錄
               批量和單條 內部實現   還是獲取了所有的 可以獲取到的佇列訊息 放入集合中 判斷集合大小是否 大於設定的單次消費數量

               小於
                  直接將其 訊息集合 放入執行回撥方法中
               大於
                  使用的是For 迴圈 來單條處理


          interface MQPushConsumer:
          {
              class DefaultMQPushConsumer  extends ClientConfig implements MQPushConsumer

              DefaultMQPushConsumer 包含很多可以配置的資訊   詳情見文件
              其中最主要的 有幾個
                   messageModel  訊息模型   支援以下兩種  1、叢集消費 2、廣播消費
                  messageListener  訊息監聽器
                  consumeThreadMin 消費執行緒池數量 預設10
                  consumeThreadMax 消費執行緒池數量 預設20

                  重要的是  消費執行緒池 !  這就說明  我釋出一個 消費應用 消費邏輯就可以 N 個 處理! 不用自己搞了有沒有!!
                  安預設的來算  20個消費邏輯  可以配置  而且還 可以橫向 增加 消費應用  只要保持是一個組就可以了

                  難怪會在文件中 特意話一個 效能圖啊!!




              應用通常吐 Consumer 物件註冊一個 Listener 介面,一旦收到訊息,Consumer 物件立刻回撥 Listener 介面方法

              MessageListenerOrderly 這個是有序的
              MessageListenerConcurrently 這個是無序的

              關鍵方法 DefaultMQPushConsumer registerMessageListener(new implements MessageListenerConcurrently)
              {
                  public void registerMessageListener(MessageListenerConcurrently messageListener) {
                      this.messageListener = messageListener; // 給自己複製一個 消費邏輯類物件  方法後續查詢 替換修改等

                      關鍵方法
                        this.defaultMQPushConsumerImpl.registerMessageListener(messageListener);
                      // 將消費邏輯類告訴 呼叫者類
                  }
              }

              關鍵方法 start

              DefaultMQPushConsumer.start()  -->  DefaultMQPushConsumerImpl.start()
              {
                   this.serviceState 來記錄設定當前程式執行狀態  來做多型

                   checkConfig() 檢查配置 初始化賦值

                   copySubscription() 拷貝訂閱者資訊 賦值 消費邏輯類


                   // 有就獲取 沒有就建立一個
                   this.mQClientFactory =  MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQPushConsumer,this.rpcHook);



                   接著初始化一系列資訊

                   // 載入消費進度
                   this.offsetStore.load();
                   // 該方法有兩個實現

                   一個是本地 this.readLocalOffset() 獲取資料
                   {
                       //獲取檔案字串
                       String content = MixAll.file2String(this.storePath);
                       OffsetSerializeWrapper offsetSerializeWrapper =OffsetSerializeWrapper.fromJson(content, OffsetSerializeWrapper.class);

                          可以看出 淘寶使用的是JSON

                   }




                   if (this.getMessageListenerInner() instanceof MessageListenerOrderly)
                   else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently)
                   判斷 消費邏輯類 實現那個介面 建立對應的 ConsumeMessageOrderlyService 物件

                  ConsumeMessageConcurrentlyService  該實現為空


                  本地
                  ConsumeMessageOrderlyService.start()
                  {



                       建立並執行一個週期性的動作成為了第一個在給定的初始延遲之後,隨後用給定的時期,這是執行後將開始initialDelay然後initialDelay +,然後initialDelay + 2 *時期,等等。如果任何執行任務遇到異常,後續執行的鎮壓。否則,只會終止的任務通過取消或終止執行器。如果執行這個任務花費的時間比其期,然後後續執行可能會遲到,但不會同時執行。
                       //就是一個定時器

                       this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                                          @Override
                                          public void run() {
                                              ConsumeMessageOrderlyService.this.lockMQPeriodically();
                                          }
                      }, 1000 * 1, ProcessQueue.RebalanceLockInterval, TimeUnit.MILLISECONDS);

                      scheduleAtFixedRate 應該是一個執行緒池管理

                      不用去關心 scheduleAtFixedRate 方法  看  ConsumeMessageOrderlyService.this.lockMQPeriodically()
                      {

                          this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll()
                          是
                            RebalanceImpl.lockAll() //  將讀取到的訊息上鎖
                      }


                  }


                  // 最關鍵的服務啟動了
                  // 正在的啟動了
                  mQClientFactory.start();
                  {
                       synchronized (this){

                              //Start request-response channel  啟動請求-響應通道
                              this.mQClientAPIImpl.start();
                              //Start various schedule tasks    開始各種安排任務  啟動定時任務   其中就包含 獲取到MQ訊息消費的 回撥方法
                              this.startScheduledTask();
                              //Start pull service              開始拉取服務
                              this.pullMessageService.start();
                              //Start rebalance service         啟動負載均衡  //  該服務非常重要
                              this.rebalanceService.start();
                              //Start push service              開始推動服務
                              this.defaultMQProducer.getDefaultMQProducerImpl().start(false);
                       }
                  }
              }


          }

          指定 group
          訂閱 topic
          註冊訊息監聽處理器,當訊息到來時消費訊息
          消費端 Start

              複製訂閱關係

              初始化 rebalance 變數

              構建 offsetStore 消費進度儲存物件

              啟動消費訊息服務

              向 mqClientFactory 註冊本消費者

              啟動 client 端遠端通訊

              * 載入消費進度 Loand()

              * 啟動定時任務

                  定時獲取 nameserver 地址

                  定時從 nameserver 獲取 topic 路由資訊

                  定時清理下線的 borker

                  定時向所有 broker 傳送心跳資訊, (包括訂閱關係)

                  * 定時持久化 Consumer 消費進度(廣播儲存到本地,叢集儲存到 Broker)
                  PS:  這裡也是是個關鍵   持久化消費進度 是用來記錄當前 組的消費情況 可以做到 回溯消費  和當機等情況下 啟動後接著上次執行消費

                  統計資訊打點

                  動態調整消費執行緒池

              啟動拉訊息服務 PullMessageService

              啟動消費端負載均衡服務 RebalanceService

              從 namesrv 更新 topic 路由資訊

              向所有 broker 傳送心跳資訊, (包括訂閱關係)

              喚醒 Rebalance 服務執行緒


      }


      // 有些懶得看了 直接看別人 得了

       消費端負載均衡
      {
         這個也是個重點

         消費端會通過 RebalanceService 執行緒,10 秒鐘做一次基於 topic 下的所有佇列負載

         消費端 遍歷自己所有的 Topic 獲取 Topic 下所有的 佇列  (一個Topic 包含對個佇列  預設是 4 個 有別於其他MQ )

         從 broker 獲取當前 組(group)的所有消費端( 有心跳的)
         獲取佇列集合Set<MessageQueue> mqSet
         現在佇列分配策略例項  AllocateMessageQueueStrategy 執行分配演算法
         {
            1:平均分配演算法 :
                  其實是類似於分頁的演算法
                  將所有 queue 排好序類似於記錄
                  將所有消費端 consumer 排好序,相當於頁數
                  然後獲取當前 consumer 所在頁面應該分配到的 queue
            2:按照配置來分配佇列 :
                 消費服務啟動的時候 就指定好了要消費的 是哪個佇列
            3:按照機房來配置佇列 :
                  Consumer 啟動的時候會指定在哪些機房的訊息  (應該是指定 broker)
                  獲取指定機房的 queue
                  然後在執行如 1)平均演算法
         }

          根據分配佇列的結果更新 ProccessQueueTable<MessageQueue, ProcessQueue>
          {
              比對 mqSet 將多餘的佇列刪除, 當 broker 當機或者新增,會導致分配到 mqSet 變化,

              新增新增佇列, 比對 mqSet,給新增的 messagequeue 構建長輪詢物件 PullRequest 物件,會從 broker 獲取消費的進度 構建這個佇列的 ProcessQueue
              將 PullRequest 物件派發到長輪詢拉訊息服務(單執行緒非同步拉取)


              注:ProcessQueue 正在被消費的佇列,

                          (1) 長輪詢拉取到訊息都會先儲存到 ProcessQueue 的 TreeMap<Long, MessageExt> 集合中,消費調後會刪除掉,用來控制 consumer 訊息堆積,  TreeMap<Long, MessageExt> key 是訊息在此 ConsumeQueue 佇列中索引

                          (2) 對於順序訊息消費 處理

                          locked 屬性:當 consumer 端向 broker 申請鎖佇列成功後設定 true,  只有被鎖定 的 processqueue 才能被執行消費  rollback: 將消費在 msgTreeMapTemp 中的訊息,放回 msgTreeMap 重新消費

                          commit: 將臨時表 msgTreeMapTemp 資料清空,代表消費完成,放回最大偏移
                          值

                          (3) 這裡是個 TreeMap,對 key 即訊息的 offset 進行排序,這個樣可以使得訊息進 行順序消費
          }
      }

      長輪詢
      {
                      Rocketmq 的訊息是由 consumer 端主動到 broker拉取的, consumer 向 broker 傳送拉訊息
                      請求, PullMessageService 服務通過一個執行緒將阻塞佇列 LinkedBlockingQueue<PullRequest>
                      中的 PullRequest 到 broker 拉取訊息

                      DefaultMQPushConsumerImpl 的 pullMessage(pullRequest)方法執行向 broker 拉訊息動作
                      1. 獲取 ProcessQueue 判讀是否 drop 的, drop 為 true 返回
                      2. 給 ProcessQueue 設定拉訊息時間戳
                      3. 流量控制,正在消費佇列中訊息(未被消費的)超過閥值,稍後在執行拉訊息
                      4. 流量控制,正在消費佇列中訊息的跨度超過閥值(預設 2000) ,稍後在消費
                      5. 根據 topic 獲取訂閱關係
                      6. 構建拉訊息回撥物件 PullBack, 從 broker 拉取訊息(非同步拉取)返回結果是回撥
                      7. 從記憶體中獲取 commitOffsetValue  //TODO 這個值跟 pullRequest.getNextOffset 區別
                      8. 構建 sysFlag   pull 介面用到的 flag
                      9. 調底層通訊層向 broker 傳送拉訊息請求
                      如果 master 壓力過大,會建議去 slave 拉取訊息
                      如果是到 broker 拉取訊息清楚實時提交標記位,因為 slave 不允許實時提交消費進
                      度,可以定時提交
                      //TODO 關於 master 拉訊息實時提交指的是什麼?
                      10. 拉到訊息後回撥 PullCallback
                      處理 broker 返回結果 pullResult

                      更新從哪個 broker(master 還是 slave)拉取訊息

                      反序列化訊息

                      訊息過濾

                      訊息中放入佇列最大最小 offset, 方便應用來感知訊息堆積度
                      將訊息加入正在處理佇列 ProcessQueue
                      將訊息提交到消費訊息服務 ConsumeMessageService
                      流控處理, 如果 pullInterval 引數大於 0 (拉訊息間隔,如果為了降低拉取速度,
                      可以設定大於 0 的值) , 延遲再執行拉訊息,  如果 pullInterval 為 0 立刻在執行拉
                      訊息動作


              看圖 人家花的不錯 很明瞭
      }

      push 訊息
      {

        PS:
             長輪詢向broker拉取訊息是批量拉取的,  預設設定批量的值為pullBatchSize = 32, 可配置

            消費端 consumer 構建一個消費訊息任務 ConsumeRequest 消費一批訊息的個數是 可配置的 consumeMessageBatchMaxSize = 1, 預設批量個數為一個
                也就是說 每次傳遞給回撥方法的 引數 訊息集合 的解釋

            ConsumeRequest 任務 run 方法執行


              判斷 proccessQueue 是否被 droped 的, 廢棄直接返回,不在消費訊息

              構建並行消費上下文

              給訊息設定消費失敗時候的 retry topic,當訊息傳送失敗的時候傳送到 topic
              為%RETRY%groupname 的佇列中


              調 MessageListenerConcurrently 監聽器的 consumeMessage 方法消費訊息,返回消
              費結果

              如果 ProcessQueue 的 droped 為 true,不處理結果,不更新 offset, 但其實這裡消
              費端是消費了訊息的,這種情況感覺有被重複消費的風險

              處理消費結果  :

                      消費成功,  對於批次消費訊息, 返回消費成功並不代表所有訊息都消費成功,
                      但是消費訊息的時候一旦遇到消費訊息失敗直接放回,根據 ackIndex 來標記
                      成功消費到哪裡了


                      消費失敗, ackIndex 設定為-1
                      廣播模式傳送失敗的訊息丟棄, 廣播模式對於失敗重試代價過高,對整個集
                      群效能會有較大影響,失敗重試功能交由應用處理
                      叢集模式, 將消費失敗的訊息一條條的傳送到 broker 的重試佇列中去,如果
                      此時還有傳送到重試佇列傳送失敗的訊息, 那就在 cosumer 的本地執行緒定時 5
                      秒鐘以後重試重新消費訊息, 在走一次上面的消費流程。


              刪除正在消費的佇列 processQueue 中本次消費的訊息,放回消費進度


              更新消費進度, 這裡的更新只是一個記憶體 offsetTable 的更新,後面有定時任務定
              時更新到 broker 上去


        PS:
              關於消費成功 和 失敗的  問題

                  在叢集模式下   回撥方法設定為消費失敗  會將當前消費的失敗訊息 傳送到 broker 的容錯度列中  等待N次+ 從新消費 。


        push 消費-順序消費訊息

                          順序消費服務 ConsumeMessageConcurrentlyService 構建的時候

                          構建一個執行緒池來接收消費請求 ConsumeRequest

                          構建一個單執行緒的本地執行緒, 用來稍後定時重新消費 ConsumeRequest, 用來執行
                          定時週期性(一秒)鍾鎖佇列任務

                          週期性鎖佇列 lockMQPeriodically

                          獲取正在消費佇列列表 ProcessQueueTable 所有 MesssageQueue,  構建根據 broker
                          歸類成 MessageQueue 集合 Map<brokername, Set<MessageQueue>>

                          遍歷 Map<brokername, Set<MessageQueue>>的 brokername, 獲取 broker 的 master
                          機器地址, 將brokerName的Set<MessageQueue>傳送到broker請求鎖定這些佇列。  在broker
                          端鎖定佇列,其實就是在 broker 的 queue 中標記一下消費端,表示這個 queue 被某個 client
                          鎖定。 Broker 會返回成功鎖定佇列的集合, 根據成功鎖定的 MessageQueue,設定對應的正
                          在處理佇列 ProccessQueue 的 locked 屬性為 true 沒有鎖定設定為 false

                          通過長輪詢拉取到訊息後會提交到訊息服務 ConsumeMessageOrderlyService,
                          ConsumeMessageOrderlyService 的 submitConsumeRequest 方法構建 ConsumeRequest 任務提
                          交到執行緒池。ConsumeRequest 是由 ProcessQueue 和 Messagequeue 組成。

                          ConsumeRequest 任務的 run 方法

                          判斷 proccessQueue 是否被 droped 的, 廢棄直接返回,不在消費訊息

                          每個 messagequeue 都會生成一個佇列鎖來保證在當前 consumer 內,同一個佇列序列
                          消費,

                          判斷 processQueue 的 lock 屬性是否為 true, lock 屬性是否過期, 如果為 false 或者過期,
                          放到本地執行緒稍後鎖定在消費。 如果 lock 為 true 且沒有過期,開始消費訊息

                          計算任務執行的時間如果大於一分鐘且執行緒數小於佇列數情況下,將 processqueue,
                          messagequeue 重新構建 ConsumeRequest 加到執行緒池 10ms 後在消費,這樣防止個別佇列被
                          餓死

                          獲取客戶端的消費批次個數,預設一批次為一條

                          從 proccessqueue 獲取批次訊息,  processqueue.takeMessags(batchSize) , 從 msgTreeMap
                          中移除訊息放到臨時 map 中 msgTreeMapTemp, 這個臨時 map 用來回滾訊息和 commit 消
                          息來實現事物消費

                          調回撥介面消費訊息,返回狀態物件 ConsumeOrderlyStatus

                          根據消費狀態,處理結果
                          1) 非事物方式,自動提交
                          訊息訊息狀態為 success: 呼叫 processQueue.commit 方法

                          獲取 msgTreeMapTemp 的最後一個 key,表示提交的 offset

                          清空 msgTreeMapTemp 的訊息,已經成功消費
                          2) 事物提交,由使用者來控制提交回滾(精衛專用)
                               更新消費進度,  這裡的更新只是一個記憶體 offsetTable 的更新, 後面有定時任務定時更
                          新到 broker 上去
      }

          關閉
          {

           shutdown

                  DefaultMQPushConsumerImpl  關閉消費端

                  關閉消費執行緒

                  將分配到的 Set<MessageQueue>的消費進度儲存到 broker
                  利 用
                  DefaultMQPushConsumerImpl
                  獲 取
                  ProcessQueueTable<MessageQueue,
                  ProcessQueue>的 keyset 的 messagequeue 去獲取
                  RemoteBrokerOffsetStore.offsetTable<MessageQueue, AutomicLong>Map 中的消費進
                  度,
                  offsetTable 中 的 messagequeue 的 值, 在 update 的時候如果 沒有對應 的
                  Messagequeue 會構建, 但是也會 rebalance 的時候將沒有分配到的 messagequeue
                  刪除
                  rebalance 會將 offsettable 中沒有分配到 messagequeue 刪除,  但是在從 offsettable
                  刪除之前會將 offset 儲存到 broker

                  Unregiser 客戶端

                  pullMessageService 關閉

                  scheduledExecutorService 關閉,關閉一些客戶端的起的定時任務

                  mqClientApi 關閉

                  rebalanceService 關閉
          }

   補充 一
  訊息的延遲
      {
          通過測試程式可以看出  通過設定 message 的DelayTimeLevel 可以設定訊息延遲處理

      }

      訊息重試機制  容錯機制
      {

          通過原始碼可以看出 消費方法的返回物件 只有兩個值

          CONSUME_SUCCESS // 消費成功

          RECONSUME_LATER // 消費失敗,稍後重試


          CONSUME_SUCCESS 無異議

          關鍵是 RECONSUME_LATER

              我們可以通過 RECONSUME_LATER 來容錯。 阿里提供的這個  重試機制 是通過新增到一個錯誤佇列中 設定期  DelayTimeLevel 來實現的

              第一次消費的時候  列印 MessageExt 沒有 properties屬性的詳細資訊  返回 RECONSUME_LATER 稍後重試

              [queueId=0, storeSize=106, queueOffset=0, sysFlag=0, bornTimestamp=1458803327047, bornHost=/10.10.12.27:41697, storeTimestamp=1458803327059, storeHost=/10         .10.12.27:10911, msgId=0A0A0C1B00002A9F0000000000031F10, commitLogOffset=204560, bodyCRC=910247988, reconsumeTimes=0, preparedTransactionOffset=0, toStrin         g()=Message [topic=Topic2, flag=0, properties={MAX_OFFSET=1, MIN_OFFSET=0}, body=9]]

              第二次消費的時候

              [queueId=0, storeSize=260, queueOffset=0, sysFlag=0, bornTimestamp=1458803327047, bornHost=/10.10.12.27:41697, storeTimestamp=1458803516104, storeHost=/10         .10.12.27:10911, msgId=0A0A0C1B00002A9F0000000000032079, commitLogOffset=204921, bodyCRC=910247988, reconsumeTimes=1, preparedTransactionOffset=0, toStrin         g()=Message [topic=Topic2, flag=0, properties={ORIGIN_MESSAGE_ID=0A0A0C1B00002A9F0000000000031F10, DELAY=3, REAL_TOPIC=%RETRY%ConsumerGroupName, WAIT=fals         e, RETRY_TOPIC=Topic2, MAX_OFFSET=1, MIN_OFFSET=0, REAL_QID=0}, body=9]]

              可以看出 訊息  雖然  queueId 是相同的值 0 但是  msgId 卻變了 ! 同時用rocketmq-console 來監控 該 消費者 你會發現 多了個 Topic  %RETRY%ConsumerGroupName

              所有 可以得出一個結論

              我們返回 消費失敗,稍後重試  RECONSUME_LATER  訊息會回到 broker 同時建立一條相同的訊息 訪如   %RETRY%ConsumerGroupName
              同時 設定 該 訊息的 延遲消費 每次延遲時間 +1

              我覺得可以通過 reconsumeTimes 來做一個簡單的容錯  獲取 當前消費的 次數  是否大於 設定值   大於就說明其是死信  記錄到異常資料庫
      }

      
  備註問題:
      背景:
            生產端 使用 linux 伺服器 (UTF-8 編碼)
                 Message me = new Message();
                                   me.setBody("中國人".getBytes());
                                   producer.send(me);
            消費端 使用   Windows 伺服器 (GBK 編碼)
                                   MessageExt msg = msgs.get(0);
                                   String strBody = new String(msg.getBody());
             問題 :
                         生產端無問題,消費端 存在 字符集 編碼問題 。

             原因 :
                         生產端傳送給MQ 的資料是 位元組 !  getBytes() 不指定位元組格式 會預設使用 本地系統編碼格式  linux下通常是 UTF-8 格式
                         消費端由於是Windows 本地系統的編碼格式是 GBK 格式 。
                         new String(msg.getBody()); 方法 不指定編碼格式 使用的也是 本地系統編碼格式 也就是 GBK格式

                         可能會說 直接 GBK轉換UTF-8就好了,但是! GBK 對應的是2個位元組  UTF-8 對應的是3個位元組  當出現 3個字的中文或者 特殊符號的時候
                         轉換過程中會 主動 2補1 所有 “中國人”  這裡 人 字就會亂碼

                         String iso = new String(strBody.getBytes("UTF-8"), "ISO-8859-1");
                           strBody = new String(iso.getBytes("ISO-8859-1"), "UTF-8");

                         上面這種解決方法在 測試方法中有效  在消費端 具體消費類中的消費方法 並未生效

                         這裡希望有大神可以指出為什麼!?

             解決方法:

                         MessageExt msg = msgs.get(0);
                         strBody = new String(msg.getBody(), "UTF-8");

                         在第一次 位元組轉換成字串的時候 就指定 該位元組按照 UTF-8 格式轉換!

               PS:
                         雖然解決方法很簡單,但是 稍微不注意 就會跳過這裡啊  勁量做到統一開發環境啊!
   

        消費端 多例項問題
        經過試驗,一個消費 組 只能處理一個 Topic 下的一個 Tags  !
       




2.

相關文章