前言
吃多了拉就是佇列,吃飽了吐就是棧
- 使用場景
- 對操作的實時性要求不高,而需要執行的任務極為耗時;(傳送簡訊,郵件提醒,更新文章閱讀計數,記錄使用者操作日誌)
- 存在異構系統間的整合;
安裝
-
- 安裝完確定ERLANG_HOME環境變數是否新增,否則:
Setx ERLANG_HOME “D:\Program Files\erl8.2″
- 安裝完確定ERLANG_HOME環境變數是否新增,否則:
-
- 安裝完透過
rabbitmqctl status
確定rabbitmq狀態
- 安裝完透過
-
管理服務
- 預設安裝成功會自動啟動服務
- 透過開始選單可以啟動,停止,解除安裝服務
-
佔用埠
- 4369(叢集、Erlang)
- 5671,5672(應用層標準高階訊息佇列協議)
- 25672(Erlang分發,CLI通訊)
- 15672(如果管理外掛啟用)
- 61613,61614(如果訊息文字協議STOMP已啟用)
- 1883,8883(如果erl實時通訊已啟用)
-
支援的平臺
- 基於Ubuntu和Debian的Linux發行版
- 基於Fedora,CentOS和RPM的Linux發行版
- Mac OS X
- Windows XP及更高版本
概念
-
Connections:客戶端連線,建立該資源非常耗時,應儘量避免多次建立。
-
Channel:訊息通道,在客戶端的每個連線裡,可建立多個channel,每個channel代表一個會話任務。
-
Exchange:訊息交換機,它指定訊息按什麼規則,路由到哪個佇列。
-
Queue:訊息佇列載體,每個訊息都會被投入到一個或多個佇列。
-
Broker:簡單來說就是訊息佇列伺服器實體。
-
Binding:繫結,它的作用就是把exchange和queue按照路由規則繫結起來。
-
Routing Key:路由關鍵字,exchange根據這個關鍵字進行訊息投遞。
-
vhost:虛擬主機,一個broker裡可以開設多個vhost,用作不同使用者的許可權分離。
-
producer:訊息生產者,就是投遞訊息的程式。
-
consumer:訊息消費者,就是接收訊息的程式。
訊息佇列的傳送過程大概如下:
- 客戶端建立Connection,連線到訊息佇列伺服器,開啟一個channel。
- 客戶端宣告一個Exchange,並設定相關屬性。
- 客戶端宣告一個Queue,並設定相關屬性。
- 客戶端使用routing key,在exchange和queue之間建立好繫結關係。
- 客戶端傳送訊息首先到exchange
- exchange根據type路由到對應的佇列(可以是多個佇列)中.
Exchange Type
- direct(直連)
- routing key 與 binding key相同
- fanout
- 給所有繫結佇列傳送訊息
- topic
- routing key:audit.irs.corporate => binding key:audit.#
- routing key:audit.irs => binding key:audit.*
- default
- direct
- binding key為queue名稱
常用命令
-
管理外掛
rabbitmq-plugins enable rabbitmq_management
// 啟用rabbitmq-plugins disable rabbitmq_management
// 禁用
-
管理佇列
rabbitmqctl list_queues
// 檢視佇列
-
管理使用者及許可權
rabbitmqctl list_users
// 檢視所有使用者rabbitmqctl add_user user_admin passwd_admin
// 新增使用者rabbitmqctl set_user_tags user_admin administrator
// 新增許可權rabbitmqctl delete_user guest
// 刪除使用者rabbitmqctl change_password {username} {newpassowrd}
// 修改密碼
-
管理虛擬主機vhost
rabbitmqctl add_vhost vhostpath
// 建立虛擬主機rabbitmqctl delete_vhost vhostpath
// 刪除虛擬主機rabbitmqctl list_vhosts
// 列出所有虛擬主機
使用
- 傳送訊息(以持久化程式碼為例)
var factory = new ConnectionFactory
{
HostName = hostName, // rabbit server
UserName = "admin",
Password = "admin",
Port = 5672, // Broker埠
VirtualHost = "/" // 虛擬Host,需提前配置
};
using (var connection = factory.CreateConnection()) // 建立與RabbitMQ伺服器的連線
{
using (var channel = connection.CreateModel()) // 建立1個Channel(大部分API在該Channel中)
{
// 定義1個佇列,自動會和預設的exchange 做direct型別繫結
channel.QueueDeclare(
queue: "hello", // 佇列名稱
durable: true, // 佇列是否持久化
exclusive: false, // 排他佇列:如果一個佇列被宣告為排他佇列,該佇列僅對首次宣告它的連線可見,並在連線斷開時自動刪除。(活動在一次連線內)
autoDelete: false, // 自動刪除:當最後一個消費者取消訂閱時,佇列自動刪除。如果您需要僅由一個使用者使用的臨時佇列,請將自動刪除與排除。當消費者斷開連線時,佇列將被刪除。(至少消費者能連一次)
arguments: null); // 配置引數
var randomQueue = channel.QueueDeclare(); // 定義隨機的佇列 該佇列為臨時佇列(排他佇列 + 自動刪除)
// 定義Exchange(一般而言,不需要定義exchange,rabbitmq預設建立了所有型別的exchange)
//channel.ExchangeDeclare("direct-demo", ExchangeType.Direct); // 定義direct exchange
//channel.ExchangeDeclare("fannout-demo", ExchangeType.Fanout); // 定義fanout exchange
//channel.ExchangeDeclare("topic-demo", ExchangeType.Topic); // 定義fanout exchange
// 定義queue exchange key 關係(在某些業務場景下,會使用該關係做路由功能)
//channel.QueueBind(queue: "hello", exchange: "amq.direct", routingKey: "hello"); // 預設繫結的關係和該行程式碼效果一樣
//channel.QueueBind("hello", "amq.fanout", "hello"); // 該型別下的routingKey 實際不需要
var properties = channel.CreateBasicProperties();
properties.Persistent = true;
while (true)
{
string message = "Hello World!" + DateTime.Now;
var body = Encoding.UTF8.GetBytes(message);
// 傳送訊息到佇列中
channel.BasicPublish(
exchange: string.Empty, // 傳遞為Empty的時候,透過 `(AMQP default)`傳遞
routingKey: "hello", // routing key 與 queuebind中的binding key對應
basicProperties: properties, // 訊息header
body: body); // 訊息body:傳送的是bytes 可以任意編碼
Console.WriteLine(" [x] Sent {0}", message);
}
}
}
- 接收訊息(以訊息響應為例)
var factory = new ConnectionFactory
{
HostName = hostName, // rabbit server
UserName = "admin",
Password = "admin",
Port = 5672, // Broker埠
VirtualHost = "/" // 虛擬Host,需提前配置
};
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
var consumer = new EventingBasicConsumer(channel); // 建立Consumer
consumer.Received += (model, ea) => // 透過回撥函式非同步推送我們的訊息
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
Thread.Sleep(1000);
channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false); // 訊息響應
Console.WriteLine(" [x] Received {0}", message);
};
channel.BasicQos(0, 1, false); // 設定perfetchCount=1 。這樣就告訴RabbitMQ 不要在同一時間給一個工作者傳送多於1個的訊息
channel.BasicConsume(queue: "hello",
noAck: false, // 需要訊息響應(Acknowledgments)機制
consumer: consumer);
Console.WriteLine(" Press [enter] to exit.");
Console.ReadLine();
}
}
-
訊息響應(acknowledgments)
- 為了防止訊息丟失,RabbitMQ提供了訊息響應(acknowledgments)機制。消費者會透過一個ack(響應),告訴RabbitMQ已經收到並處理了某條訊息,然後RabbitMQ才會釋放並刪除這條訊息。
-
持久化
- 新佇列(無法修改佇列)配置為可持久化
- 傳送訊息配置為持久化
- 訊息什麼時候刷到磁碟?
- 寫入檔案前會有一個Buffer,大小為1M,資料在寫入檔案時,首先會寫入到這個Buffer,如果Buffer已滿,則會將Buffer寫入到檔案(未必刷到磁碟)。
- 固定的刷盤時間:25ms,也就是不管Buffer滿不滿,每個25ms,Buffer裡的資料及未重新整理到磁碟的檔案內容必定會刷到磁碟。
- 每次訊息寫入後,如果沒有後續寫入請求,則會直接將已寫入的訊息刷到磁碟:使用Erlang的receive x after 0實現,只要程式的信箱裡沒有訊息,則產生一個timeout訊息,而timeout會觸發刷盤操作。
常見問題
-
RabbitMQ 管理外掛啟動報錯
- 確認RabbitMQ服務是否啟動
- C:\Windows目錄下,將.erlang.cookie檔案,複製到使用者目錄下 C:\Users{使用者名稱},這是Erlang的Cookie檔案,允許與Erlang進行互動
- 重新安裝erl 和 rabbit,儘量不要帶空格的路徑
-
修改配置檔案