RabbitMQ消息應答模式、事務、公平轉發

1.消息應答模式(手動、自動)

1.1應答模式

        爲了確保消息不會丟失,RabbitMQ支持消息應答。消費者發送一個消息應答,告訴RabbitMQ這個消息已經接收並且處理完畢了。RabbitMQ就可以刪除它了。

        如果一個消費者掛掉卻沒有發送應答,RabbitMQ會理解爲這個消息沒有處理完全,然後交給另一個消費者去重新處理。這樣,你就可以確認即使消費者偶爾掛掉也不會丟失任何消息了。 沒有任何消息超時限制;只有當消費者掛掉時,RabbitMQ纔會重新投遞。即使處理一條消息會花費很長的時間。

        消息應答是默認打開的。我們通過顯示的設置autoAsk=true關閉這種機制。現即自動應答開,一旦我們完成任務,消費者會自動發送應答。通知RabbitMQ消息已被處理,可以從內存刪除。如果消費者因宕機或鏈接失敗等原因沒有發送ACK(不同於ActiveMQ,在RabbitMQ裏,消息沒有過期的概念),則RabbitMQ會將消息重新發送給其他監聽在隊列的下一個消費者


.1.2自動應答
// 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
 channel.basicConsume(QUEUE_EMAIL, true, defaultConsumer);

生產者

public class Producer {

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException {
        //1.創建連接
        Connection connection = ConnectionUtils.newConnection();
        // 2.創建通道
        Channel channel = connection.createChannel();
        //3.綁定的交換機 參數1交互機名稱 參數2 exchange類型
        channel.exchangeDeclare(EXCHANGE, "fanout");
        String msg = "測試生產消息" + new Date();
        try {
//            channel.txSelect();
            // 4.生產消息
            channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
//            int i = 1 / 0;
            System.out.println("生產者生產消息:" + msg);
//            channel.txCommit();
        } catch (IOException e) {
            e.printStackTrace();
//            channel.txRollback();
        } finally {
            channel.close();
            connection.close();
        }
    }
}

短信消費者

public class SmsConsumer {
    private static String QUEUE_SMS = "sms";

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("短信消費者");
        //1.創建連接
        Connection connection = ConnectionUtils.newConnection();
        // 2.創建通道
        final Channel channel = connection.createChannel();
        // 3.聲明隊列
        /**
         * String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
         * queue:這沒什麼好說的,隊列名
         *
         * durable:是否持久化,那麼問題來了,這是什麼意思?持久化,指的是隊列持久化到數據庫中。在之前的博文中也說過,如果RabbitMQ服務掛了怎麼辦,隊列丟失了自然是不希望發生的。持久化設置爲true的話,即使服務崩潰也不會丟失隊列
         * exclusive:是否排外,what? 這又是什麼呢。設置了排外爲true的隊列只可以在本次的連接中被訪問,也就是說在當前連接創建多少個channel訪問都沒有關係,但是如果是一個新的連接來訪問,對不起,不可以,下面是我嘗試訪問了一個排外的queue報的錯。還有一個需要說一下的是,排外的queue在當前連接被斷開的時候會自動消失(清除)無論是否設置了持久化
         * autoDelete:這個就很簡單了,是否自動刪除。也就是說queue會清理自己。但是是在最後一個connection斷開的時候
         * 設置隊列的其他一些參數
         */
        channel.queueDeclare(QUEUE_SMS, false, false, false, null);
        /**
         * String queue, String exchange, String routingKey
         */
        channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msgString = new String(body, "UTF-8");
                System.out.println("短信消費者開始獲取消息.........." );
                int i = 1 / 0;
                // 手動應答消息
                channel.basicAck(envelope.getDeliveryTag(), false);
                System.out.println("短信消費者結束獲取消息:" + msgString);

            }
        };
        // 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
        channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
    }
}

郵件消費者

public class SmsConsumer {
    private static String QUEUE_SMS = "sms";

