訊息佇列中介軟體是分散式系統中的重要元件,主要解決應用耦合、非同步訊息、流量削鋒等問題。可幫助實現高效能,高可用,可伸縮和最終一致性的架構
在訊息佇列方面,除了 ActiveMQ、RabbitMQ、RocketMQ、ZeroMQ,Kafka等,還有很多其他的競爭者。這篇文章我們不會去講解它們之間的區別,僅只詳細的介紹一下 ActiveMQ,以及它在 .NET 中的使用
訊息佇列應用場景
非同步任務
比如有以下場景:現在很多網站或App註冊時都採用了驗證碼的機制,因此,當伺服器收到客戶端發起獲取驗證碼的請求,有以下處理方式
- 在當前執行緒中立即傳送簡訊(會阻塞當前執行緒一小會兒)
- 新建立一個執行緒傳送簡訊(在 .NET 中建立一個 Task 就行)
- 交由其他的服務來處理這個任務(轉發給訊息佇列,讓訊息佇列處理)
那麼,以上幾種方式哪種更好呢?
- 第一種:實時性肯定更好,收到請求立即處理,但它阻塞了當前執行緒,會造成其他客戶端的請求被阻塞(請求少的時候我們可能根本感覺不到);
- 第二種:在當前程式中建立一個執行緒來處理,實時性不如第一種,但它不會阻塞其他客戶端的請求。不過一個程式中能建立的執行緒數量有限,因此也有瓶頸
- 第三種:使用其他特定場景的服務,這種實時性最差(但如果伺服器配置好,我們也不一定能感覺到差異),但其是使用的最多的,並且其上線後效果是最好的(穩定性、可伸縮性)
因此,如果是正式上線的版本(比如專案初期用於驗證市場的版本,往往會為了速度而不考慮架構,這時可能會選擇第一種或第二種方案),且峰值較高的服務,選用第三種方案無疑是最好的。因為對於上線的服務,穩定性是非常重要的
對於傳送簡訊這樣的任務(對實時性要求不是那麼高),使用訊息佇列是非常合適的。將任務交由訊息佇列之後,傳送簡訊具體要做的事情主服務就不需要干涉了。如果需要,主服務訂閱任務的處理結果即可(傳送成功或者失敗)。這樣,主服務就可以繼續處理其他客戶端的請求,並且,有訊息佇列的參與,主服務的壓力就沒有那麼重了
當然,實際專案中,這樣的場景還有很多,比如記錄日誌,我們都知道,寫檔案(磁碟I/O)很耗時。因此現在很多大型的服務,都有專門的日誌伺服器來處理其他伺服器傳送過來的日誌,這時候我們可以使用 Kafka 來做這樣的事情(因為它就是為了處理日誌而生的)
訊息服務
比如現如今的微服務、分散式叢集等,各個節點之間的通訊,就可以使用訊息佇列來處理。具體使用什麼方式,可更具場景從以下兩種選擇
- P2P(Point to Point)點對點模式
- Publish/Subscribe(Pub/Sub) 釋出訂閱模式
後面在給出案例時會具體講解這兩種模式
ActiveMQ
ActiveMQ 是Apache出品,最流行的,能力強勁的開源訊息匯流排。ActiveMQ 是一個完全支援JMS1.1和J2EE 1.4規範的 JMS Provider實現,儘管JMS規範出臺已經是很久的事情了,但是JMS在當今的J2EE應用中間仍然扮演著特殊的地位。另外,在很多大型的網站或服務中,也都會使用到它
它具有以下特性
- 多種語言和協議編寫客戶端
語言:Java、C、C++、C#、Ruby、Perl、Python、PHP;
應用協議:OpenWire、Stomp REST、WS Notification、XMPP、AMQP - 完全支援JMS1.1和J2EE 1.4規範 (持久化,XA訊息,事務)
- 對Spring的支援,ActiveMQ可以很容易內嵌到使用Spring的系統裡面去,而且也支援Spring 的最新特性
- 通過了常見J2EE伺服器(如 Geronimo、JBoss 4、GlassFish、WebLogic)的測試,其中通過JCA 1.5 resource adaptors的配置,可以讓ActiveMQ可以自動的部署到任何相容J2EE 1.4 商業伺服器上
- 支援多種傳輸協議:in-VM、TCP、SSL、NIO、UDP、JGroups、JXTA
- 支援通過JDBC和journal提供高速的訊息持久化
- 從設計上保證了高效能的叢集,客戶端-伺服器以及點對點的通訊
- 支援Ajax
- 支援與Axis的整合
- 可以很容易呼叫內嵌 JMS provider 進行測試
它的優勢
- 穩定性:失敗重連機制,持久化服務, 容錯機制, 多種恢復機制
- 高效性:支援多種傳送協議如TCP, SSL, NIO, UDP等,叢集訊息在多個代理之間轉發防止訊息丟失,支援超快的JDBC訊息持久化和高效的日誌系統
- 可擴充套件:ActiveMQ 的高階特性都可以配置的形式來表現,很好的實現例如遊標,容錯機制,訊息group及監控服務,同時擴充套件了很多成熟的框架
- 高階特性:訊息群組(Message Groups)、虛擬端點(Virtual Destinations)、萬用字元(Wildcards)、複合端點(Composite Destinations)
ActiveMQ在Windows上的安裝配置
這方面的教程在網上有很多,我們在這就不提供了,只提供一些移動端友好的連結以幫助朋友安裝配置
- www.cnblogs.com/yangw/p/591…
- www.cnblogs.com/chy123/p/87…
- www.cnblogs.com/donsenChen/…
- blog.csdn.net/j080624/art…
ActiveMQ在C#中的使用
首先,需要在 Apache官網 上下載 .NET 的驅動,也可以通過以下連結下載
mirrors.hust.edu.cn/apache/acti…
要在專案中使用 ActiveMQ,需要引入上面下載的包中的兩個 dll 檔案:Apache.NMS.ActiveMQ.dll
和 Apache.NMS.dll
P2P模式案例
P2P模式包含三個角色:訊息佇列(Queue),傳送者(Sender),接收者(Receiver)。
每條訊息都被髮送到一個特定的佇列,接收者從佇列中獲取訊息。佇列保留著訊息,直到它們被消費或超時
P2P的特點:
- 每條訊息只有一個消費者(即一旦被消費,訊息就會被移除訊息佇列):在執行了多個消費者之後,一條訊息只會有一個消費者收到,其他的消費者是不可以收到的
- 接收者在成功接收訊息之後需向佇列應答成功:我們可以通過指定應答模式來更改,預設是自動應答模式
因此,如果希望傳送的每個訊息都會被成功處理的話,則應該P2P模式
示例程式碼的基類如下
public abstract class ActiveMQBase {
protected IConnectionFactory factory;
protected IConnection connection;
protected ISession session;
public virtual void Init() {
try {
//初始化工廠, 埠預設為61616,指定其他會拋異常
factory = new ConnectionFactory("tcp://localhost:61616");
connection = factory.CreateConnection();
connection.Start();
session = connection.CreateSession();
} catch (Exception e) {
Console.WriteLine($"Error: {e.Message}");
}
}
public abstract void Run();
// 釋放相關資源
public virtual void Release() {
try {
if (session != null) session.Close();
if (connection != null) connection.Close();
} finally {
session = null;
connection = null;
factory = null;
}
}
}
複製程式碼
生產者(Producer)如下
public class ActiveMQP2PDemoProducer : ActiveMQBase {
private IMessageProducer messageProducer;
private ActiveMQQueue demoQueue;
public override void Init() {
base.Init();
try {
// 指定佇列,以實現點對點的通訊
demoQueue = new ActiveMQQueue("DEMO_QUEUE");
// 建立生產者物件
messageProducer = session.CreateProducer(demoQueue);
} catch (Exception e) {
Console.WriteLine($"Error: {e.Message}");
}
}
public override void Run() {
while (true) {
Console.WriteLine("請輸入訊息,exit 退出");
string line = Console.ReadLine();
if (line.Equals("exit", StringComparison.InvariantCultureIgnoreCase)) {
break;
}
// 建立一條文字訊息,在 MessageProvider 中存在多個建立訊息的方法
// 在實際專案中靈活選擇即可
ITextMessage message = messageProducer.CreateTextMessage(line);
// 傳送訊息,可呼叫其他的過載,以設定是否持久化、優先順序等特性
messageProducer.Send(message);
}
}
public override void Release() {
base.Release();
try {
if (demoQueue != null) demoQueue.Dispose();
if (messageProducer != null) messageProducer.Close();
} finally {
demoQueue = null;
messageProducer = null;
}
}
}
複製程式碼
消費者(Consumer)如下
public class ActiveMQP2PDemoComsumer : ActiveMQBase {
private IMessageConsumer messageConsumer;
private ActiveMQQueue demoQueue;
public override void Init() {
base.Init();
try {
demoQueue = new ActiveMQQueue("DEMO_QUEUE");
// 建立訊息的消費者
messageConsumer = session.CreateConsumer(demoQueue);
// 新增監聽,當訊息來臨時,會觸發此事件
messageConsumer.Listener += this.MessageConsumer_Listener;
} catch (Exception e) {
Console.WriteLine($"Error: {e.Message}");
}
}
private void MessageConsumer_Listener(IMessage message) {
// 解析接收到的訊息
if (message is ITextMessage msg) {
Console.WriteLine($"Received Message: {msg.Text}");
}
}
public override void Run() {
// 此處用於阻止控制檯結束,以保證訊息可被正確處理
Console.WriteLine("請輸入訊息,exit 退出");
string line = Console.ReadLine();
}
public override void Release() {
base.Release();
try {
if (demoQueue != null) demoQueue.Dispose();
if (messageConsumer != null){
messageConsumer.Listener -= this.MessageConsumer_Listener;
messageConsumer.Close();
}
} finally {
demoQueue = null;
messageConsumer = null;
}
}
}
複製程式碼
使用方式如下
// 生產者初始化
ActiveMQP2PBase demo = new ActiveMQP2PDemoProducer();
// 消費者初始化程式碼則為: ActiveMQP2PBase demo = new ActiveMQP2PDemoComsumer();
demo.Init();
demo.Run();
demo.Release();
複製程式碼
在 ActiveMQ 管理介面可以看到如下,表示生產者傳送的訊息,都已經被消費者消費了
Pub/Sub模式
Pub/Sub模式:包含三個角色主題(Topic),釋出者(Publisher),訂閱者(Subscriber)。多個釋出者將訊息傳送到Topic, 系統將這些訊息傳遞給多個訂閱者,可以認為生產者與消費者之間是多對多的關係
Pub/Sub的特點
- 每條訊息可以有多個消費者
- 為了消費訊息,訂閱者必須保持執行的狀態
- 為了緩和這樣嚴格的時間相關性,JMS 允許訂閱者建立一個可持久化的訂閱。這樣即使訂閱者沒有執行,在執行之後它也能接收到釋出者的訊息
因此,如果允許傳送的訊息可以被一個或多個消費者消費、或者可以不被消費,那麼可以採用 Pub/Sub 模型
在 C# 中,它與 P2P 的使用區別不大,只需要將上述程式碼生產者和消費者初始化程式碼中
demoQueue = new ActiveMQQueue("DEMO_QUEUE");
複製程式碼
這部分換成
demoTopic = new ActiveMQTopic("DEMO_TOPIC");
複製程式碼
在管理員介面可以看到如下資料
通過示例可以看出,P2P 是基於 Queue 的,而 Pub/Sub 模式則是基於 Topic 的。
在 Pub/Sub 模式下,可以實現多對多的通訊,即可以有多個生產者,也可以有多個消費者,一旦有訊息到來,它們會都會收到訊息。
而P2P模式下,它可以允許有多個生產者,也可以有多個消費者。與 Pub/Sub 不同的是,如果有多個消費者,如果有訊息到來,這些消費者會輪流著去消費該訊息,而不是每個消費者都收到訊息。即一條訊息只會有一個消費者
由於在 C# 中,這兩種模式的使用方式差別很小,而執行之後產生的行為卻差別較大。因此,在實際專案中,我們需要注意這兩者之間的區別,以免帶來不必要的困惑
實際專案中的一些問題
ActiveMQ伺服器當機怎麼辦 如果我們想要在伺服器當機之後恢復資料,則需要對訊息進行持久化
在通常的情況下,非持久化訊息是儲存在記憶體中的,持久化訊息是儲存在檔案中的。它們的最大限制在配置檔案的<systemUsage>
節點中配置
但是,在非持久化訊息堆積到一定程度,記憶體告急的時候,ActiveMQ 會將記憶體中的非持久化訊息寫入臨時檔案中,以騰出記憶體。雖然都儲存到了檔案裡,但它和持久化訊息的區別是,重啟後持久化訊息會從檔案中恢復,非持久化的臨時檔案會直接刪除(即重啟之後不會從臨時檔案中恢復訊息)
因此,為了保證資料的可靠性
- 儘量使用持久化訊息(訊息不重要也可以不用持久化)
- 可以將持久化與非持久化檔案的限制調大一點,以保證服務最大可用
丟訊息
這同樣是持久化訊息的問題。對於這種情況,我們可以
- 儘量將訊息持久化
- 如果不想持久化,那麼我們應該儘可能的及時處理非持久化的訊息
- 使用事務,它可以保證訊息不會因為連線關閉而丟失
持久化訊息比較慢
預設的情況下,非持久化訊息是非同步傳送的;而持久化訊息是同步傳送的。遇到慢一點的硬碟,傳送訊息的速度也會很慢
但如果開啟事務的情況下,訊息都會非同步傳送,效率會有非常大的提升。所以在傳送持久化訊息時,我們應該務必開啟事務。並且我們也建議傳送非持久化訊息時也開啟事務
自定義 ActiveMQ 的重發策略(Redelivery Policy)
可通過 ConnectionFactory.RedeliveryPolicy
屬性設定
CollisionAvoidancePercent
:預設值 0.15, 設定防止衝突範圍的正負百分比,只有啟用UseCollisionAvoidance
引數時才生效MaximumRedeliveries
:預設值 6, 最大重傳次數,達到最大重連次數後丟擲異常。為-1時不限制次數,為0時表示不進行重傳InitialRedeliveryDelay
:預設值 1000, 初始重發延遲時間UseCollisionAvoidance
:預設值false
, 啟用防止衝突功能UseExponentialBackOff
:預設值false
, 啟用指數倍數遞增的方式增加延遲時間BackOffMultiplier
:預設值 5, 重連時間間隔遞增倍數,只有值大於1和啟用UseExponentialBackOff
引數時才生效。
多消費者併發處理
在有多個消費者,ActiveMQ 中累積了大量的資料的情況下,有可能會出現只有一個消費者消費、其他消費者不“工作”的情況
這種情況下,我們只需要將 ActiveMQ 的 prefetch
值設定得小一點即可。在 Queue模式時,其預設值為 1000;Topic 下為 32766。可通過 ConnectionFactory.PrefetchPolicy
設定
這篇文章就先講到這裡,後面我們會講解 ActiveMQ 的一些其他場景,如分散式叢集。歡迎持續關注公眾號【嘿嘿的學習日記】,Thank you~