本文翻譯自RabbitMQ官網。
一、工作隊列介紹
上一部分通過簡單的代碼介紹了通過隊列發送和接收消息,這一部分創建一個工作隊列來發送和接收耗時的消息。工作隊列的主要設計思想是將資源密集型的任務先放進隊列中,避免立刻做資源密集型的任務,也就避免了一直等待該任務處理完成造成的堵塞。在短暫的HTTP請求中不可能一直等待複雜的任務處理完成後返回,而是先將消息放入消息隊列就可以返回了,消息的處理待消費者從消息隊列中取出後再處理,達到了異步的效果。
在本節中模擬一個複雜的任務,如下所示。
private static void doWork(String msg){
char[] array = msg.toCharArray();
for (char ch : array){
if (ch == '.') {
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
當消息中有字符‘.’時,sleep 1ms。
將消費隊列改爲:task_queue3。
結果如下:
producer:jing... quan
before send message:jing... quan
after send message:jing... quan
******time is :******2ms
start consume....
consumer:jing... quan
二、相關屬性設置
1、消息確認(message acknowledgment):爲了防止在消費端掛掉之後消息丟失
在消費端接收到消息之後,開始處理複雜的任務,如果當某一個消費者在執行任務的過程中突然掛掉了(原因可能是channel被關閉了、連接被關閉了或者是TCP的連接被斷掉了)怎麼辦?消息會不會丟失呢?一旦RabbitMQ發送消息給消費者後,該消息就立刻被從內存中刪除掉了,這種情況下,如果某一個消費者掛掉了,那麼它正在處理的所有消息就會丟失掉。但是在實際情況中,我們是不希望丟失任何消息的,所以需要使用到RabbitMQ的消息確認屬性。消息確認即當消費者已經對某一個特定的消息處理完畢之後它會發給RabbitMQ一個ACK(nowledgment),告知RabbitMQ可以將這段消息刪除掉了。如果消費端是在處理消息的過程中掛掉的,那麼肯定不會發送給RabbitMQ一個ACK,這個時候RabbitMQ會將消息重新發送給其他未掛掉的消費者。
在默認情況下,ACK是打開的,可以通過basicConsume來進行設置:
channel.basicConsume(queue, false, this);
以上情況會將ACK關掉,但是爲了防止丟失,還是將其打開:
channel.basicConsume(queue, true, this);
2、 消息的持久性:爲了防止在服務器掛掉之後消息丟失
當RabbitMQ掛掉需要重啓時,在默認情況下,它會將其之前創建的channel和message全部刪除,這個時候就會造成數據丟失。爲了防止這種情況發生,需要做兩個方面的設置:a,消息隊列的持久性設置;b,消息的持久性設置。
首先是消息隊列的持久性設置:
durable = true;
channel.queueDeclare(queue, durable, exclusive, autoDelete, arguments);
以上代碼在生產者端和消費者端需同時設置。
然後是消息的持久性設置:
channel.basicPublish("", queue, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes());
在發佈消息的時候,將消息的屬性設置爲MessageProperties.PERSISTENT_TEXT_PLAIN
以上設置就保證了消息的持久性,當Rabbit服務器因爲某些原因重啓後,還是可以消費到尚未被消費的消息。
3、合理分發
有這樣的情景:兩個消費者,一個耗費資源比較小的消息和一個耗費資源比較大的消息,在默認情況下,RabbitMQ會平均將消息分發給兩個消費者,這樣就容易造成一個消費者一直很忙而另一個消費者比較空閒的情況,這是因爲當消息被放入消息隊列後RabbitMQ只負責分發,而不關注消費的處理情況,如果進行以下設置:
int prefetchCount = 1;
channel.basicQos(prefetchCount);
RabbitMQ就會在某一個消費者處理完成前一個消息之後纔會給其分發下一個消息,如果該消費者還沒有處理完畢,它就會將待處理消息分發給其他消費者。