簡體   English   中英

從多個隊列中讀取,RabbitMQ

[英]Reading from multiple queues, RabbitMQ

我是 RabbitMQ 的新手。 當有多個隊列(要讀取)時,我希望能夠處理讀取消息而不會阻塞。 關於我如何做到這一點的任何意見?

//編輯1

public class Rabbit : IMessageBus
{   

    private List<string> publishQ = new List<string>();
    private List<string> subscribeQ = new List<string>();

    ConnectionFactory factory = null;
    IConnection connection = null;
    IModel channel = null;  
    Subscription sub = null;

    public void writeMessage( Measurement m1 ) {
        byte[] body = Measurement.AltSerialize( m1 );
        int msgCount = 1;
        Console.WriteLine("Sending message to queue {1} via the amq.direct exchange.", m1.id);

        string finalQueue = publishToQueue( m1.id );

        while (msgCount --> 0) {
            channel.BasicPublish("amq.direct", finalQueue, null, body);
        }

        Console.WriteLine("Done. Wrote the message to queue {0}.\n", m1.id);
    }

     public string publishToQueue(string firstQueueName) {
        Console.WriteLine("Creating a queue and binding it to amq.direct");
        string queueName = channel.QueueDeclare(firstQueueName, true, false, false, null);
        channel.QueueBind(queueName, "amq.direct", queueName, null);
        Console.WriteLine("Done.  Created queue {0} and bound it to amq.direct.\n", queueName);
        return queueName;
    }


    public Measurement readMessage() {
        Console.WriteLine("Receiving message...");
        Measurement m = new Measurement();

        int i = 0;
        foreach (BasicDeliverEventArgs ev in sub) {
            m = Measurement.AltDeSerialize(ev.Body);
            //m.id = //get the id here, from sub
            if (++i == 1)
                break;
            sub.Ack();
        }

        Console.WriteLine("Done.\n");
        return m;
    }


    public void subscribeToQueue(string queueName ) 
    {
        sub = new Subscription(channel, queueName);
    }

    public static string MsgSysName;
    public string MsgSys
    {
        get 
        { 
            return MsgSysName;
        }
        set
        {
            MsgSysName = value;
        }
    }

    public Rabbit(string _msgSys) //Constructor
    {   
        factory = new ConnectionFactory();
        factory.HostName = "localhost"; 
        connection = factory.CreateConnection();
        channel = connection.CreateModel();
        //consumer = new QueueingBasicConsumer(channel);

        System.Console.WriteLine("\nMsgSys: RabbitMQ");
        MsgSys = _msgSys;
    }

    ~Rabbit()
    {
        //observer??
        connection.Dispose();
        //channel.Dispose();
        System.Console.WriteLine("\nDestroying RABBIT");
    }   
}

//編輯2

private List<Subscription> subscriptions = new List<Subscription>();
    Subscription sub = null;

public Measurement readMessage()
    {
        Measurement m = new Measurement();
        foreach(Subscription element in subscriptions)
        {
            foreach (BasicDeliverEventArgs ev in element) {
                //ev = element.Next();
                if( ev != null) {
                    m = Measurement.AltDeSerialize( ev.Body );
                    return m;
                }
                m =  null;  
            }           
        }   
        System.Console.WriteLine("No message in the queue(s) at this time.");
        return m;
    }

    public void subscribeToQueue(string queueName) 
    {   
        sub = new Subscription(channel, queueName);
        subscriptions.Add(sub);     
    }

//編輯 3

//MessageHandler.cs

public class MessageHandler
{   
    // Implementation of methods for Rabbit class go here
    private List<string> publishQ = new List<string>();
    private List<string> subscribeQ = new List<string>();

    ConnectionFactory factory = null;
    IConnection connection = null;
    IModel channel = null;  
    QueueingBasicConsumer consumer = null;  

    private List<Subscription> subscriptions = new List<Subscription>();
    Subscription sub = null;

