點擊關注公衆號,Java乾貨及時送達
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", true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.basicQos(0, 3, false);
//一般不用代碼綁定,在管理界面手動綁定
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技術棧看更多幹貨
本文分享自微信公衆號 - Java技術棧(javastack)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。