RocketMQ 自己的整理和理解

atliwen發表於2016-02-25

每個人的想法不同, RocketMQ 介紹的時候就說 是阿里從他們使用的上 解耦出來 近一步簡化 便捷的 目的當然是 讓其能快速入手和開發

如果不是在專案設計層面上 只是使用的話 從Git上下載該專案的原始碼 其中有一個包是專門的測試 例項的 只需要照貓畫虎 使用就可以了


1
不能有中文路徑! 2 3 不能有中文路徑! 4 5 不能有中文路徑! 6 7 8 關係 9 10 兩個介面 11 12 interface MQProducer //生產者介面 13 14 { 15 實現該介面的只有一個 預設的 DefaultMQProducer 16 17 DefaultMQProducer 實現 MQProducer 介面的時候 還繼承了 ClientConfig類 (客戶端配置類) 18 可以配置如 sendMsgTimeout 超時時間 producerGroup 訊息最大多少 超過多少壓縮等等 19 20 21 22 關鍵方法 : 23 send(Message) 傳送一個訊息到MQ 24 25 這個方法其實是呼叫 DefaultMQProducer構造方法 建立的 defaultMQProducerImpl 類物件的 send(..)方法 26 27 defaultMQProducerImpl 類 才是真正傳送訊息的核心類 28 29 defaultMQProducerImpl.send 方法 --》 sendDefaultImpl方法 30 31 sendDefaultImpl --》 tryToFindTopicPublishInfo 來檢測對映 佇列是否存在 是否正常 32 { 33 final Segment<K,V>[] segments; 這個 鍵值 34 35 不存在 不正常 : 36 建立一個 TopicPublishInfo 到 segments 對映檔案 同時 將 Topic (佇列) 資訊 更新到NameServer中 37 } 38 39 sendDefaultImpl --》 通過設定是失敗重複次數 和 超時時間 來從新傳送訊息 40 41 詳細 for (; times < 失敗重複次數 && (結束時間 - 開始時間) < 配置的超時時間; times++) 42 43 sendDefaultImpl --》sendKernelImpl 裝載 配置 資訊 44 45 --》sendKernelImpl --》this.mQClientFactory.getMQClientAPIImpl().sendMessage() 46 47 MQClientInstance mQClientFactory 物件 是在 DefaultMQProducer start啟動方式時候初始化的 48 49 MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQProducer,rpcHook); 50 51 52 --》 --》sendMessage 53 { 54 MQClientInstance --》 MQClientAPIImpl mQClientAPIImpl 55 56 MQClientAPIImpl.sendMessage() --> sendMessageSync 57 switch (communicationMode) 同步 非同步 單向 處理 預設是 同步 58 } 59 60 61 後續返回 SendResult sendResult 改型別描述當時 傳送MQ 的最終狀態 62 63 64 Message 訊息的 Topic 不能為空 65 66 67 producer.shutdown(); 關閉 shutdown來清理資源,關閉網路連線,從MetaQ伺服器上登出自己 68 69 } 70 71 72 73 傳送訊息負載的問題 74 { 75 76 看原始碼 是通過迴圈從 namesrv 獲取的到 Topic 路由訊息 (也就是有幾個broker 每個 broker 有幾個佇列) 77 然後 記錄當前傳送過的 +1 78 79 80 備註 : 佇列數量 小於 消費者數量 多餘的消費者將不起做用 81 } 82 83 84 85 關於順序訊息傳送 的問題 86 { 87 環境: 1 下單 2 付款 3 收貨 3個狀態 , 普通模式下 傳送到佇列中的 是輪詢佇列 將3個訊息分別傳送到多個佇列中。 88 89 很可能會照成出現 先消費 2 在消費 1 流程錯亂的情況 當然可以業務層處理 但是業務層處理比較麻煩 90 91 92 順序消費的傳送的原理 : 93 94 我們自己指定 訊息將要新增的佇列 95 96 SendResult sendResult = producer.send(msg, 97 new MessageQueueSelector() 98 { 99 @Override 100 public MessageQueue select(List<MessageQueue> mqs, 101 Message msg, Object arg) 102 { 103 Integer id = (Integer) arg; 104 int index = id % mqs.size(); // 通過取於來 講 同一個訂單號 訪入同一個佇列中 105 // 前提是 佇列數量沒有變動 106 return mqs.get(index); 107 108 } 109 }, “10001”); // orderID “10001”是傳遞給回撥方法的 自定義資料 110 111 112 List<MessageQueue> mqs 就是從namesrv 獲取的所有佇列 113 } 114 115 116 117 備註 118 // 訂單 Id 119 String orderId = "20034568923546"; 120 message.setKeys(orderId); 121 // Keys 122 每個訊息在業務局面的唯一標識碼 通過 topic,key 來查詢返條訊息內容,以及訊息被誰消費 123 查詢的時候 非常重要 124 125 126 127 消費者 128 interface MQConsumer 129 { 130 131 // 回溯消費 132 { 133 mqadmin resetOffsetByTime 命令 134 改方式 是通過消費的日誌來恢復的 但是隻能通過 消費的組來恢復 恢復訊息後 也只能用改組來從新消費 135 136 -s : 時間戳的問題 可以是 毫秒 或者是從什麼時候開始 137 138 } 139 140 141 142 //拉取模式 143 interface MQPullConsumer: 144 { 145 146 147 148 } 149 150 151 152 153 154 155 // 接收模式 156 157 長輪詢模式 一次獲取一批 訊息 158 159 160 記錄 161 批量和單條 內部實現 還是獲取了所有的 可以獲取到的佇列訊息 放入集合中 判斷集合大小是否 大於設定的單次消費數量 162 163 小於 164 直接將其 訊息集合 放入執行回撥方法中 165 大於 166 使用的是For 迴圈 來單條處理 167 168 169 170 171 172 173 174 interface MQPushConsumer: 175 { 176 class DefaultMQPushConsumer extends ClientConfig implements MQPushConsumer 177 178 DefaultMQPushConsumer 包含很多可以配置的資訊 詳情見文件 179 其中最主要的 有幾個 180 messageModel 訊息模型 支援以下兩種 1、叢集消費 2、廣播消費 181 messageListener 訊息監聽器 182 consumeThreadMin 消費執行緒池數量 預設10 183 consumeThreadMax 消費執行緒池數量 預設20 184 185 重要的是 消費執行緒池 ! 這就說明 我釋出一個 消費應用 消費邏輯就可以 N 個 處理! 不用自己搞了有沒有!! 186 安預設的來算 20個消費邏輯 可以配置 而且還 可以橫向 增加 消費應用 只要保持是一個組就可以了 187 188 難怪會在文件中 特意話一個 效能圖啊!! 189 190 191 192 193 應用通常吐 Consumer 物件註冊一個 Listener 介面,一旦收到訊息,Consumer 物件立刻回撥 Listener 介面方法 194 195 MessageListenerOrderly 這個是有序的 196 MessageListenerConcurrently 這個是無序的 197 198 關鍵方法 DefaultMQPushConsumer registerMessageListener(new implements MessageListenerConcurrently) 199 { 200 public void registerMessageListener(MessageListenerConcurrently messageListener) { 201 this.messageListener = messageListener; // 給自己複製一個 消費邏輯類物件 方法後續查詢 替換修改等 202 203 關鍵方法 204 this.defaultMQPushConsumerImpl.registerMessageListener(messageListener); 205 // 將消費邏輯類告訴 呼叫者類 206 } 207 } 208 209 關鍵方法 start 210 211 DefaultMQPushConsumer.start() --> DefaultMQPushConsumerImpl.start() 212 { 213 this.serviceState 來記錄設定當前程式執行狀態 來做多型 214 215 checkConfig() 檢查配置 初始化賦值 216 217 copySubscription() 拷貝訂閱者資訊 賦值 消費邏輯類 218 219 220 // 有就獲取 沒有就建立一個 221 this.mQClientFactory = MQClientManager.getInstance().getAndCreateMQClientInstance(this.defaultMQPushConsumer,this.rpcHook); 222 223 224 225 接著初始化一系列資訊 226 227 // 載入消費進度 228 this.offsetStore.load(); 229 // 該方法有兩個實現 230 231 一個是本地 this.readLocalOffset() 獲取資料 232 { 233 //獲取檔案字串 234 String content = MixAll.file2String(this.storePath); 235 OffsetSerializeWrapper offsetSerializeWrapper =OffsetSerializeWrapper.fromJson(content, OffsetSerializeWrapper.class); 236 237 可以看出 淘寶使用的是JSON 238 239 } 240 241 242 243 244 if (this.getMessageListenerInner() instanceof MessageListenerOrderly) 245 else if (this.getMessageListenerInner() instanceof MessageListenerConcurrently) 246 判斷 消費邏輯類 實現那個介面 建立對應的 ConsumeMessageOrderlyService 物件 247 248 ConsumeMessageConcurrentlyService 該實現為空 249 250 251 本地 252 ConsumeMessageOrderlyService.start() 253 { 254 255 256 257 建立並執行一個週期性的動作成為了第一個在給定的初始延遲之後,隨後用給定的時期,這是執行後將開始initialDelay然後initialDelay +,然後initialDelay + 2 *時期,等等。如果任何執行任務遇到異常,後續執行的鎮壓。否則,只會終止的任務通過取消或終止執行器。如果執行這個任務花費的時間比其期,然後後續執行可能會遲到,但不會同時執行。 258 //就是一個定時器 259 260 this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { 261 @Override 262 public void run() { 263 ConsumeMessageOrderlyService.this.lockMQPeriodically(); 264 } 265 }, 1000 * 1, ProcessQueue.RebalanceLockInterval, TimeUnit.MILLISECONDS); 266 267 scheduleAtFixedRate 應該是一個執行緒池管理 268 269 不用去關心 scheduleAtFixedRate 方法 看 ConsumeMessageOrderlyService.this.lockMQPeriodically() 270 { 271 272 this.defaultMQPushConsumerImpl.getRebalanceImpl().lockAll() 273 274 RebalanceImpl.lockAll() // 將讀取到的訊息上鎖 275 } 276 277 278 } 279 280 281 // 最關鍵的服務啟動了 282 // 正在的啟動了 283 mQClientFactory.start(); 284 { 285 synchronized (this){ 286 287 //Start request-response channel 啟動請求-響應通道 288 this.mQClientAPIImpl.start(); 289 //Start various schedule tasks 開始各種安排任務 啟動定時任務 其中就包含 獲取到MQ訊息消費的 回撥方法 290 this.startScheduledTask(); 291 //Start pull service 開始拉取服務 292 this.pullMessageService.start(); 293 //Start rebalance service 啟動負載均衡 // 該服務非常重要 294 this.rebalanceService.start(); 295 //Start push service 開始推動服務 296 this.defaultMQProducer.getDefaultMQProducerImpl().start(false); 297 } 298 } 299 } 300 301 302 } 303 304 指定 group 305 訂閱 topic 306 註冊訊息監聽處理器,當訊息到來時消費訊息 307 消費端 Start 308 309 複製訂閱關係 310 311 初始化 rebalance 變數 312 313 構建 offsetStore 消費進度儲存物件 314 315 啟動消費訊息服務 316 317 向 mqClientFactory 註冊本消費者 318 319 啟動 client 端遠端通訊 320 321 * 載入消費進度 Loand() 322 323 * 啟動定時任務 324 325 定時獲取 nameserver 地址 326 327 定時從 nameserver 獲取 topic 路由資訊 328 329 定時清理下線的 borker 330 331 定時向所有 broker 傳送心跳資訊, (包括訂閱關係) 332 333 * 定時持久化 Consumer 消費進度(廣播儲存到本地,叢集儲存到 Broker) 334 PS: 這裡也是是個關鍵 持久化消費進度 是用來記錄當前 組的消費情況 可以做到 回溯消費 和當機等情況下 啟動後接著上次執行消費 335 336 統計資訊打點 337 338 動態調整消費執行緒池 339 340 啟動拉訊息服務 PullMessageService 341 342 啟動消費端負載均衡服務 RebalanceService 343 344 從 namesrv 更新 topic 路由資訊 345 346 向所有 broker 傳送心跳資訊, (包括訂閱關係) 347 348 喚醒 Rebalance 服務執行緒 349 350 351 } 352 353 354 // 有些懶得看了 直接看別人 得了 355 356 消費端負載均衡 357 { 358 這個也是個重點 359 360 消費端會通過 RebalanceService 執行緒,10 秒鐘做一次基於 topic 下的所有佇列負載 361 362 消費端 遍歷自己所有的 Topic 獲取 Topic 下所有的 佇列 (一個Topic 包含對個佇列 預設是 4 個 有別於其他MQ ) 363 364 從 broker 獲取當前 組(group)的所有消費端( 有心跳的) 365 獲取佇列集合Set<MessageQueue> mqSet 366 現在佇列分配策略例項 AllocateMessageQueueStrategy 執行分配演算法 367 { 368 1:平均分配演算法 : 369 其實是類似於分頁的演算法 370 將所有 queue 排好序類似於記錄 371 將所有消費端 consumer 排好序,相當於頁數 372 然後獲取當前 consumer 所在頁面應該分配到的 queue 373 2:按照配置來分配佇列 : 374 消費服務啟動的時候 就指定好了要消費的 是哪個佇列 375 3:按照機房來配置佇列 : 376 Consumer 啟動的時候會指定在哪些機房的訊息 (應該是指定 broker) 377 獲取指定機房的 queue 378 然後在執行如 1)平均演算法 379 } 380 381 根據分配佇列的結果更新 ProccessQueueTable<MessageQueue, ProcessQueue> 382 { 383 比對 mqSet 將多餘的佇列刪除, 當 broker 當機或者新增,會導致分配到 mqSet 變化, 384 385 新增新增佇列, 比對 mqSet,給新增的 messagequeue 構建長輪詢物件 PullRequest 物件,會從 broker 獲取消費的進度 構建這個佇列的 ProcessQueue 386 將 PullRequest 物件派發到長輪詢拉訊息服務(單執行緒非同步拉取) 387 388 389 注:ProcessQueue 正在被消費的佇列, 390 391 (1) 長輪詢拉取到訊息都會先儲存到 ProcessQueue 的 TreeMap<Long, MessageExt> 集合中,消費調後會刪除掉,用來控制 consumer 訊息堆積, TreeMap<Long, MessageExt> key 是訊息在此 ConsumeQueue 佇列中索引 392 393 (2) 對於順序訊息消費 處理 394 395 locked 屬性:當 consumer 端向 broker 申請鎖佇列成功後設定 true, 只有被鎖定 的 processqueue 才能被執行消費 rollback: 將消費在 msgTreeMapTemp 中的訊息,放回 msgTreeMap 重新消費 396 397 commit: 將臨時表 msgTreeMapTemp 資料清空,代表消費完成,放回最大偏移 398 399 400 (3) 這裡是個 TreeMap,對 key 即訊息的 offset 進行排序,這個樣可以使得訊息進 行順序消費 401 402 403 404 } 405 406 407 408 } 409 410 411 412 413 長輪詢 414 { 415 Rocketmq 的訊息是由 consumer 端主動到 broker拉取的, consumer 向 broker 傳送拉訊息 416 請求, PullMessageService 服務通過一個執行緒將阻塞佇列 LinkedBlockingQueue<PullRequest> 417 中的 PullRequest 到 broker 拉取訊息 418 419 DefaultMQPushConsumerImpl 的 pullMessage(pullRequest)方法執行向 broker 拉訊息動作 420 1. 獲取 ProcessQueue 判讀是否 drop 的, drop 為 true 返回 421 2. 給 ProcessQueue 設定拉訊息時間戳 422 3. 流量控制,正在消費佇列中訊息(未被消費的)超過閥值,稍後在執行拉訊息 423 4. 流量控制,正在消費佇列中訊息的跨度超過閥值(預設 2000) ,稍後在消費 424 5. 根據 topic 獲取訂閱關係 425 6. 構建拉訊息回撥物件 PullBack, 從 broker 拉取訊息(非同步拉取)返回結果是回撥 426 7. 從記憶體中獲取 commitOffsetValue //TODO 這個值跟 pullRequest.getNextOffset 區別 427 8. 構建 sysFlag pull 介面用到的 flag 428 9. 調底層通訊層向 broker 傳送拉訊息請求 429 如果 master 壓力過大,會建議去 slave 拉取訊息 430 如果是到 broker 拉取訊息清楚實時提交標記位,因為 slave 不允許實時提交消費進 431 度,可以定時提交 432 //TODO 關於 master 拉訊息實時提交指的是什麼? 433 10. 拉到訊息後回撥 PullCallback 434 處理 broker 返回結果 pullResult 435 436 更新從哪個 broker(master 還是 slave)拉取訊息 437 438 反序列化訊息 439 440 訊息過濾 441 442 訊息中放入佇列最大最小 offset, 方便應用來感知訊息堆積度 443 將訊息加入正在處理佇列 ProcessQueue 444 將訊息提交到消費訊息服務 ConsumeMessageService 445 流控處理, 如果 pullInterval 引數大於 0 (拉訊息間隔,如果為了降低拉取速度, 446 可以設定大於 0 的值) , 延遲再執行拉訊息, 如果 pullInterval 為 0 立刻在執行拉 447 訊息動作 448 449 450 看圖 人家花的不錯 很明瞭 451 } 452 453 push 訊息 454 { 455 456 PS: 457 長輪詢向broker拉取訊息是批量拉取的, 預設設定批量的值為pullBatchSize = 32, 可配置 458 459 消費端 consumer 構建一個消費訊息任務 ConsumeRequest 消費一批訊息的個數是 可配置的 consumeMessageBatchMaxSize = 1, 預設批量個數為一個 460 也就是說 每次傳遞給回撥方法的 引數 訊息集合 的解釋 461 462 ConsumeRequest 任務 run 方法執行 463 464 465 判斷 proccessQueue 是否被 droped 的, 廢棄直接返回,不在消費訊息 466 467 構建並行消費上下文 468 469 給訊息設定消費失敗時候的 retry topic,當訊息傳送失敗的時候傳送到 topic 470 為%RETRY%groupname 的佇列中 471 472 473 調 MessageListenerConcurrently 監聽器的 consumeMessage 方法消費訊息,返回消 474 費結果 475 476 如果 ProcessQueue 的 droped 為 true,不處理結果,不更新 offset, 但其實這裡消 477 費端是消費了訊息的,這種情況感覺有被重複消費的風險 478 479 處理消費結果 : 480 481 消費成功, 對於批次消費訊息, 返回消費成功並不代表所有訊息都消費成功, 482 但是消費訊息的時候一旦遇到消費訊息失敗直接放回,根據 ackIndex 來標記 483 成功消費到哪裡了 484 485 486 消費失敗, ackIndex 設定為-1 487 廣播模式傳送失敗的訊息丟棄, 廣播模式對於失敗重試代價過高,對整個集 488 群效能會有較大影響,失敗重試功能交由應用處理 489 叢集模式, 將消費失敗的訊息一條條的傳送到 broker 的重試佇列中去,如果 490 此時還有傳送到重試佇列傳送失敗的訊息, 那就在 cosumer 的本地執行緒定時 5 491 秒鐘以後重試重新消費訊息, 在走一次上面的消費流程。 492 493 494 刪除正在消費的佇列 processQueue 中本次消費的訊息,放回消費進度 495 496 497 更新消費進度, 這裡的更新只是一個記憶體 offsetTable 的更新,後面有定時任務定 498 時更新到 broker 上去 499 500 501 502 503 PS: 504 關於消費成功 和 失敗的 問題 505 506 在叢集模式下 回撥方法設定為消費失敗 會將當前消費的失敗訊息 傳送到 broker 的容錯度列中 等待N次+ 從新消費 。 507 508 509 push 消費-順序消費訊息 510 511 順序消費服務 ConsumeMessageConcurrentlyService 構建的時候 512 513 構建一個執行緒池來接收消費請求 ConsumeRequest 514 515 構建一個單執行緒的本地執行緒, 用來稍後定時重新消費 ConsumeRequest, 用來執行 516 定時週期性(一秒)鍾鎖佇列任務 517 518 週期性鎖佇列 lockMQPeriodically 519 520 獲取正在消費佇列列表 ProcessQueueTable 所有 MesssageQueue, 構建根據 broker 521 歸類成 MessageQueue 集合 Map<brokername, Set<MessageQueue>> 522 523 遍歷 Map<brokername, Set<MessageQueue>>的 brokername, 獲取 broker 的 master 524 機器地址, 將brokerName的Set<MessageQueue>傳送到broker請求鎖定這些佇列。 在broker 525 端鎖定佇列,其實就是在 broker 的 queue 中標記一下消費端,表示這個 queue 被某個 client 526 鎖定。 Broker 會返回成功鎖定佇列的集合, 根據成功鎖定的 MessageQueue,設定對應的正 527 在處理佇列 ProccessQueue 的 locked 屬性為 true 沒有鎖定設定為 false 528 529 通過長輪詢拉取到訊息後會提交到訊息服務 ConsumeMessageOrderlyService, 530 ConsumeMessageOrderlyService 的 submitConsumeRequest 方法構建 ConsumeRequest 任務提 531 交到執行緒池。ConsumeRequest 是由 ProcessQueue 和 Messagequeue 組成。 532 533 ConsumeRequest 任務的 run 方法 534 535 判斷 proccessQueue 是否被 droped 的, 廢棄直接返回,不在消費訊息 536 537 每個 messagequeue 都會生成一個佇列鎖來保證在當前 consumer 內,同一個佇列序列 538 消費, 539 540 判斷 processQueue 的 lock 屬性是否為 true, lock 屬性是否過期, 如果為 false 或者過期, 541 放到本地執行緒稍後鎖定在消費。 如果 lock 為 true 且沒有過期,開始消費訊息 542 543 計算任務執行的時間如果大於一分鐘且執行緒數小於佇列數情況下,將 processqueue, 544 messagequeue 重新構建 ConsumeRequest 加到執行緒池 10ms 後在消費,這樣防止個別佇列被 545 餓死 546 547 獲取客戶端的消費批次個數,預設一批次為一條 548 549 從 proccessqueue 獲取批次訊息, processqueue.takeMessags(batchSize) , 從 msgTreeMap 550 中移除訊息放到臨時 map 中 msgTreeMapTemp, 這個臨時 map 用來回滾訊息和 commit 消 551 息來實現事物消費 552 553 調回撥介面消費訊息,返回狀態物件 ConsumeOrderlyStatus 554 555 根據消費狀態,處理結果 556 1) 非事物方式,自動提交 557 訊息訊息狀態為 success: 呼叫 processQueue.commit 方法 558 559 獲取 msgTreeMapTemp 的最後一個 key,表示提交的 offset 560 561 清空 msgTreeMapTemp 的訊息,已經成功消費 562 2) 事物提交,由使用者來控制提交回滾(精衛專用) 563 更新消費進度, 這裡的更新只是一個記憶體 offsetTable 的更新, 後面有定時任務定時更 564 新到 broker 上去 565 566 567 } 568 569 570 關閉 571 { 572 573 shutdown 574 575 DefaultMQPushConsumerImpl 關閉消費端 576 577 關閉消費執行緒 578 579 將分配到的 Set<MessageQueue>的消費進度儲存到 broker 580 利 用 581 DefaultMQPushConsumerImpl 582 獲 取 583 ProcessQueueTable<MessageQueue, 584 ProcessQueue>的 keyset 的 messagequeue 去獲取 585 RemoteBrokerOffsetStore.offsetTable<MessageQueue, AutomicLong>Map 中的消費進 586 度, 587 offsetTable 中 的 messagequeue 的 值, 在 update 的時候如果 沒有對應 的 588 Messagequeue 會構建, 但是也會 rebalance 的時候將沒有分配到的 messagequeue 589 刪除 590 rebalance 會將 offsettable 中沒有分配到 messagequeue 刪除, 但是在從 offsettable 591 刪除之前會將 offset 儲存到 broker 592 593 Unregiser 客戶端 594 595 pullMessageService 關閉 596 597 scheduledExecutorService 關閉,關閉一些客戶端的起的定時任務 598 599 mqClientApi 關閉 600 601 rebalanceService 關閉 602 603 604 }

補充 一
 1 訊息的延遲
 2     {
 3         通過測試程式可以看出  通過設定 message 的DelayTimeLevel 可以設定訊息延遲處理
 4 
 5     }
 6 
 7     訊息重試機制  容錯機制
 8     {
 9 
10         通過原始碼可以看出 消費方法的返回物件 只有兩個值 
11         
12         CONSUME_SUCCESS // 消費成功
13         
14         RECONSUME_LATER // 消費失敗,稍後重試  
15         
16         
17         CONSUME_SUCCESS 無異議  
18         
19         關鍵是 RECONSUME_LATER 
20             
21             我們可以通過 RECONSUME_LATER 來容錯。 阿里提供的這個  重試機制 是通過新增到一個錯誤佇列中 設定期  DelayTimeLevel 來實現的
22             
23             第一次消費的時候  列印 MessageExt 沒有 properties屬性的詳細資訊  返回 RECONSUME_LATER 稍後重試
24             
25             [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]]
26 
27             第二次消費的時候 
28             
29             [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]]
30 
31             可以看出 訊息  雖然  queueId 是相同的值 0 但是  msgId 卻變了 ! 同時用rocketmq-console 來監控 該 消費者 你會發現 多了個 Topic  %RETRY%ConsumerGroupName  
32             
33             所有 可以得出一個結論  
34             
35             我們返回 消費失敗,稍後重試  RECONSUME_LATER  訊息會回到 broker 同時建立一條相同的訊息 訪如   %RETRY%ConsumerGroupName  
36             同時 設定 該 訊息的 延遲消費 每次延遲時間 +1  
37             
38             我覺得可以通過 reconsumeTimes 來做一個簡單的容錯  獲取 當前消費的 次數  是否大於 設定值   大於就說明其是死信  記錄到異常資料庫 
39             
40             
41     }

 

 

    

備註問題:

    背景:

          生產端 使用 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  !

     






努力或許不會有收穫,但是不努力卻一定不會有收穫!







 

相關文章