    public void writeMessage ( Measurement m1 )
    {
        byte[] body = Measurement.AltSerialize( m1 );
        //declare a queue if it doesn't exist
        publishToQueue(m1.id);

        channel.BasicPublish("amq.direct", m1.id, null, body);
        Console.WriteLine("\n  [x] Sent to queue {0}.", m1.id);
    }

    public void publishToQueue(string queueName)
    {   
        string finalQueueName = channel.QueueDeclare(queueName, true, false, false, null);
        channel.QueueBind(finalQueueName, "amq.direct", "", null);
    }

    public Measurement readMessage()
    {
        Measurement m = new Measurement();
        foreach(Subscription element in subscriptions)
        {
            if( element.QueueName == null)
            {
                m = null;
            }
            else 
            {
                BasicDeliverEventArgs ev = element.Next();
                if( ev != null) {
                    m = Measurement.AltDeSerialize( ev.Body );
                    m.id = element.QueueName;
                    element.Ack();
                    return m;
                }
                m =  null;                      
            }
            element.Ack();
        }   
        System.Console.WriteLine("No message in the queue(s) at this time.");
        return m;
    }

    public void subscribeToQueue(string queueName) 
    {   
        sub = new Subscription(channel, queueName);
        subscriptions.Add(sub); 
    }

    public static string MsgSysName;
    public string MsgSys
    {
        get 
        { 
            return MsgSysName;
        }
        set
        {
            MsgSysName = value;
        }
    }

    public MessageHandler(string _msgSys) //Constructor
    {   
        factory = new ConnectionFactory();
        factory.HostName = "localhost"; 
        connection = factory.CreateConnection();
        channel = connection.CreateModel();
        consumer = new QueueingBasicConsumer(channel);

        System.Console.WriteLine("\nMsgSys: RabbitMQ");
        MsgSys = _msgSys;
    }

    public void disposeAll()
    {
        connection.Dispose();
        channel.Dispose();
        foreach(Subscription element in subscriptions)
        {
            element.Close();
        }
        System.Console.WriteLine("\nDestroying RABBIT");
    }   
}

//App1.cs

using System;
using System.IO;

using UtilityMeasurement;
using UtilityMessageBus;


public class MainClass
{
    public static void Main()
    {

    MessageHandler obj1 = MessageHandler("Rabbit");

    System.Console.WriteLine("\nA {0} object is now created.", MsgSysName);

    //Create new Measurement messages
    Measurement m1 = new Measurement("q1", 2345, 23.456); 
    Measurement m2 = new Measurement("q2", 222, 33.33);

    System.Console.WriteLine("Test message 1:\n    ID: {0}", m1.id);
    System.Console.WriteLine("    Time: {0}", m1.time);
    System.Console.WriteLine("    Value: {0}", m1.value);

    System.Console.WriteLine("Test message 2:\n    ID: {0}", m2.id);
    System.Console.WriteLine("    Time: {0}", m2.time);
    System.Console.WriteLine("    Value: {0}", m2.value);   

    // Ask queue name and store it
    System.Console.WriteLine("\nName of queue to publish to: ");
    string queueName = (System.Console.ReadLine()).ToString();
    obj1.publishToQueue( queueName );

    // Write message to the queue
    obj1.writeMessage( m1 );    

    System.Console.WriteLine("\nName of queue to publish to: ");
    string queueName2 = (System.Console.ReadLine()).ToString();
    obj1.publishToQueue( queueName2 );

    obj1.writeMessage( m2 );

    obj1.disposeAll();
}
}

//App2.cs

using System;
using System.IO;

using UtilityMeasurement;
using UtilityMessageBus;

