概述
在實際項目中如果業務代碼出現BUG,消費端進行消費的時候,我們可以記錄錯誤日誌,然後在消費端進行消息補償。如果出現服務器宕機問題,那就需要手工ACK,然後在生產端進行消息補償。生產端消息補償可以使用RabbitMQ的confirm機制。
ACK機制與消費端消息補償機制
-
把
channel.basicConsume(...)
方法的autoAck
參數改爲false
-
channel.basicAck(long deliveryTag, boolean multiple);
方法,消費成功簽收
參數說明:
deliveryTag:消息標識
multiple:是否批量簽收 -
basicNack(long deliveryTag, boolean multiple, boolean requeue)
,消息消費失敗
參數說明:
deliveryTag:消息標識
multiple:是否批量簽收
requeue:true 消息會重回隊列,false 消息會進入到死信隊列
代碼演示
- 生產端
public static void main(String[] args) throws Exception{
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設置虛擬主機
connectionFactory.setVirtualHost("/");
//創建一個鏈接
Connection connection = connectionFactory.newConnection();
//創建channel
Channel channel = connection.createChannel();
String exchangeName="test_ack_exchange";
String routeKey="ack.test";
for (int i=0;i<5;i++){
Map<String, Object> headers = new HashMap<String, Object>();
//演示重回隊列機制,使用num==0的消息簽收失敗重回隊列
headers.put("num", i);
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder()
.deliveryMode(2)
.contentEncoding("UTF-8")
.headers(headers)
.build();
String msg="RabbitMQ send message ack test!"+i;
channel.basicPublish(exchangeName,routeKey,properties,msg.getBytes());
}
}
- 消息端
public static void main(String[] args) throws Exception{
System.out.println("======消息接收start==========");
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//設置虛擬主機
connectionFactory.setVirtualHost("/");
//創建鏈接
Connection connection = connectionFactory.newConnection();
//創建channel
Channel channel = connection.createChannel();
String exchangeName="test_ack_exchange";
String exchangeType="topic";
//聲明Exchange
channel.exchangeDeclare(exchangeName,exchangeType,true,false,false,null);
String queueName="test_ack_queue";
//聲明隊列
channel.queueDeclare(queueName,true,false,false,null);
String routeKey="ack.#";
//綁定隊列和交換機
channel.queueBind(queueName,exchangeName,routeKey);
/**
* autoAck:false 設置爲手工簽收
*/
channel.basicConsume(queueName, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息::"+new String(body));
try {
Thread.sleep(3000); //休眠5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//演示重回隊列機制,使用num==0的消息簽收失敗重回隊列
if((Integer)properties.getHeaders().get("num") == 0) {
/**
* 參數說明:1、消息標識 2、是否批量簽收 3、是否重回隊列
*/
channel.basicNack(envelope.getDeliveryTag(), false, true);
} else {
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
});
}
運行代碼以上後,由於在消費端,設置了第一條消息,簽收失敗重回隊列,在RabbitMQ控制檯中我們可以看到始終有一條消息未簽收確認