消費者收到的每一條消息都必須進行確認。消息確認後,RabbitMQ纔會從隊列刪除這條消息,RabbitMQ不會爲未確認的消息設置超時時間,它判斷此消息是否需要重新投遞給消費者的唯一依據是消費該消息的消費者連接是否已經斷開。這麼設計的原因是RabbitMQ允許消費者消費一條消息的時間可以很久很久。
RabbitMQ中消息的應答,共有兩種方式:
- 自動確認
- 手動確認
自動確認
消費者在聲明隊列時,可以指定autoAck參數,當autoAck=true
時,一旦消費者接收到了消息,就視爲自動確認了消息。
比如RabbitMQ消息的獲取方式中,介紹的消息推送Consume、消息拉取Get都是採用的自動確認方式,如下:
上述也注重說明了一旦消費者接收到了消息,就視爲自動確認了消息。這裏和ActiveMQ有些不同,所以需要進行區分,在ActiveMQ消息的可靠性中採用自動確認且是異步的情況下,其默認是在業務邏輯處理完成後進行自動確認,即在消息監聽器最後異步進行確認,如下:
但是在RabbitMQ中聲明消費者處理業務邏輯之前自動確認就已經完成了(推送Consume、消息拉取Ge都是如此),可由下面代碼進行測試,如下:
上述代碼我們採用了自動確認,並且在業務處理的最後手動拋出異常,然後我們進行測試,發現重啓消費者後不會再收到消息,說明該消息已經被消費者消費掉並確認了,RabbitMQ已經將其移除了。
手動確認
從上述的介紹,我們發現在自動確認中間,如果消費者在處理消息的過程中,出了錯,就沒有什麼辦法重新處理這條消息,所以我們很多時候,需要在消息處理成功後,再確認消息,這就需要手動確認。
當autoAck=false
時,RabbitMQ會等待消費者顯式發回ack信號後才從內存(和磁盤,如果是持久化消息的話)中移除消息。
採用消息確認機制後,只要令autoAck=false
,消費者就有足夠的時間處理消息(任務),不用擔心處理消息過程中消費者進程掛掉後消息丟失的問題,因爲RabbitMQ會一直持有消息直到消費者顯式調用channel.basicAck
爲止。
當autoAck=false
時,對於RabbitMQ服務器端而言,如果服務器端一直沒有收到消費者的ack信號,並且消費此消息的消費者已經斷開連接,則服務器端會安排該消息重新進入隊列,等待投遞給下一個消費者(也可能還是原來的那個消費者)。
這裏我們啓動了手動確認後,就必須調用channel.basicAck
方法進行確認,否則的話RabbitMQ會一直進行等待,當我們這個消費者關閉後,RabbitMQ會將該條消息再發給對應的消費者進行消費,直到有消費者對該條消息進行消費並應答完成。
看到上述對消息的處理後,我們還發現一個問題,如果channel.basicAck
收到確認前的代碼有問題,會拋出異常無法進行手動確認怎麼辦,一般消費者也不會連接中斷,那麼該消息就一直無法被處理,連被其他消費者處理的機會都沒有,所以一般我們會進行try-catch處理,處理成功則手動確認,失敗或有異常則拒絕。
最後我們再看看上述手動確認的basicAck方法中的兩個參數,如下:
該參數我們在RabbitMQ消息發佈之發送方確認的異步監聽發送方確認模式中也介紹過,第一個參數deliveryTag
爲消息的ID,第二個參數multiple
則爲是否批量確認,這裏我們都是逐條消息讀取並確認的,所以都爲false。
如果是批量確認的話,這裏第一個參數deliveryTag
爲最後一條消息的ID,第二個參數爲true,其批量確認代碼如下:
public class BatchAckConsumer extends DefaultConsumer {
private int messasgeCount = 0;
public BatchAckConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String content = new String(body, "UTF-8");
String message = "Exchange: " + envelope.getExchange() + ", " +
"RoutingKey: " + envelope.getRoutingKey() + ", " +
"Content: " + content;
System.out.println(message);
messasgeCount++;
if (messasgeCount % 100 == 0) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
}
if (content.equals("end")) {
this.getChannel().basicAck(envelope.getDeliveryTag(), true);
}
}
上述採用了100條消息進批量確認,只有上述代碼的最後判斷的if (message.equals("end"))
,是因爲消息的條數很少是我們批量確認數量的整數倍,所以這裏要求消息的生產者在所有消息發送完成後,需要額外的發送一條消息內容爲end的消息,進行告訴消費者所有消息已發送完成,進行最後剩餘消息的批量確認。
上述BatchAckConsumer
是繼承了DefaultConsumer
進行實現的,在消費者中使用則很簡單了嘛,如下:
注意: 如果採用了上述的批量確認的方式,如果消費者應用程序在確認消息之前崩潰,則所有未確認的消息(包括已成功處理,未被確認)將被重新發送給其他消費者。所以這裏存在着一定程度上的可靠性風險。