public class MainClass
{
    public static void Main()
    {
    //Asks for the message system
    System.Console.WriteLine("\nEnter name of messageing system: ");
    System.Console.WriteLine("Usage: [Rabbit] [Zmq]");
    string MsgSysName = (System.Console.ReadLine()).ToString();

    //Declare an IMessageBus instance:
    //Here, an object of the corresponding Message System
        // (ex. Rabbit, Zmq, etc) is instantiated
    IMessageBus obj1 = MessageBusFactory.GetMessageBus(MsgSysName);

    System.Console.WriteLine("\nA {0} object is now created.", MsgSysName);

    //Create a new Measurement object m
    Measurement m = new Measurement();  

    System.Console.WriteLine("Queue name to subscribe to: ");
    string QueueName1 = (System.Console.ReadLine()).ToString();
    obj1.subscribeToQueue( QueueName1 );

    //Read message into m
    m = obj1.readMessage();

    if (m != null ) {
        System.Console.WriteLine("\nMessage received from queue {0}:\n    ID: {1}", m.id, m.id);
        System.Console.WriteLine("    Time: {0}", m.time);
        System.Console.WriteLine("    Value: {0}", m.value);
    }

    System.Console.WriteLine("Another queue name to subscribe to: ");
    string QueueName2 = (System.Console.ReadLine()).ToString();
    obj1.subscribeToQueue( QueueName2 );

    m = obj1.readMessage();

    if (m != null ) {
        System.Console.WriteLine("\nMessage received from queue {0}:\n    ID: {1}", m.id, m.id);
        System.Console.WriteLine("    Time: {0}", m.time);
        System.Console.WriteLine("    Value: {0}", m.value);
    }

    obj1.disposeAll();
}
}

