使用RabbitMQ創建AMQP示例應用程序

使用RabbitMQ創建AMQP示例應用程序

RabbitMQ提供了一個名爲RabbitMQ.Client的.NET客戶機庫。在這裏可以找到關於客戶端的文檔。
與所有新技術一樣,文檔是空白的,API是原始的和無組織的,幾乎所有的東西都可以從一個對象——IModel中訪問。
首先要做的事情之一是:

  1. 通過圍繞最常見的用例組織功能,使其更容易使用。

    • 發佈消息
    • 接受消息
  2. 將聲明與功能分開

    • 創建exchange(交換機),queue(隊列)和綁定過程是獨立的。
    • 發佈和接受消息的過程。
  3. 使用app.config文件進行配置。

在我們設計上面描述的簡化api之前,讓我們簡要地介紹一下RabbitMQ的功能,
客戶提供下面是一些示例代碼,以突出RabbitMQ的方式。
客戶端API允許我們與RabbitMQ服務器進行交互。

//Creating Connections
RabbitMQ.Client.ConnectionFactory factory = new RabbitMQ.Client.ConnectionFactory();
factory.Endpoint = new AmqpTcpEndpoint(server);
factory.UserName = userName;
factory.Password = password;

IConnection Connection = factory.CreateConnection();

要連接到RabbitMQ服務器,必須在客戶機和服務器之間建立連接,
如上所示。第7行創造了我們將在下面的例子中使用的連接。

連接並不是我們要用來與服務器通信的東西,
相反,我們使用的是連接對象爲我們創建的專用通信通道IModel對象。

IModel是客戶端和代理之間的通信通道,可以建立多個通道。
RabbitMQ的一個奇怪之處是IModel無法在線程之間共享對象,
我們需要確保在設計應用程序時考慮到這一點。
下一節將介紹通道對象提供的一些最常用的功能。

using (IModel channel = Connection.CreateModel())
{
    ... // Object declaration //
   channel.ExchangeDeclare( /* parameters */);
   channel.QueueDeclare( /* parameters */);
   channel.QueueBind( /* parameters */);

// Publishing 
   channel.BasicPublish( /* object parameters */);

// Synchronous receiving
   channel.BasicGet( /* parameters */);

// Acknowledging a message
   channel.BasicAck( /* parameters */);

// Reject a message
   channel.BasicReject( /* parameters */);

// Requeue a message
   channel.BasicReject( /* parameters - make sure to set requeue = true */);
}

奇怪的是,Requeueing消息使用了該BasicReject(…)方法。
BasicReject有一個requeue參數,通過設置爲true,消息被重新排隊。

創建專用AMQP App配置部分

當我們使用RabbitMQ時,我們必須配置服務器以適當地接收和分發消息給客戶端,
以及配置我們的應用程序以從適當的交換或隊列發送或接收消息。
最簡單的方法是使用配置文件來執行此操作,
這也允許我們在應用程序部署到各種環境時輕鬆更改這些值。
我選擇爲RabbitMQ創建一個專用配置部分,因爲需要配置很多,
我想將所有RabbitMQ配置元素保存在一起。結果如下所示
(代碼可以在CodePlex項目中找到:codeplex_link)

<configuration>
  <configsections>
    <sectiongroup name="AMQPConnection">
      <section name="ConnectionSettings" 
      type="Sample.Configuration.AMQP.Config.ConnectionSection, 
      Sample.Configuration.AMQP" />
    </sectiongroup>
    <sectiongroup name="AMQPAdmin">
      <section name="AMQPObjectsDeclaration" 
      type="Sample.Configuration.AMQP.Config.AMQPObjectsDeclarationSection, 
      Sample.Configuration.AMQP" allowlocation="true" 
      allowdefinition="Everywhere" />
    </sectiongroup>
  </configsections>
  <amqpadmin>
    <amqpobjectsdeclaration>
      <exchangelist>
        <add name="orders" type="topic" 
        durable="true" autodelete="false" />
      </exchangelist>
      <queuelist>
        <add name="uk_orders" durable="true" 
        autodelete="false" />
      </queuelist>
      <bindinglist>
        <add subscriptionkey="order.uk.#" 
        queue="uk_orders" exchange="orders" />
      </bindinglist>
    </amqpobjectsdeclaration>
  </amqpadmin>
  <amqpconnection>
    <connectionsettings>
      <connection name="connection" 
      username="guest" server="devserver-rr1" 
      password="guest" />
      <publisher exchange="orders" />
      <asyncreceiver queue="uk_orders" maxthreads="4" />
    </connectionsettings>
  </amqpconnection>
  </configuration>

按行描述配置文件

  • 4,7:將配置節解釋器映射到項目中的相應類。每個XML元素代表一個對象,必須進行解釋
  • 13.創建一個稱爲持久的訂單的主題交換(意味着它將持續服務器重新啓動)並且不是自動刪除
  • (如果autodelete標誌設置爲true,則在所有客戶端完成發佈後將刪除交換)。
  • 16.創建一個名爲uk_orders 的隊列來表示英國客戶的所有訂單。隊列設置爲持久而不是自動刪除
    1. 使用uk_orders訂閱密鑰將uk_orders隊列綁定到orders交換order.uk.#
    2. 這樣,所有以order.uk 開頭的訂單都將以該隊列結束
  • 25:配置與服務器的連接字符串。
  • 26 ,. 配置發佈者將消息發佈到訂單交換
  • 27.配置異步接收器使用4個線程在uk_orders它們到達時從uk_orders隊列中獲取訂單。

配置部分可以接受每種類型的AMQP對象(交換,隊列和綁定)的列表。

RabbitAdmin

那麼在用於創建交換,隊列及其綁定時,它會是什麼樣子?