    private static String EXCHANGE = "sms_email";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        System.out.println("短信消費者");
        //1.創建連接
        Connection connection = ConnectionUtils.newConnection();
        // 2.創建通道
        final Channel channel = connection.createChannel();
        // 3.聲明隊列
        /**
         * String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments
         * queue:這沒什麼好說的,隊列名
         *
         * durable:是否持久化,那麼問題來了,這是什麼意思?持久化,指的是隊列持久化到數據庫中。在之前的博文中也說過,如果RabbitMQ服務掛了怎麼辦,隊列丟失了自然是不希望發生的。持久化設置爲true的話,即使服務崩潰也不會丟失隊列
         * exclusive:是否排外,what? 這又是什麼呢。設置了排外爲true的隊列只可以在本次的連接中被訪問,也就是說在當前連接創建多少個channel訪問都沒有關係,但是如果是一個新的連接來訪問,對不起,不可以,下面是我嘗試訪問了一個排外的queue報的錯。還有一個需要說一下的是,排外的queue在當前連接被斷開的時候會自動消失(清除)無論是否設置了持久化
         * autoDelete:這個就很簡單了,是否自動刪除。也就是說queue會清理自己。但是是在最後一個connection斷開的時候
         * 設置隊列的其他一些參數
         */
        channel.queueDeclare(QUEUE_SMS, false, false, false, null);
        /**
         * String queue, String exchange, String routingKey
         */
        channel.queueBind(QUEUE_SMS, EXCHANGE, "message");
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msgString = new String(body, "UTF-8");
                System.out.println("短信消費者開始獲取消息.........." );
                int i = 1 / 0;
                // 手動應答消息
                channel.basicAck(envelope.getDeliveryTag(), false);
                System.out.println("短信消費者結束獲取消息:" + msgString);

            }
        };
        // 4.設置應答模式,true的時候爲自動應答,false爲手動應答,需要處理
        channel.basicConsume(QUEUE_SMS, false, defaultConsumer);
    }
}

異常狀態
int i = 1 / 0,我們在郵件和短信消費者代碼裏面加入,在手動或者自動應答之前拋出異常,就會導致消費都沒被消費,而如果在應答之後拋出異常,只能用事務來處理,使用的消息隊列是sms和email.
在這裏插入圖片描述
看到消息都是出於等待被消費的狀態
正常
註釋掉int i = 1/0,
手動應答(隊列sms):通過debug可以看到,執行完1,消費還沒被確認
在這裏插入圖片描述
直到2執行完成後,才被確認
在這裏插入圖片描述
在這裏插入圖片描述
自動應答(隊列email):執行完成後就被確認了
在這裏插入圖片描述

1.消息事務

事務的實現主要是對信道(Channel)的設置,主要的方法有三個:

channel.txSelect()聲明啓動事務模式;
channel.txComment()提交事務;
channel.txRollback()回滾事務;

生產者回滾:消費隊列中看不到任何消息

   channel.txSelect();
        // 4.生產消息
        channel.basicPublish(EXCHANGE, "", null, msg.getBytes());
        int i = 1 / 0;
        channel.txCommit();
        System.out.println("生產者生產消息:" + msg);
    } catch (IOException e) {
        e.printStackTrace();
        channel.txRollback();
        }

消費者回滾:消息沒有被消費,進行了回滾

try {
        channel.txSelect();
        System.out.println("短信消費者開始獲取消息.........." );
        // 手動應答消息
        channel.basicAck(envelope.getDeliveryTag(), false);
        int i = 1 / 0;
        channel.txCommit();
        System.out.println("短信消費者結束獲取消息:" + msgString);
    } catch (IOException e) {
        channel.txRollback();
    }

在這裏插入圖片描述

3.公平轉發

目前消息轉發機制是平均分配,這樣就會出現倆個消費者,奇數的任務很耗時,偶數的任何工作量很小,造成的原因就是近當消息到達隊列進行轉發消息。並不在乎有多少任務消費者並未傳遞一個應答給RabbitMQ。僅僅盲目轉發所有的奇數給一個消費者,偶數給另一個消費者。 爲了解決這樣的問題,我們可以使用basicQos方法,傳遞參數爲prefetchCount= 1。這樣告訴RabbitMQ不要在同一時間給一個消費者超過一條消息。 換句話說,只有在消費者空閒的時候會發送下一條信息。調度分發消息的方式,也就是告訴RabbitMQ每次只給消費者處理一條消息,也就是等待消費者處理完畢並自己對剛剛處理的消息進行確認之後,才發送下一條消息,防止消費者太過於忙碌,也防止它太過去清閒。

**通過 設置channel.basicQos(1);** 我們生產了10條消息, 01消費者,休眠時間比較短,消費了9條消息

在這裏插入圖片描述

消費者02,休眠時間較長,最後只消費了1條信息
在這裏插入圖片描述

總結:也就是通過參數設置,我們可以選擇讓空閒的消費者多去消費消息,而忙碌的消費者,則等空閒再去消費消息

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