兩個信息來源:

  1. http://lists.rabbitmq.com/cgi-bin/mailman/listinfo/rabbitmq-discuss

  2. 您應該首先嘗試理解這些示例。

    • %Program Files%\RabbitMQ\DotNetClient\examples\src(基本示例)

    • 從他們的 Mercurial 存儲庫(c# 項目)中獲取完整的工作示例。

有用的操作來理解:

  • 聲明/斷言/監聽/訂閱/發布

回復:你的問題——你沒有理由不能有多個聽眾。 或者,您可以在“交換”上使用一個偵聽器訂閱 n 個路由路徑。

** 回復:非阻塞 **

典型的偵聽器一次使用一條消息。 您可以將它們從隊列中拉出,或者它們將自動以“窗口”方式放置在消費者附近(通過服務質量 qos 參數定義)。 該方法的美妙之處在於為您完成了許多艱苦的工作(例如:可靠性、保證交付等)。

RabbitMQ 的一個關鍵特性是,如果處理過程中出現錯誤,則消息會重新添加回隊列中(容錯特性)。

需要更多地了解您的情況。

通常,如果您發布到我上面提到的列表中,您可以在 RabbitMQ 找到工作人員。 他們很有幫助。

希望能有所幫助。 一開始你的頭腦很復雜,但值得堅持。


問答

見: http://www.rabbitmq.com/faq.html

Q. 可以使用 new Subscription(channel, queueName) 訂閱多個隊列嗎?

是的。 您要么使用綁定鍵,例如 abc.*.hij 或 abc.#.hij,要么附加多個綁定。 前者假設您已經圍繞某種對您有意義的原則設計了路由鍵(請參閱常見問題解答中的路由鍵)。 對於后者,您需要綁定到多個隊列。

手動實現 n 綁定。 見: http://hg.rabbitmq.com/rabbitmq-dotnet-client/file/default/projects/client/RabbitMQ.Client/src/client/messagepatterns/Subscription.cs

此模式背后的代碼不多,因此如果通配符不夠,您可以推出自己的訂閱模式。 你可以從這個 class 繼承並添加另一種方法來進行額外的綁定......可能這會起作用或接近這個(未經測試)。

AQMP 規范說可以進行多個手動綁定: http://www.rabbitmq.com/amqp-0-9-1-reference.html#queue.bind

問:如果是這樣,我怎樣才能通過所有訂閱的隊列 go 並返回一條消息(沒有消息時為空)?

通過訂閱者,您會在消息可用時收到通知。 否則,您所描述的是一個拉取接口,您可以在其中根據請求拉取消息。 如果沒有可用的消息,您將根據需要獲得 null。 順便說一句:Notify 方法可能更方便。

問:哦,請注意,我以不同的方法進行所有這些操作。 我將編輯我的帖子以反映代碼

實時代碼:

此版本必須使用通配符來訂閱多個路由密鑰

n 使用訂閱的手動路由密鑰留給讀者練習。 ;-) 我認為無論如何你都傾向於拉接口。 順便說一句:拉接口比通知接口效率低。

        using (Subscription sub = new Subscription(ch, QueueNme))
        {
            foreach (BasicDeliverEventArgs ev in sub)
            {
                Process(ev.Body);

        ...

注意: foreach使用 IEnumerable,而 IEnumerable 通過“yield”語句包裝了新消息到達的事件。 實際上它是一個無限循環。

- - 更新

AMQP 的設計理念是使 TCP 連接的數量與應用程序的數量一樣少,這意味着每個連接可以有多個通道。

這個問題(編輯 3)中的代碼嘗試使用兩個訂閱者和一個頻道,而它應該(我相信)每個線程每個頻道一個訂閱者以避免鎖定問題。 建議:使用路由鍵“通配符”。 可以使用 java 客戶端訂閱多個不同的隊列名稱,但據我所知,.net 客戶端沒有在訂閱者助手 ZA2F2ED4F8EBC2CBB4C21A29DC40AB6ZD 中實現此功能。

如果您確實需要在同一個訂閱線程上使用兩個不同的隊列名稱,那么建議為 .net 使用以下拉取序列:

        using (IModel ch = conn.CreateModel()) {    // btw: no reason to close the channel afterwards IMO
            conn.AutoClose = true;                  // no reason to closs the connection either.  Here for completeness.

            ch.QueueDeclare(queueName);
            BasicGetResult result = ch.BasicGet(queueName, false);
            if (result == null) {
                Console.WriteLine("No message available.");
            } else {
                ch.BasicAck(result.DeliveryTag, false);
                Console.WriteLine("Message:");
            }

            return 0;
        }

- 更新 2:

來自 RabbitMQ 列表:

“假設 element.Next() 在其中一個訂閱上阻塞。您可以從每個訂閱中檢索交付,並設置超時以讀取過去。或者,您可以設置一個隊列來接收所有測量值並從中檢索消息單次訂閱。” (埃米爾)

這意味着當第一個隊列為空時, .Next() 阻塞等待下一條消息出現。 即訂閱者有一個內置的等待下一個消息。

- 更新 3:

在 .net 下,使用 QueueingBasicConsumer 從多個隊列消費。

其實這里有一個關於它的線程來感受一下用法:

等待單個 RabbitMQ 消息超時

- 更新4:

有關 .QueueingBasicConsumer 的更多信息

這里有示例代碼。

http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.QueueingBasicConsumer.html

將示例復制到答案中並進行了一些修改(請參見 //<-----)。

                IModel channel = ...;
            QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
            channel.BasicConsume(queueName, false, null, consumer);  //<-----
            channel.BasicConsume(queueName2, false, null, consumer); //<-----
            // etc. channel.BasicConsume(queueNameN, false, null, consumer);  //<-----

            // At this point, messages will be being asynchronously delivered,
            // and will be queueing up in consumer.Queue.

            while (true) {
                try {
                    BasicDeliverEventArgs e = (BasicDeliverEventArgs) consumer.Queue.Dequeue();
                    // ... handle the delivery ...
                    channel.BasicAck(e.DeliveryTag, false);
                } catch (EndOfStreamException ex) {
                    // The consumer was cancelled, the model closed, or the
                    // connection went away.
                    break;
                }
            }

-- 更新 5:一個簡單的 get 將作用於任何隊列(一種較慢但有時更方便的方法)。

            ch.QueueDeclare(queueName);
            BasicGetResult result = ch.BasicGet(queueName, false);
            if (result == null) {
                Console.WriteLine("No message available.");
            } else {
                ch.BasicAck(result.DeliveryTag, false);
                Console.WriteLine("Message:"); 
                // deserialize body and display extra info here.
            }

最簡單的方法是使用 EventingBasicConsumer。 我的網站上有一個關於如何使用它的示例。 RabbitMQ EventingBasicConsumer

此使用者 class 公開了您可以使用的已接收事件,因此不會阻塞。 代碼的rest基本保持不變。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM