分佈式消息通信ActiveMQ

課程目標

  1. 持久化消息和非持久化消息的發送策略
  2. 消息的持久化方案及實踐
  3. 消費端消費消息的原理
  4. 關於PrefetchSize的優化

1、持久化消息和非持久化消息的發送策略

1.1、消息同步發送和異步發送

ActiveMQ支持同步、異步兩種發送模式將消息發送到broker上。
同步發送過程中,發送者發送一條消息會阻塞直到broker反饋一個確認消息,表示消息已經被broker處理。這個機
制提供了消息的安全性保障,但是由於是阻塞的操作,會影響到客戶端消息發送的性能
異步發送的過程中,發送者不需要等待broker提供反饋,所以性能相對較高。但是可能會出現消息丟失的情況。所
以使用異步發送的前提是在某些情況下允許出現數據丟失的情況。
默認情況下,非持久化消息是異步發送的,持久化消息並且是在非事務模式下是同步發送的。
但是在開啓事務的情況下,消息都是異步發送。由於異步發送的效率會比同步發送性能更高。所以在發送持久化消
息的時候,儘量去開啓事務會話。
除了持久化消息和非持久化消息的同步和異步特性以外,我們還可以通過以下幾種方式來設置異步發送

1.ConnectionFactory connectionFactory=new ActiveMQConnectionFactory("tcp://192.168.12.121:8161? jms.useAsyncSend=true"); 
2.((ActiveMQConnectionFactory) connectionFactory).setUseAsyncSend(true); 
3.((ActiveMQConnection)connection).setUseAsyncSend(true);

1.2、消息的發送原理分析圖解

在這裏插入圖片描述

1.2.1、ProducerWindowSize的含義

producer每發送一個消息,統計一下發送的字節數,當字節數達到ProducerWindowSize值時,需要等待broker的
確認,才能繼續發送。
代碼在:ActiveMQSession的1957行
主要用來約束在異步發送時producer端允許積壓的(尚未ACK)的消息的大小,且只對異步發送有意義。每次發送消
息之後,都將會導致memoryUsage大小增加(+message.size),當broker返回producerAck時,memoryUsage尺
寸減少(producerAck.size,此size表示先前發送消息的大小)。
可以通過如下2種方式設置:
Ø 在brokerUrl中設置: “tcp://localhost:61616?jms.producerWindowSize=1048576”,這種設置將會對所有的
producer生效。
Ø 在destinationUri中設置: “test-queue?producer.windowSize=1048576”,此參數只會對使用此Destination實例
的producer失效,將會覆蓋brokerUrl中的producerWindowSize值。
注意:此值越大,意味着消耗Client端的內存就越大。

1.2.2消息發送的源碼分析

以producer.send爲入口
在這裏插入圖片描述
ActiveMQSession的send方法

