解析RocketMQ的client客戶端
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.
相關文章
- Oracle client 客戶端與sqlplusOracleclient客戶端SQL
- MySQL client客戶端的四種連線方式MySqlclient客戶端
- Linux部署rocketmq和視覺化客戶端LinuxMQ視覺化客戶端
- MQTT客戶端JAVA程式碼----fusesource mqtt-clientMQQT客戶端Javaclient
- Web端與Client客戶端資料互動方案選擇Webclient客戶端
- IdentityServer4 (1) 客戶端授權模式(Client Credentials)IDEServer客戶端模式client
- centos下安裝openvpn———客戶端client.ovpnCentOS客戶端client
- Oracle Instant Client(即時客戶端) 安裝與配置Oracleclient客戶端
- Nginx配置ssl_client_certificate客戶端認證問題Nginxclient客戶端
- Mysql:canal 客戶端 client java包依賴 v1.1.5+MySql客戶端clientJava
- 支付寶客戶端架構解析:iOS 客戶端啟動效能優化初探客戶端架構iOS優化
- Golang 學習筆記(二) - HTTP 客戶端 - 使用 Client 型別Golang筆記HTTP客戶端client型別
- RocketMQ 客戶端負載均衡機制詳解及最佳實踐MQ客戶端負載
- Cisco Secure Client 5.1.6.103 (macOS, Linux, Windows & iOS, Andrord) - 遠端訪問客戶端clientMacLinuxWindowsiOS客戶端
- 支付寶客戶端架構解析:Android 客戶端啟動速度優化之「垃圾回收」客戶端架構Android優化
- 《samba搭建win客戶端和linux客戶端的區別》Samba客戶端Linux
- dubbo客戶端客戶端
- Pulsar客戶端客戶端
- mqtt 客戶端MQQT客戶端
- MQTTJava客戶端的使用MQQTJava客戶端
- redis客戶端的使用Redis客戶端
- IE客戶客戶端程式開發的利器Bindows客戶端
- 客戶端,服務端客戶端服務端
- 服務端,客戶端服務端客戶端
- EJB客戶端如何不用任何client jar去動態呼叫遠端伺服器的EJB?客戶端clientJAR伺服器
- Nacos - 客戶端心跳續約及客戶端總結客戶端
- 分享一款非官方 Hyperf RPC 客戶端 huangdijia/jet-clientRPC客戶端client
- Redis客戶端管理軟體:Redis Desktop Client mac中文免費版Redis客戶端clientMac
- GraphQL 漸進學習 09-graphql-apollo-client-vue-客戶端開發clientVue客戶端
- 客戶端的js js指令碼的引入 js的解析過程客戶端JS指令碼
- 物理DataGuard客戶端無縫切換--客戶端TAF 配置客戶端
- [Redis 客戶端整合] Java 中常用Redis客戶端比較Redis客戶端Java
- Elasticsearch的PHP客戶端操作ElasticsearchPHP客戶端
- Python socket的客戶端Python客戶端
- zookeeper的Java客戶端APIJava客戶端API
- Ceph的客戶端安裝客戶端
- 不安裝oracle client客戶端通過plsql developer連線oracle10.2.0.4Oracleclient客戶端SQLDeveloper
- 【VMware ESXi】新版VMware Host Client獨立客戶端Beta版現已釋出。client客戶端