RabbitMQ(五):RabbitMQ 之簡單隊列

RabbitMQ 簡述

RabbitMQ是一個消息代理:它接受並轉發消息。 您可以將其視爲郵局:當您將要把寄發的郵件投遞到郵箱中時,您可以確信Postman 先生最終會將郵件發送給收件人。 在這個比喻中,RabbitMQ是一個郵箱,郵局和郵遞員,用來接受,存儲和轉發二進制數據塊的消息。

隊列就像是在RabbitMQ中扮演郵箱的角色。 雖然消息經過RabbitMQ和應用程序,但它們只能存儲在隊列中。 隊列只受主機的內存和磁盤限制的限制,它本質上是一個大的消息緩衝區。 許多生產者可以發送到一個隊列的消息,許多消費者可以嘗試從一個隊列接收數據。

producer即爲生產者,用來產生消息發送給隊列。consumer是消費者,需要去讀隊列內的消息。producer,consumer和broker(rabbitMQ server)不必駐留在同一個主機上;確實在大多數應用程序中它們是這樣分佈的。

簡單隊列

簡單隊列是最簡單的一種模式,由生產者、隊列、消費者組成。生產者將消息發送給隊列,消費者從隊列中讀取消息完成消費。

在下圖中,“P”是我們的生產者,“C”是我們的消費者。 中間的框是隊列 - RabbitMQ代表消費者的消息緩衝區。

img

java 方式

生產者


public class MyProducer {
  private static final String QUEUE_NAME = "ITEM_QUEUE";

  public static void main(String[] args) throws Exception {
    // 1. 創建一個 ConnectionFactory 並進行設置
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    factory.setVirtualHost("/");
    factory.setUsername("guest");
    factory.setPassword("guest");

    // 2. 通過連接工廠來創建連接
    Connection connection = factory.newConnection();

    // 3. 通過 Connection 來創建 Channel
    Channel channel = connection.createChannel();

    /* Message 消息 Properties
     * deliverMode 設置爲 2 的時候代表持久化消息
     * expiration 意思是設置消息的有效期,超過10秒沒有被消費者接收後會被自動刪除
     * headers 自定義的一些屬性
     */
    Map<String, Object> headers = new HashMap<>();
    headers.put("myHead1", "111");
    headers.put("myHead2", "222");

    AMQP.BasicProperties properties =
        new AMQP.BasicProperties()
            .builder()
            .deliveryMode(2)
            .contentEncoding("UTF-8")
            .expiration("100000")
            .headers(headers)
            .build();

    // 實際場景中,消息多爲json格式的對象 Message:消息的 Body
    String msg = "hello";
    // 4. 發送三條數據
    for (int i = 1; i <= 3; i++) {
      channel.basicPublish("", QUEUE_NAME, properties, msg.getBytes());
      System.out.println("Send message" + i + " : " + msg);
    }

    // 5. 關閉連接
    channel.close();
    connection.close();
  }
}

消費者

public class MyConsumer {

  private static final String QUEUE_NAME = "ITEM_QUEUE";

