在我之前發的兩篇問題整理中,梳理了一下ActiveMQ集群轉發問題的分析過程:
ActiveMQ集群消息轉發問題整理(一)
ActiveMQ集群消息轉發問題整理(二)
這里簡要介紹一下問題的發現和分析過程:
問題發現
環境中 8000+ 個 Client 連接8臺MQ,使用 non-durable 的方式訂閱各自C類網段的TOPIC,例如 192.168.0.1 訂閱 TOPIC:VLAN.192.168.0 。環境中大約有160+個C類網段,所以大約有160個TOPIC。
下發針對全量 Client 的任務的時候,常常發現有個別 Client 收不到任務。由于我之前做了個任務流插件,可以跟蹤到任務是否到達MQ,見(ActiveMQ插件開發實例-任務日志)表現為任務到達了生產者所在的MQ,但是沒有被轉發到Client所在的MQ。
問題分析
一、進一步現象
根據現象進一步測試發現如下的現象:
- 假設 Client_A連接MQ_A,消費TOPIC_A。消息下發時,出現Client_A接收不到消息的情況。
- Client_A連接的MQ_A上只有這個Client_A一個消費者消費TOPIC_A上的消息。
- 查看TOPIC的訂閱者信息,除了Client_A外,還可能出現該MQ轉發到其他MQ上的虛擬消費者,表示發到這個TOPIC的消息需要被轉發給其他MQ
- 集群內其他所有MQ上的TOPIC_A上查看訂閱者,均未出現類似的虛擬消費者,表示消息不會被轉發到MQ_A,所以Client_A無法收到消息。
- 重啟Client,Client會自動飄到集群內其他的MQ上,此時可以正常消費。
- 指定MQ_A要求Client_A重啟后連接到MQ_A,也可以正常消費。
這些現象表明這是一個偶發的問題,所以可能需要進一步深入到源碼級別查找一下問題原因。
二、源碼分析
首先要明確一下MQ集群中的轉發機制是怎樣的。由于是 non-durable 的topic,使用的應該是 DemandForwardBridge 的方式。該方式的原理根據AMQ官網上的介紹,是通過集群中的 MQ 訂閱集群中其他 MQ 的 advisory topic 實現的。舉個例子:
- Broker A和Broker B為集群中兩臺MQ。
- A,B啟動時,便通過靜態配置對方IP的方式得知集群中有另一個MQ存在,所以建立了連接對方的通道,并訂閱了對方的 ActiveMQ.Advisory.Consumer.> 的topic。
- 當 A 上連接了一個消費者,訂閱topic1時,A便會往自己的 ActiveMQ.Advisory.Consumer.Topic.topic1 發送一條帶上了消費者信息(ConsumerInfo) 的消息。
- B接收到這條 Advisory 消息以后,知道了A有一個消費者要消費topic1,就會建立一個專門的訂閱通道(DemandSubscription)。
- 當 B 上收到生產者往 topic1 發送的消息時,會同時往 A 上轉發一份。A收到以后再發送給自己的消費者。
從訂閱 Advisory Topic 到消息轉發,所有的動作都在 activemq-broker 的 org.apache.activemq.network.DemandForwardBridgeSupport 類中實現。其中 serviceRemoteConsumerAdvisory() 方法用于處理接收到的Advisory 消息,該方法調用了 addConsumerInfo() 方法,用于建立 DemanSubscription 。
于是我修改了 DemandForwardBridgeSupport 和 AdvisoryBroker(用于發送 Advisory 消息)這兩個類,增加了一些日志來分析異常的場景下到底是哪一環節出了問題。
##########正常的日志##########
1. Client-1 連接到 Broker A,Broker A 添加消費者,消費者ID為 Client-1
2. Broker A 發送 Advisory 消息,廣播Client-1的ConsumerInfo
3. Broker B 收到 Client-1的 Advisory 消息,添加消費者,消費者ID為 Broker B->Broker A
4. Broker B 發送 Advisory 消息,廣播 Broker B->Broker A 的ConsumerInfo
5. Broker C 收到 Client-1的 Advisory 消息,添加消費者,消費者ID為 Broker C->Broker A
6. Broker C 發送 Advisory 消息,廣播 Broker C->Broker A 的ConsumerInfo
7. Broker C 收到 Broker B->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
8. Broker B 收到 Broker C->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
9. Broker A 收到 Broker B->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
10. Broker A 收到 Broker C->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
##########異常的日志##########
1. Client-1 連接到 Broker A,Broker A 添加消費者,消費者ID為 Client-1
2. Broker A 發送 Advisory 消息,廣播Client-1的ConsumerInfo
3. Broker B 收到 Client-1的 Advisory 消息,添加消費者,消費者ID為 Broker B->Broker A
4. Broker B 發送 Advisory 消息,廣播 Broker B->Broker A 的ConsumerInfo
5. Broker C 收到 Broker B->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
6. Broker A 收到 Broker B->Broker A 的 Advisory 消息,由于 networkTTL=1 的設置,不添加消費者。
可以看到,Broker C 異常場景下沒有接收到 Broker A 的消息。也就是說,消息從 Broker A 中發出,但是在 Broker C 消費時丟失了。
異常場景只在大量 Client 同時嘗試連接,且出現問題的 Topic 網段中 Client 數量較少。
問題解決
我嘗試了多種方法,始終無法解決Advisory消息沒收到的問題,于是我在默認 Advisory 消息會丟的情況下設計了幾種解決方法:
- 設置 networkTTL=2
- 嘗試本地接收 Advisory 消息并存在內存中,等到服務器不忙的時候直接再發一次。
前一種方法對一個互相連接的集群來說,有極大的網絡負擔;而后一個方法邏輯比較復雜,比較難判斷 Advisory 消息什么時候該發。一怒之下我提了個 AMQ 嚴重BUG到JIRA上。一開始有人建議我升級到5.15以上的版本再試試,于是我試了下,問題仍然存在。
在我把我分析問題的流程寫到JIRA上以后,有人回復我是我需要去掉AMQ的一個默認配置。
<policyEntry topic=">" >
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
pendingMessageLimitStrategy這個配置項用于處理 Slow Consumer,AMQ官方的解釋是這樣的(Slow Consumers)
Slow Consumers can cause problems on non-durable topics since they can force the broker to keep old messages in RAM which once it fills up, forces the broker to slow down producers, causing the fast consumers to be slowed down. One option we could implement in the future is spooling to disk - but then spooling to disk could slow down the fast consumers too.
Currently we have a strategy that lets you configure the maximum number of matched messages the broker will keep around for a consumer in addition to its prefetch buffer. Once this maximum is reached, as new messages come in, older messages are discarded. This allows you to keep the RAM for current messages and keep sending messages to a slow consumer but to discard old messages.
也就是是說,可配置一個 non-durable topic 的 consumer 可以讓MQ為其保存多少消息,由于 topic=">" 的寫法 ,Advisory的Topic也被包含在這個配置的使用范圍內。所以,當有大量 Client 連接上來時,MQ會發送大量的 Advisory Messages,如果集群中其他MQ的對 Advisory Messages沒有及時處理完,就會導致觸發這個機制,新的 Advisory 消息會持續把舊的 Advisory 消息頂出隊列(as new messages come in, older messages are discarded)。
綜上所述,如果場景中有多個MQ組成集群,有大量的Client嘗試連接,就需要調整這個配置,避免出現 advisory 消息被丟棄的情況。
Problem Solved.
山窮水復疑無路,柳暗花明又一村