基本消息模型

基本消息模型

官方介紹:
在這裏插入圖片描述
RabbitMQ是一個消息代理:它接受和轉發消息。 你可以把它想象成一個郵局:當你把郵件放在郵箱裏時,你可以確定郵差先生最終會把郵件發送給你的收件人。 在這個比喻中,RabbitMQ是郵政信箱,郵局和郵遞員。

RabbitMQ與郵局的主要區別是它不處理紙張,而是接受,存儲和轉發數據消息的二進制數據塊。
在這裏插入圖片描述
P(producer/ publisher):生產者,一個發送消息的用戶應用程序。

C(consumer):消費者,消費和接收有類似的意思,消費者是一個主要用來等待接收消息的用戶應用程序

隊列(紅色區域):rabbitmq內部類似於郵箱的一個概念。雖然消息流經rabbitmq和你的應用程序,但是它們只能存儲在隊列中。隊列只受主機的內存和磁盤限制,實質上是一個大的消息緩衝區。許多生產者可以發送消息到一個隊列,許多消費者可以嘗試從一個隊列接收數據。

總之:

生產者將消息發送到隊列,消費者從隊列中獲取消息,隊列是存儲消息的緩衝區。

我們將用Java編寫兩個程序;發送單個消息的生產者,以及接收消息並將其打印出來的消費者。我們將詳細介紹Java API中的一些細節,這是一個消息傳遞的“Hello World”。

我們將調用我們的消息發佈者(發送者)Send和我們的消息消費者(接收者)Recv。發佈者將連接到RabbitMQ,發送一條消息,然後退出。

1.1.1.生產者發送消息

public class Send {

    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接以及mq通道
        Connection connection = ConnectionUtil.getConnection();
        // 從連接中創建通道,這是完成大部分API的地方。
        Channel channel = connection.createChannel();

        // 聲明(創建)隊列,必須聲明隊列才能夠發送消息,我們可以把消息發送到隊列中。
        // 聲明一個隊列是冪等的 - 只有當它不存在時纔會被創建
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        // 消息內容
        String message = "Hello World!";
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println(" [x] Sent '" + message + "'");

        //關閉通道和連接
        channel.close();
        connection.close();
    }
}

控制檯:
在這裏插入圖片描述

1.1.2.管理工具中查看消息

進入隊列頁面,可以看到新建了一個隊列:simple_queue
在這裏插入圖片描述
點擊隊列名稱,進入詳情頁,可以查看消息:
在這裏插入圖片描述
在控制檯查看消息並不會將消息消費,所以消息還在。

1.1.3.消費者獲取消息

public class Recv {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 創建通道
        Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [x] received : " + msg + "!");
            }
        };
        // 監聽隊列,第二個參數:是否自動進行消息確認。
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}

控制檯:
在這裏插入圖片描述
這個時候,隊列中的消息就沒了:
在這裏插入圖片描述
我們發現,消費者已經獲取了消息,但是程序沒有停止,一直在監聽隊列中是否有新的消息。一旦有新的消息進入隊列,就會立即打印.

1.1.4.消息確認機制(ACK)

通過剛纔的案例可以看出,消息一旦被消費者接收,隊列中的消息就會被刪除。

那麼問題來了:RabbitMQ怎麼知道消息被接收了呢?

如果消費者領取消息後,還沒執行操作就掛掉了呢?或者拋出了異常?消息消費失敗,但是RabbitMQ無從得知,這樣消息就丟失了!

因此,RabbitMQ有一個ACK機制。當消費者獲取消息後,會向RabbitMQ發送回執ACK,告知消息已經被接收。不過這種回執ACK分兩種情況:

  • 自動ACK:消息一旦被接收,消費者自動發送ACK
  • 手動ACK:消息接收後,不會發送ACK,需要手動調用

大家覺得哪種更好呢?

這需要看消息的重要性:

  • 如果消息不太重要,丟失也沒有影響,那麼自動ACK會比較方便
  • 如果消息非常重要,不容丟失。那麼最好在消費完成後手動ACK,否則接收消息後就自動ACK,RabbitMQ就會把消息從隊列中刪除。如果此時消費者宕機,那麼消息就丟失了。

我們之前的測試都是自動ACK的,如果要手動ACK,需要改動我們的代碼:

public class Recv2 {
    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 創建通道
        final Channel channel = connection.createChannel();
        // 聲明隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 定義隊列的消費者
        DefaultConsumer consumer = new DefaultConsumer(channel) {
            // 獲取消息,並且處理,這個方法類似事件監聽,如果有消息的時候,會被自動調用
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties,
                    byte[] body) throws IOException {
                // body 即消息體
                String msg = new String(body);
                System.out.println(" [x] received : " + msg + "!");
                // 手動進行ACK
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        // 監聽隊列,第二個參數false,手動進行ACK
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

注意到最後一行代碼:

channel.basicConsume(QUEUE_NAME, false, consumer);

如果第二個參數爲true,則會自動進行ACK;如果爲false,則需要手動ACK。方法的聲明:
在這裏插入圖片描述

1.1.4.1.自動ACK存在的問題

修改消費者,添加異常,如下:
在這裏插入圖片描述
生產者不做任何修改,直接運行,消息發送成功:
在這裏插入圖片描述
運行消費者,程序拋出異常。但是消息依然被消費:
在這裏插入圖片描述
管理界面:
在這裏插入圖片描述

1.1.4.2.演示手動ACK

修改消費者,把自動改成手動(去掉之前製造的異常)
在這裏插入圖片描述
生產者不變,再次運行:
在這裏插入圖片描述
運行消費者
在這裏插入圖片描述
但是,查看管理界面,發現:
在這裏插入圖片描述
停掉消費者的程序,發現:
在這裏插入圖片描述
這是因爲雖然我們設置了手動ACK,但是代碼中並沒有進行消息確認!所以消息並未被真正消費掉。

當我們關掉這個消費者,消息的狀態再次稱爲Ready

修改代碼手動ACK:
在這裏插入圖片描述
執行:
在這裏插入圖片描述
消息消費成功!

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