  public static void main(String[] args) throws Exception {
    // 1. 創建一個 ConnectionFactory 並進行設置
    ConnectionFactory factory = new ConnectionFactory();
    factory.setHost("localhost");
    factory.setVirtualHost("/");
    factory.setUsername("guest");
    factory.setPassword("guest");

    // 2. 通過連接工廠來創建連接
    Connection connection = factory.newConnection();

    // 3. 通過 Connection 來創建 Channel
    Channel channel = connection.createChannel();

    // 4. 聲明一個隊列
    /* durable 持久化
     * exclusive 排他隊列
     * 如果你想創建一個只有自己可見的隊列,即不允許其它用戶訪問,RabbitMQ允許你將一個Queue聲明成爲排他性的(Exclusive Queue)。
     * 該隊列的特點是:
     ** 只對首次聲明它的連接(Connection)可見
     ** 會在其連接斷開的時候自動刪除。
     ** 對於第一點,首先是強調首次聲明,因爲另外一個連接無法聲明一個同樣的排他性隊列;其次是隻區別連接(Connection)而不是通道(Channel),從同一個連接創建的不同的通道可以同時訪問某一個排他性的隊列。這裏說的連接是指一個AMQPConnection,以RabbitMQ的Java客戶端爲例:
     ** 如果試圖在一個不同的連接中重新聲明或訪問(如publish,consume)該排他性隊列,會得到資源被鎖定的錯誤:
     **   `ESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'UserLogin2'`
     ** 對於第二點,RabbitMQ會自動刪除這個隊列,而不管這個隊列是否被聲明成持久性的(Durable =true)。 也就是說即使客戶端程序將一個排他性的隊列聲明成了Durable的,只要調用了連接的Close方法或者客戶端程序退出了,RabbitMQ都會刪除這個隊列。注意這裏是連接斷開的時候,而不是通道斷開。這個其實前一點保持一致,只區別連接而非通道。
     *  */
    channel.queueDeclare(QUEUE_NAME, true, false, false, null);
    System.out.println(" [*] Waiting for messages. To exit press CTRL+C");

    /*
       true:表示自動確認,只要消息從隊列中獲取,無論消費者獲取到消息後是否成功消費,都會認爲消息已經成功消費
       false:表示手動確認,消費者獲取消息後,服務器會將該消息標記爲不可用狀態,等待消費者的反饋,如果消費者一
       直沒有反饋,那麼該消息將一直處於不可用狀態,並且服務器會認爲該消費者已經掛掉,不會再給其發送消息,
       直到該消費者反饋。
    */

    // 5. 創建消費者並接收消息
    Consumer consumer =
        new DefaultConsumer(channel) {
          @Override
          public void handleDelivery(
              String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
              throws IOException {
            String message = new String(body, "UTF-8");
            Map<String, Object> headers = properties.getHeaders();
            System.out.println("head: " + headers.get("myHead1"));
            System.out.println(" [x] Received '" + message + "'");
            System.out.println("expiration : " + properties.getExpiration());
          }
        };

    // 6. 設置 Channel 消費者綁定隊列
    channel.basicConsume(QUEUE_NAME, true, consumer);
  }
}

運行結果

## MyProducer
Send message1 : hello
Send message2 : hello
Send message3 : hello

## MyConsumer
 [*] Waiting for messages. To exit press CTRL+C
head: 111
 [x] Received 'hello'
expiration : 100000
head: 111
 [x] Received 'hello'
expiration : 100000
head: 111
 [x] Received 'hello'
expiration : 100000

當我們啓動生產者之後查看RabbitMQ管理後臺可以看到有一條消息正在等待被消費(需要先手動創建隊列或者啓動一下消費者)。
img
當我們啓動消費者之後再次查看,可以看到積壓的一條消息已經被消費。
img

總結

  • 聲明交換機
/**
     * Declare an exchange, via an interface that allows the complete set of
     * arguments.
     * @see com.rabbitmq.client.AMQP.Exchange.Declare
     * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk
     * @param exchange the name of the exchange
     * @param type the exchange type
     * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
     * @param autoDelete true if the server should delete the exchange when it is no longer in use
     * @param internal true if the exchange is internal, i.e. can't be directly
     * published to by a client.
     * @param arguments other properties (construction arguments) for the exchange
     * @return a declaration-confirm method to indicate the exchange was successfully declared
     * @throws java.io.IOException if an error is encountered
     */
    Exchange.DeclareOk exchangeDeclare(String exchange,
                                       String type,boolean durable,
                                       boolean autoDelete,boolean internal,
                                       Map<String, Object> arguments) throws IOException;
  • Name: 交換機名稱

  • Type: 交換機類型direct、topic、 fanout、 headers

  • Durability: 是否需要持久化,true爲持久化

  • Auto Delete: 當最後一個綁定到Exchange. 上的隊列刪除後,自動刪除該Exchange

  • Internal: 當前Exchange是否用於RabbitMQ內部使用,默認爲False

  • Arguments: 擴展參數,用於擴展AMQP協議自制定化使用

  • 隊列聲明

 /**
     * Declare a queue
     * @see com.rabbitmq.client.AMQP.Queue.Declare
     * @see com.rabbitmq.client.AMQP.Queue.DeclareOk
     * @param queue the name of the queue
     * @param durable true if we are declaring a durable queue (the queue will survive a server restart)
     * @param exclusive true if we are declaring an exclusive queue (restricted to this connection)
     * @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use)
     * @param arguments other properties (construction arguments) for the queue
     * @return a declaration-confirm method to indicate the queue was successfully declared
     * @throws java.io.IOException if an error is encountered
     */
    Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments) throws IOException;

方法 queueDeclare的參數:第一個參數表示隊列名稱、第二個參數爲是否持久化(true表示是,隊列將在服務器重啓時生存)、第三個參數爲是否是獨佔隊列(創建者可以使用的私有隊列,斷開後自動刪除)、第四個參數爲當所有消費者客戶端連接斷開時是否自動刪除隊列、第五個參數爲隊列的其他參數。

