RabbitMQ 如何對消費端限流?

點擊關注公衆號,Java乾貨及時送達

作者:海向
出處:www.cnblogs.com/haixiang/p/10905189.html

1. 爲什麼要對消費端限流

假設一個場景,首先,我們 RabbitMQ 服務器積壓了有上萬條未處理的消息,我們隨便打開一個消費者客戶端,會出現這樣情況: 巨量的消息瞬間全部推送過來,但是我們單個客戶端無法同時處理這麼多數據!

當數據量特別大的時候,我們對生產端限流肯定是不科學的,因爲有時候併發量就是特別大,有時候併發量又特別少,我們無法約束生產端,這是用戶的行爲。

所以我們應該對消費端限流,用於保持消費端的穩定,當消息數量激增的時候很有可能造成資源耗盡,以及影響服務的性能,導致系統的卡頓甚至直接崩潰。

2.限流的 API 講解

RabbitMQ 提供了一種 qos (服務質量保證)功能,即在非自動確認消息的前提下,如果一定數目的消息(通過基於 consume 或者 channel 設置 Qos 的值)未被確認前,不進行消費新的消息。

/**
* Request specific "quality of service" settings.
* These settings impose limits on the amount of data the server
* will deliver to consumers before requiring acknowledgements.
* Thus they provide a means of consumer-initiated flow control.
@param prefetchSize maximum amount of content (measured in
* octets) that the server will deliver, 0 if unlimited
@param prefetchCount maximum number of messages that the server
* will deliver, 0 if unlimited
@param global true if the settings should be applied to the
* entire channel rather than each consumer
@throws java.io.IOException if an error is encountered
*/

void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException;
  • prefetchSize:0,單條消息大小限制,0代表不限制
  • prefetchCount:一次性消費的消息數量。會告訴 RabbitMQ 不要同時給一個消費者推送多於 N 個消息,即一旦有 N 個消息還沒有 ack,則該 consumer 將 block 掉,直到有消息 ack。
  • global:true、false 是否將上面設置應用於 channel,簡單點說,就是上面限制是 channel 級別的還是 consumer 級別。當我們設置爲 false 的時候生效,設置爲 true 的時候沒有了限流功能,因爲 channel 級別尚未實現。
  • 注意:prefetchSize 和 global 這兩項,rabbitmq 沒有實現,暫且不研究。特別注意一點,prefetchCount 在 no_ask=false 的情況下才生效,即在自動應答的情況下這兩個值是不生效的。

3.如何對消費端進行限流

  • 首先第一步,我們既然要使用消費端限流,我們需要關閉自動 ack,將 autoAck 設置爲 false channel.basicConsume(queueName, false, consumer);
  • 第二步我們來設置具體的限流大小以及數量。 channel.basicQos(0, 15, false);
  • 第三步在消費者的 handleDelivery 消費方法中手動 ack,並且設置批量處理 ack 迴應爲 true channel.basicAck(envelope.getDeliveryTag(), true);

這是生產端代碼,與前幾章的生產端代碼沒有做任何改變,主要的操作集中在消費端。RabbitMQ 系列面試題我都整理好了,關注公衆號Java技術棧,回覆:面試,免費獲取哦。

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class QosProducer {
    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. 聲明
        String exchangeName = "test_qos_exchange";
        String routingKey = "item.add";

        //5. 發送
        String msg = "this is qos msg";
        for (int i = 0; i < 10; i++) {
            String tem = msg + " : " + i;
            channel.basicPublish(exchangeName, routingKey, null, tem.getBytes());
            System.out.println("Send message : " + tem);
        }

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

這裏我們創建一個消費者,通過以下代碼來驗證限流效果以及 global 參數設置爲 true 時不起作用。我們通過Thread.sleep(5000); 來讓 ack 即處理消息的過程慢一些,這樣我們就可以從後臺管理工具中清晰觀察到限流情況。

import com.rabbitmq.client.*;
import java.io.IOException;
public class QosConsumer {
    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");
        factory.setAutomaticRecoveryEnabled(true);
        factory.setNetworkRecoveryInterval(3000);

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

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

        //4. 聲明
        String exchangeName = "test_qos_exchange";
        String queueName = "test_qos_queue";
        String routingKey = "item.#";
        channel.exchangeDeclare(exchangeName, "topic"truefalsenull);
        channel.queueDeclare(queueName, truefalsefalsenull);

        channel.basicQos(03false);

        //一般不用代碼綁定,在管理界面手動綁定
        channel.queueBind(queueName, exchangeName, routingKey);

        //5. 創建消費者並接收消息
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body)

                    throws IOException 
{
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                String message = new String(body, "UTF-8");
                System.out.println("[x] Received '" + message + "'");

                channel.basicAck(envelope.getDeliveryTag(), true);
            }
        };
        //6. 設置 Channel 消費者綁定隊列
        channel.basicConsume(queueName, false, consumer);
        channel.basicConsume(queueName, false, consumer1);
    }
}

我們從下圖中發現 Unacked值一直都是 3 ,每過 5 秒 消費一條消息即 Ready 和 Total 都減少 3,而 Unacked的值在這裏代表消費者正在處理的消息,通過我們的實驗發現了消費者一次性最多處理 3 條消息,達到了消費者限流的預期功能。

當我們將void basicQos(int prefetchSize, int prefetchCount, boolean global)中的 global 設置爲 true的時候我們發現並沒有了限流的作用。






關注Java技術棧看更多幹貨



獲取 Spring Boot 實戰筆記!

本文分享自微信公衆號 - Java技術棧(javastack)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

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