namespace Sample.Configuration.AMQP.Admin
{
    public class RabbitAdmin
    {
        internal static void InitializeObjects(IConnection Connection)
        {
            var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            var objects = config.GetSection
            ("AMQPAdmin/AMQPObjectsDeclaration") as AMQPObjectsDeclarationSection;
            if (objects != null)
            {
                Parallel.For(0, objects.ExchangeList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var exchange = objects.ExchangeList[i];
                        channel.ExchangeDeclare(exchange.Name, 
                        exchange.Type.ToString(), exchange.Durable, exchange.AutoDelete, null);
                    }
                });
                Parallel.For(0, objects.QueueList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var queue = objects.QueueList[i];
                        channel.QueueDeclare(queue.Name, queue.Durable, 
                        queue.Exclusive, queue.AutoDelete, null);
                    }
                });
                Parallel.For(0, objects.BindingList.Count, i =>
                {
                    using (IModel channel = Connection.CreateModel())
                    {
                        var binding = objects.BindingList[i];
                        channel.QueueBind(binding.Queue, 
                        binding.Exchange, binding.SuscriptionKey);
                    }
                });
            }
      }
}

在RabbitAdmin類創建的交流,隊列及其綁定。
它接受一個連接對象並使用它與RabbitMQ服務器進行通信。
在InitializeObjects使用平行for循環創建每種類型的對象。
首先創建交換,然後將隊列綁定聲明爲最後一個,因爲它們需要隊列和交換來綁定兩者。

GatewayFactory

在GatewayFactory創建與服務器的連接,調用RabbitAdmin聲明中的所有對象,
並提供方法來創建發佈者和聽衆同步助手對象。消息傳遞給發佈者並從隊列接收,
消息對象是一個非常簡單的對象,它包含一個標題和一個正文。


/// <summary>
/// The object encapsulating all the common message properties
/// transmitted to and received from the message bus.
/// </summary>
public class Message {
    public IBasicProperties Properties { get; set;}
    public byte[] Body { get; set; }
    public string RoutingKey { get; set; }
}

正文是一個簡單的字節數組,您的內部數據結構被序列化。
爲了能夠一般地處理數據結構和消息之間的轉換,包中包含以下委託和接口。


namespace Sample.Configuration.AMQP.Gateway
{
    public delegate Message ConvertToMessage(IModel channel, object packetToSend);

    public interface IConvertToMessage {
        Message ConvertObjectToMessage( IModel channel, object packetToSend);
    }
}

系統的客戶端負責從其數據結構轉換爲消息結構。

Publishing


using (var gf = new GatewayFactory())
{
    var mc = new Sample.Configuration.AMQP.Gateway.Converter.StringToMessageConverter();
    var publisher = gf.GetPublisher(mc.ConvertObjectToMessage);
    publisher.Publish("Hello world");
}

隨着配置的到位,出版是一個四線的事情。
只需創建一個GatewayFactory,在內部讀取配置文件並設置所有內容。
然後請求發佈者並傳遞objectToMessage轉換處理程序。
該庫附帶了一個默認string的消息轉換器,它在第5行中使用,對XML文檔很有用。
否則,必須創建自定義轉換器。
爲了讓您瞭解如何創建轉換器,讓我們來看看string轉換器。


public class StringToMessageConverter : IConvertToMessage
{
    public static readonly string PLAIN_TEXT = "text/plain";
    public const string _defaultCharSet = "utf-8";
    public string CharSet { get; set; }
    public StringToMessageConverter()
    {
        CharSet = _defaultCharSet;
    }
    public virtual Message ConvertObjectToMessage
    (RabbitMQ.Client.IModel channel, object packetToSend)
    {
        var properties = channel.CreateBasicProperties();
        var bytes = Encoding.GetEncoding(CharSet).GetBytes((string)packetToSend);
        properties.ContentType = PLAIN_TEXT;
        properties.ContentEncoding = CharSet;
        return new Message()
        { Body = bytes, Properties = properties, RoutingKey = string.Empty };
    }
}

轉換爲消息主要是將需要發送的任何內容轉換爲位數組,讓接收者知道內容是什麼並設置RoutingKey。

The Asynchronous Receiver (異步接收)

異步接收消息就像發佈一樣簡單。接收方必須將消息的狀態與服務器通信 - 它可以被確認,拒絕或重新排隊。

class Program
{
    static void Main(string[] args)
    {
        var mp = new MessageProcessor();
        using (var cf = new GatewayFactory())
        {
            cf.GetAsyncReceiver(mp.ConsumeMessage);
        }
    }
}

class MessageProcessor : IMessageConsumer
{
    public void ConsumeMessage
    (Message message, RabbitMQ.Client.IModel channel, DeliveryHeader header)
    {
        try
        {
            var str = ConvertFromMessageToString(message);
            channel.BasicAck(header.DeliveryTag, false);
        }
        catch (Exception ex)
        {
            channel.BasicReject(header.DeliveryTag, true);
        }
    }
    public string ConvertFromMessageToString(Message message)
    {
        var content = string.Empty;
        if (message.Properties.ContentType == StringToMessageConverter.PLAIN_TEXT)
        {
            var encoding = Encoding.GetEncoding
            (message.Properties.ContentEncoding ?? "utf-8");
            var ms = new MemoryStream(message.Body);
            var reader = new StreamReader(ms, encoding, false);
            content = reader.ReadToEnd();
        }
        return content;
    }
}

在第8行,我們將消息處理程序傳遞給異步接收器。
異步接收器是多線程的,並且處理程序應該是無狀態的,以便它可以被多個線程同時使用。
響應服務器是通過IModel對象完成的。Delivery標頭包含a deliverytag和a flag,表示這是否是第一次傳遞。
可以在第19行和第20行之間完成對消息有用的東西。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章