第三個參數(exclusive)

排他隊列
如果你想創建一個只有自己可見的隊列,即不允許其它用戶訪問,RabbitMQ允許你將一個Queue聲明成爲排他性的(Exclusive Queue)。

該隊列的特點是:
** 只對首次聲明它的連接(Connection)可見
** 會在其連接斷開的時候自動刪除。
** 對於第一點,首先是強調首次聲明,因爲另外一個連接無法聲明一個同樣的排他性隊列;其次是隻區別連接(Connection)而不是通道(Channel),從同一個連接創建的不同的通道可以同時訪問某一個排他性的隊列。這裏說的連接是指一個AMQPConnection,以RabbitMQ的Java客戶端爲例:
** 如果試圖在一個不同的連接中重新聲明或訪問(如publish,consume)該排他性隊列,會得到資源被鎖定的錯誤:
** ESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'UserLogin2'
** 對於第二點,RabbitMQ會自動刪除這個隊列,而不管這個隊列是否被聲明成持久性的(Durable =true)。 也就是說即使客戶端程序將一個排他性的隊列聲明成了Durable的,只要調用了連接的Close方法或者客戶端程序退出了,RabbitMQ都會刪除這個隊列。注意這裏是連接斷開的時候,而不是通道斷開。這個其實前一點保持一致,只區別連接而非通道。

  • basicConsume的第二個參數autoAck: 應答模式,true:自動應答,即消費者獲取到消息,該消息就會從隊列中刪除掉,false:手動應答,當從隊列中取出消息後,需要程序員手動調用方法應答,如果沒有應答,該消息還會再放進隊列中,就會出現該消息一直沒有被消費掉的現象。

  • 這種簡單隊列的模式,系統會爲每個隊列隱式地綁定一個默認交換機,交換機名稱爲" (AMQP default)",類型爲直連 direct,當你手動創建一個隊列時,系統會自動將這個隊列綁定到一個名稱爲空的 Direct 類型的交換機上,綁定的路由鍵 routing key 與隊列名稱相同,相當於channel.queueBind(queue:"QUEUE_NAME", exchange:"(AMQP default)“, routingKey:"QUEUE_NAME");雖然實例沒有顯式聲明交換機,但是當路由鍵和隊列名稱一樣時,就會將消息發送到這個默認的交換機中。這種方式比較簡單,但是無法滿足複雜的業務需求,所以通常在生產環境中很少使用這種方式。

  • The default exchange is implicitly bound to every queue, with a routing key equal to the queue name. It is not possible to explicitly bind to, or unbind from the default exchange. It also cannot be deleted.默認交換機隱式綁定到每個隊列,其中路由鍵等於隊列名稱。不可能顯式綁定到,或從缺省交換中解除綁定。它也不能被刪除。

    ——引自 RabbitMQ 官方文檔

  • 聲明交換機

/**
     * Declare an exchange, via an interface that allows the complete set of
     * arguments.
     * @see com.rabbitmq.client.AMQP.Exchange.Declare
     * @see com.rabbitmq.client.AMQP.Exchange.DeclareOk
     * @param exchange the name of the exchange
     * @param type the exchange type
     * @param durable true if we are declaring a durable exchange (the exchange will survive a server restart)
     * @param autoDelete true if the server should delete the exchange when it is no longer in use
     * @param internal true if the exchange is internal, i.e. can't be directly
     * published to by a client.
     * @param arguments other properties (construction arguments) for the exchange
     * @return a declaration-confirm method to indicate the exchange was successfully declared
     * @throws java.io.IOException if an error is encountered
     */
    Exchange.DeclareOk exchangeDeclare(String exchange,
                                       String type,boolean durable,
                                       boolean autoDelete,boolean internal,
                                       Map<String, Object> arguments) throws IOException;
  • Name: 交換機名稱

  • Type: 交換機類型direct、topic、 fanout、 headers

  • Durability: 是否需要持久化,true爲持久化

  • Auto Delete: 當最後一個綁定到Exchange. 上的隊列刪除後,自動刪除該Exchange

  • Internal: 當前Exchange是否用於RabbitMQ內部使用,默認爲False

  • Arguments: 擴展參數,用於擴展AMQP協議自制定化使用

    Spring Boot簡單整合

    參考《RabbitMQ(四):RabbitMQ與Spring Boot簡單整合 快速嚐鮮版》

    Spring Boot 版與原生的寫法相比不知道簡便的多少。但是原生寫法是基礎,我們也是需要了解的。

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