protected void send(ActiveMQMessageProducer producer, ActiveMQDestination destination, Message message, int deliveryMode, int priority, long timeToLive, MemoryUsage producerWindow, int sendTimeout, AsyncCallback onComplete) throws JMSException {

ActiveMQConnection. doAsyncSendPacket

private void doAsyncSendPacket(Command command) throws JMSException { try {
this.transport.oneway(command); 
} catch (IOException e) { 
throw JMSExceptionSupport.create(e); 
} 
}

同步發送和異步發送的區別

public Object request(Object command, int timeout) throws IOException { FutureResponse response = asyncRequest(command, null); return response.getResult(timeout); // 從future方法阻塞等待返回 }

在ResponseCorrelator的request方法中,需要通過response.getResult去獲得broker的反饋,否則會阻塞

持久化消息和非持久化消息的存儲原理

正常情況下,非持久化消息是存儲在內存中的,持久化消息是存儲在文件中的。能夠存儲的最大消息數據在
${ActiveMQ_HOME}/conf/activemq.xml文件中的systemUsage節點
SystemUsage配置設置了一些系統內存和硬盤容量

在這裏插入圖片描述
從上面的配置我們需要get到一個結論,當非持久化消息堆積到一定程度的時候,也就是內存超過指定的設置閥
值時,ActiveMQ會將內存中的非持久化消息寫入到臨時文件,以便騰出內存。但是它和持久化消息的區別是,重
啓之後,持久化消息會從文件中恢復,非持久化的臨時文件會直接刪除

消息的持久化策略分析

消息持久性對於可靠消息傳遞來說是一種比較好的方法,即時發送者和接受者不是同時在線或者消息中心在發送者
發送消息後宕機了,在消息中心重啓後仍然可以將消息發送出去。消息持久性的原理很簡單,就是在發送消息出去
後,消息中心首先將消息存儲在本地文件、內存或者遠程數據庫,然後把消息發送給接受者,發送成功後再把消息
從存儲中刪除,失敗則繼續嘗試。接下來我們來了解一下消息在broker上的持久化存儲實現方式

持久化存儲支持類型

ActiveMQ支持多種不同的持久化方式,主要有以下幾種,不過,無論使用哪種持久化方式,消息的存儲邏輯都是
一致的。
Ø KahaDB存儲(默認存儲方式)
Ø JDBC存儲
Ø Memory存儲
Ø LevelDB存儲
Ø JDBC With ActiveMQ Journal

消費端消費消息的原理

有兩種方法可以接收消息,一種是使用同步阻塞的MessageConsumer#receive方
法。另一種是使用消息監聽器MessageListener。這裏需要注意的是,在同一個session下,這兩者不能同時工
作,也就是說不能針對不同消息採用不同的接收方式。否則會拋出異常。
至於爲什麼這麼做,最大的原因還是在事務性會話中,兩種消費模式的事務不好管控

消費端的 PrefetchSize

消息消費流程圖
在這裏插入圖片描述
activemq 的 consumer 端也有窗口機制,通過 prefetchSize 就可以設置窗口
大小。不同的類型的隊列,prefetchSize 的默認值也是不一樣的
持久化隊列和非持久化隊列的默認值爲 1000
持久化 topic 默認值爲 100
非持久化隊列的默認值爲 Short.MAX_VALUE-1
消費端會根據
prefetchSize 的大小批量獲取數據,比如默認值是 1000,那麼消費端會預先加
載 1000 條數據到本地的內存中

prefetchSize 的設置方法
在 createQueue 中添加 consumer.prefetchSize,就可以看到效果

Destination
destination=session.createQueue("myQueue?consumer.prefetchSize=10");

optimizeAcknowledge

ActiveMQ 提供了 optimizeAcknowledge 來優化確認,它表示是否開啓“優化
ACK”,只有在爲 true 的情況下,prefetchSize 以及
optimizeAcknowledgeTimeout 參數纔會有意義
優化確認一方面可以減輕 client 負擔(不需要頻繁的確認消息)、減少通信開
銷,另一方面由於延遲了確認(默認 ack 了 0.65*prefetchSize 個消息才確
認),broker 再次發送消息時又可以批量發送
如果只是開啓了 prefetchSize,每條消息都去確認的話,broker 在收到確認後
也只是發送一條消息,並不是批量發佈,當然也可以通過設置 DUPS_OK_ACK
來手動延遲確認, 我們需要在 brokerUrl 指定 optimizeACK 選項

ConnectionFactory connectionFactory= new ActiveMQConnectionFactory ("tcp://192.168.11.153:61616?jms.optimizeAcknowledge=true&jms.optimiz
eAcknowledgeTimeOut=10000");

Ø 注意,如果 optimizeAcknowledge 爲 true,那麼 prefetchSize 必須大於 0.
當 prefetchSize=0 的時候,表示 consumer 通過 PULL 方式從 broker 獲取消

optimizeAcknowledge 和 prefetchSize 的作用,兩
者協同工作,通過批量獲取消息、並延遲批量確認,來達到一個高效的消息消
費模型。它比僅減少了客戶端在獲取消息時的阻塞次數,還能減少每次獲取消
息時的網絡通信開銷
Ø 需要注意的是,如果消費端的消費速度比較高,通過這兩者組合是能大大提
升 consumer 的性能。如果 consumer 的消費性能本身就比較慢,設置比較大
的 prefetchSize 反而不能有效的達到提升消費性能的目的。因爲過大的
prefetchSize 不利於 consumer 端消息的負載均衡。因爲通常情況下,我們都
會部署多個 consumer 節點來提升消費端的消費性能。
這個優化方案還會存在另外一個潛在風險,當消息被消費之後還沒有來得及確
認時,client 端發生故障,那麼這些消息就有可能會被重新發送給其他
consumer,那麼這種風險就需要 client 端能夠容忍“重複”消息。

消息的確認過程

ACK_MODE

消息確認有四種 ACK_MODE,分別是
AUTO_ACKNOWLEDGE = 1 自動確認
CLIENT_ACKNOWLEDGE = 2 客戶端手動確認
DUPS_OK_ACKNOWLEDGE = 3 自動批量確認
SESSION_TRANSACTED = 0 事務提交併確認

雖然 Client 端指定了 ACK 模式,但是在 Client 與 broker 在交換 ACK 指令的時
候,還需要告知 ACK_TYPE,ACK_TYPE 表示此確認指令的類型,不同的
ACK_TYPE 將傳遞着消息的狀態,broker 可以根據不同的 ACK_TYPE 對消息進
行不同的操作

ACK_TYPE

DELIVERED_ACK_TYPE = 0 消息"已接收",但尚未處理結束
STANDARD_ACK_TYPE = 2 “標準"類型,通常表示爲消息"處理成功”,broker 端
可以刪除消息了
POSION_ACK_TYPE = 1 消息"錯誤",通常表示"拋棄"此消息,比如消息重發多
次後,都無法正確處理時,消息將會被刪除或者 DLQ(死信隊列)
REDELIVERED_ACK_TYPE = 3 消息需"重發",比如 consumer 處理消息時拋出
了異常,broker 稍後會重新發送此消息
INDIVIDUAL_ACK_TYPE = 4 表示只確認"單條消息",無論在任何 ACK_MODE 下
UNMATCHED_ACK_TYPE = 5 在 Topic 中,如果一條消息在轉發給“訂閱者”
時,發現此消息不符合 Selector 過濾條件,那麼此消息將 不會轉發給訂閱
者,消息將會被存儲引擎刪除(相當於在 Broker 上確認了消息)。
Client 端在不同的 ACK 模式時,將意味着在不同的時機發送 ACK 指令,每個 ACK
Command 中會包含 ACK_TYPE,那麼 broker 端就可以根據 ACK_TYPE 來決定
此消息的後續操作

消息的重發機制原理

在正常情況下,有幾中情況會導致消息重新發送
Ø 在事務性會話中,沒有調用 session.commit 確認消息或者調用
session.rollback 方法回滾消息
Ø 在非事務性會話中,ACK 模式爲 CLIENT_ACKNOWLEDGE 的情況下,沒有
調用 acknowledge 或者調用了 recover 方法;
一個消息被 redelivedred 超過默認的最大重發次數(默認 6 次)時,消費端會
給 broker 發送一個”poison ack”(ActiveMQMessageConsumer#dispatch:
1460 行),表示這個消息有毒,告訴 broker 不要再發了。這個時候 broker 會
把這個消息放到 DLQ(死信隊列)。

ActiveMQ 的優缺點

優點:
ActiveMQ 採用消息推送方式,所以最適合的場景是默認消息都可在短時間內
被消費。數據量越大,查找和消費消息就越慢,消息積壓程度與消息速度成反
比。
缺點:
1.吞吐量低。由於 ActiveMQ 需要建立索引,導致吞吐量下降。這是無法克服
的缺點,只要使用完全符合 JMS 規範的消息中間件,就要接受這個級別的
TPS。
2.無分片功能。這是一個功能缺失,JMS 並沒有規定消息中間件的集羣、分片
機制。而由於 ActiveMQ 是偉企業級開發設計的消息中間件,初衷並不是爲了
處理海量消息和高併發請求。如果一臺服務器不能承受更多消息,則需要橫向
拆分。ActiveMQ 官方不提供分片機制,需要自己實現。
適用場景

  1. 對 TPS 要求比較低的系統,可以使用 ActiveMQ 來實現,一方面比較簡單,能
    夠快速上手開發,另一方面可控性也比較好,還有比較好的監控機制和界面
    不適用的場景
    1.消息量巨大的場景。ActiveMQ 不支持消息自動分片機制,如果消息量巨大,
    導致一臺服務器不能處理全部消息,就需要自己開發消息分片功能。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章