基本消息模型-rabbitMq消息模型(一)

1. 搭建demo工程

首先創建一個工程rabbitmq_demo(開發工具idea),之後的消息模型demo都基於此工程
在這裏插入圖片描述在這裏插入圖片描述
用的springboot版本比較高,2.2.2的,根據需要選擇吧。
在這裏插入圖片描述創建完成後,會開始下載所需要的依賴,網速慢的話得等一小會。

pom文件如下(自動生成的,無需改動):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>rabbitmq_demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>rabbitmq_demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

spring的配置文件的話暫時用不到,這裏rabbitmq的連接信息配置在了一個工具類裏:

public class ConnectionUtil {
    /**
     * 建立與RabbitMQ的連接
     * @return
     * @throws Exception
     */
    public static Connection getConnection() throws Exception {
        //定義連接工廠
        ConnectionFactory factory = new ConnectionFactory();
        //設置服務地址
        factory.setHost("192.168.33.128");
        //端口
        factory.setPort(5672);
        //設置賬號信息,用戶名、密碼、vhost
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        factory.setConnectionTimeout(1000000);
        factory.setHandshakeTimeout(1000000);
        // 通過工程獲取連接
        Connection connection = factory.newConnection();
        return connection;
    }
}

注意端口默認時5672,在瀏覽其裏訪問默認時15672

2. 基本消息模型概述

官網介紹如下:
在這裏插入圖片描述

RabbitMQ message broker:它接受和轉發消息。 你可以考慮一下郵局:當你把你想要發佈的郵件信箱, 可以肯定的是,先生或女士Mailperson最終會交付收件人的郵件。 在這個比喻中,RabbitMQ信箱,一個郵局和一個郵差。

在這裏插入圖片描述

P(producer/ publisher):生產者,一個發送消息的用戶應用程序。

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

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

總之:

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

3. 代碼實現

3.1 生產者

/**
 * 生產者
 */
public class Send {

    private final static String QUEUE_NAME = "simple_queue";

    public static void main(String[] argv) throws Exception {
        // 1. 獲取到連接
        Connection connection = ConnectionUtil.getConnection();
        // 2. 從連接中創建通道,使用通道才能完成消息相關的操作
        Channel channel = connection.createChannel();
        // 3. 聲明(創建)隊列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 消息內容
        String message = "Hello 大家好啦啦啦!";
        // 向指定的隊列中發送消息  (需要轉換爲字節)
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
       
        System.out.println(" [x] Sent '" + message + "'");
        //關閉通道和連接
        channel.close();
        connection.close();
    }
}

執行這段代碼後,瀏覽器登錄服務端管理界面如下:

http://192.168.33.128:15672/#/queues -網址ip+端口 (默認15672)

生成了通道和隊列

在這裏插入圖片描述在這裏插入圖片描述(看到隊列裏邊已經有了一條消息)

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

3.2 消費者

代碼如下:

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);
    }
}

運行如下:
在這裏插入圖片描述
消費者已經獲取了消息,但是程序沒有停止,一直在監聽隊列中是否有新的消息。一旦有新的消息進入隊列,就會立即打印.

在這裏插入圖片描述

3.3 消息確認機制(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。方法的聲明:

在這裏插入圖片描述

3.4 自動ACK存在的問題

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

在這裏插入圖片描述

3.5 演示手動ACK

修改消費者,把自動改成手動(去掉之前製造的異常)
在這裏插入圖片描述生產者不變,再次運行:
在這裏插入圖片描述

運行消費者
在這裏插入圖片描述但是,查看管理界面,發現:

在這裏插入圖片描述
停掉消費者的程序,發現:
在這裏插入圖片描述
這是因爲雖然我們設置了手動ACK,但是代碼中並沒有進行消息確認!所以消息並未被真正消費掉。

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

修改代碼手動ACK:
在這裏插入圖片描述
執行:
在這裏插入圖片描述

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