(本教程是使用Net客戶端,也就是針對微軟技術平臺的)
在前一個教程中,我們建立了一個工作佇列。工作佇列背後的假設是每個任務會被交付給一個【工人】。在這一部分我們將做一些完全不同的事情--我們將向多個【消費者】傳遞資訊。這種模式被稱為“釋出/訂閱”。
為了說明這種模式,我們將構建一個簡單的日誌系統。它將包括兩個程式,第一個將發出日誌訊息,第二個將接收並列印它們。
在我們的日誌系統中每個接收程式的執行副本都會得到訊息。這樣我們就可以執行一個接收者程式,將日誌記錄到磁碟;同時我們可以執行另一個接收者程式,並在螢幕上看到列印出來的日誌。
從本質上講,已釋出的日誌訊息將被廣播到所有的接收者程式。
1、訊息交換機【Exchange】
在教程的前面部分,我們從佇列中傳送和接收訊息。在RabbitMQ中,現在是時候引入全訊息模型。
讓我們快速看看我們以前的教程講了什麼:
【生產者】:就是一個用於傳送訊息的使用者程式
【消費者】:就是一個用於接收和使用訊息的使用者程式
【佇列】:是一個暫存訊息的快取區
RabbitMQ訊息傳遞模型的核心思想是,【生產者】不直接傳送任何資訊到佇列。事實上,【生產者】根本就不知道訊息是否會被傳送到任何佇列。
相反,【生產者】只能傳送訊息到【訊息交換機】。交換是件很簡單的事。一方面它接收來自【生產者】的訊息,另一方面是將接收到訊息推送到佇列中。【訊息交換機】必須知道它如何處理接收訊息的確切方法。是否應該傳送到特定佇列?它應該被髮送到多個佇列呢?或者它應該被丟棄。該規則由【訊息交換機】的型別來定義。
這裡有一些可用的【訊息交換機】的型別:【Direct】直接,【Topic】主題,【Headers】標題和【Fanout】扇出。我們將集中關注最後一個-【Fanout】扇出。讓我們建立一個這種型別的【訊息交換機】,並給它命名為Logs:
channel.ExchangeDeclare("logs", "fanout");
【Fanout】型別的【訊息交換機】非常簡單。正如你從名字可能猜出的,它只是傳播它收到的所有訊息去它知道所有的佇列中。這正是我們需要我們的日誌記錄器。
顯示【訊息交換機】的列表:
使用Rabbitmqctl列出在伺服器上可以執行的最有用的【訊息交換機】
sudo rabbitmqctl list_exchanges
在這個列表中會有一些amq.*【訊息交換機】和預設(未命名)訊息交換機。這些都是預設建立的,但現在不太可能需要使用它們。
預設的訊息交換機
在教程前面的部分我們隊【訊息交換機】是一無所知,但是我依然可以傳送訊息去想去的佇列,那是因為我們使用了預設的【訊息交換機】,這些預設的訊息 交換機我用使用兩個雙引號“”來標識。
我們回憶一下以前是如何傳送訊息的:
var message = GetMessage(args); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "", routingKey: "hello", basicProperties: null, body: body);
第一個引數是【訊息交換機】的名稱。空字串表示預設或未命名的訊息交換機:訊息會被路由到指定的routingkey名稱的佇列,如果它存在的話。
現在,我們可以釋出到我們命名的【訊息交換機】:
var message = GetMessage(args); var body = Encoding.UTF8.GetBytes(message); channel.BasicPublish(exchange: "logs", routingKey: "", basicProperties: null, body: body);
2、臨時佇列
也許你還記得以前我們使用的佇列所指定的名稱(記得Hello和task_queue嗎?)對我們來說,能夠給一個佇列指定名稱是至關重要的--因為我們需要把【Worker】指向同一個佇列。如果要在【生產者】和【消費者】之間共享佇列,給佇列命名是很重要的。
但這不是我們的日誌記錄器的情況。我們想聽到所有的日誌訊息,而不僅僅是其中的一個子集。我們也只對當前剛剛收到的訊息感興趣,而不是對舊的。為了解決上述問題,我們需要做兩件事。
首先,無論何時當我們連線到Rabbit的時候,我們都需要一個新的並且是空的佇列。要做到這一點,我們可以建立一個具有隨機名稱的佇列,或者,甚至更好一點-讓伺服器為我們選擇一個隨機佇列名稱。
其次,一旦我們斷開與【消費者】的佇列就應該自動刪除該佇列。
在.NET客戶端中,當我們沒有為queueDeclare()提供引數時,我們建立了一個具有生成名稱的非持久,排他,自動刪除佇列:
var queueName = channel.QueueDeclare().QueueName;
在這點上,QueueName包含隨機佇列名稱。例如,它可能看起來像amq.gen-jzty20brgko-hjmujj0wlg。
3、繫結【Binding】
我們已經建立了一個【Fanout】型別的【訊息交換機】和佇列。現在我們需要告訴【訊息交換機】向我們的佇列傳送訊息。【訊息交換機】和【佇列】之間的關係稱為繫結。
channel.QueueBind(queue: queueName, exchange: "logs", routingKey: "");
從現在開始,日誌的【訊息交換機】就可以將訊息推送到我們定義的佇列中去了。
我們可以通過以下語句檢視【binding】列表資料:
rabbitmqctl list_bindings
4、把所有的程式碼整合到一起
【生產者】的程式,它發出的日誌訊息,看起來並沒有和以前的教程有很大的不同。最重要的變化是,我們現在想傳送的訊息是到達我們指定名稱的日誌【訊息交換機】,而不是無名的。我們在傳送訊息的時候需要提供一個routingkey表示的名稱,但【Fanout】型別的【訊息交換機】會容忽視該routingKey的值的。這裡有EmitLog.cs檔案程式碼:
1 using System; 2 using RabbitMQ.Client; 3 using System.Text; 4 5 class EmitLog 6 { 7 public static void Main(string[] args) 8 { 9 var factory = new ConnectionFactory() { HostName = "localhost" }; 10 using(var connection = factory.CreateConnection()) 11 using(var channel = connection.CreateModel()) 12 { 13 channel.ExchangeDeclare(exchange: "logs", type: "fanout"); 14 15 var message = GetMessage(args); 16 var body = Encoding.UTF8.GetBytes(message); 17 channel.BasicPublish(exchange: "logs", 18 routingKey: "", 19 basicProperties: null, 20 body: body); 21 Console.WriteLine(" [x] Sent {0}", message); 22 } 23 24 Console.WriteLine(" Press [enter] to exit."); 25 Console.ReadLine(); 26 } 27 28 private static string GetMessage(string[] args) 29 { 30 return ((args.Length > 0) 31 ? string.Join(" ", args) 32 : "info: Hello World!"); 33 } 34 }
(EmitLog.cs 的原始碼)
如你所見,在建立連線後,我們宣告瞭【訊息交換機】。此步驟是必要的,因為對非存在【訊息交換機】的傳送是被禁止的。
如果沒有佇列繫結到【訊息交換機】,訊息將會丟失,但對我們來說沒有問題;如果沒有【消費者】正在偵聽,我們可以安全地丟棄訊息。
以下是ReceiveLogs.cs的程式碼:
1 using System; 2 using RabbitMQ.Client; 3 using RabbitMQ.Client.Events; 4 using System.Text; 5 6 class ReceiveLogs 7 { 8 public static void Main() 9 { 10 var factory = new ConnectionFactory() { HostName = "localhost" }; 11 using(var connection = factory.CreateConnection()) 12 using(var channel = connection.CreateModel()) 13 { 14 channel.ExchangeDeclare(exchange: "logs", type: "fanout"); 15 16 var queueName = channel.QueueDeclare().QueueName; 17 channel.QueueBind(queue: queueName, 18 exchange: "logs", 19 routingKey: ""); 20 21 Console.WriteLine(" [*] Waiting for logs."); 22 23 var consumer = new EventingBasicConsumer(channel); 24 consumer.Received += (model, ea) => 25 { 26 var body = ea.Body; 27 var message = Encoding.UTF8.GetString(body); 28 Console.WriteLine(" [x] {0}", message); 29 }; 30 channel.BasicConsume(queue: queueName, 31 noAck: true, 32 consumer: consumer); 33 34 Console.WriteLine(" Press [enter] to exit."); 35 Console.ReadLine(); 36 } 37 } 38 }
(ReceiveLogs.cs 的原始碼)
按照教程一的安裝說明生成的EmitLogs和ReceiveLogs兩個專案檔案。
如果要將日誌儲存到檔案,只需開啟控制檯並鍵入:
cd ReceiveLogs
dotnet run > logs_from_rabbit.log
如果你希望看到你的螢幕上的日誌,生成一個新的終端和執行:
cd ReceiveLogs
dotnet run
當然,要傳送日誌型別:
cd EmitLog
dotnet run
使用rabbitmqctl list_bindings可以驗證程式碼確實建立了我們想要的【繫結】和【佇列】。執行兩個ReceiveLogs.cs程式時,您應該看到如下所示:
rabbitmqctl list_bindings # => Listing bindings ... # => logs exchange amq.gen-JzTY20BRgKO-HjmUJj0wLg queue [] # => logs exchange amq.gen-vso0PVvyiRIL2WoV3i48Yg queue [] # => ...done.
對結果的解釋很簡單:來自【訊息交換機】日誌的資料轉到具有伺服器分配名稱的兩個佇列。 這正是我們的意圖。
好了,終於翻譯了第三篇教程了,翻譯的不好,請見諒。如有大家英文比較好可以檢視原文地址:http://www.rabbitmq.com/tutorials/tutorial-three-dotnet.html