當生產端推送消息到rabbit上時我們需要確認消息是否正常的推送到rabbit上了,推送成功了生產端無需在關心消息的進一步處理,推送失敗了生產端需要對該信息做進一步的處理。
rabbit爲該類問題提供了對應的解決策略。
- addConfirmListener
- addReturnListener
addConfirmListener:監聽信息是否有推送到rabbit中
/**
* Channel 對象提供的 ConfirmListener()回調方法只包含 deliveryTag(當前 Chanel 發出的消息序號),我們需要自己爲
* 每一個 Channel 維護一個 unconfirm 的消息序號集合,每 publish 一條數據,集合中元素加 1,每回調一次 handleAck
* 方法,unconfirm 集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。從程序運行效率上看,這個
* unconfirm 集合最好採用有序集合 SortedSet 存儲結構。實際上,SDK 中的 waitForConfirms()方法也是通過 SortedSet
* 維護消息序號的。
* Created by py
* 2020/4/20
*/
public class SendAync {
private static final String QUEUE_NAME = "QUEUE_simple_confirm_aync";
public static void main(String[] args) throws IOException, TimeoutException {
/* 獲取一個連接 */
Connection connection = RabbitUtils.getInstance();
/* 從連接中創建通道 */
Channel channel = connection.createChannel();
channel.basicQos(1);
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//生產者通過調用channel的confirmSelect方法將channel設置爲confirm模式
channel.confirmSelect();
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
channel.addConfirmListener(new ConfirmListener() {
//每回調一次handleAck方法,unconfirm集合刪掉相應的一條(multiple=false)或多條(multiple=true)記錄。
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if (multiple) {
System.out.println("--multiple--");
confirmSet.headSet(deliveryTag + 1).clear();
//用一個SortedSet, 返回此有序集合中小於end的所有元素。
} else {
System.out.println("--multiple false--");
confirmSet.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//正常只有rabbit出問題纔會走此方法:如磁盤寫滿了,MQ出現了一些異常,或者Queue容量到達上限了等等
System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple);
if (multiple) {
confirmSet.headSet(deliveryTag + 1).clear();
} else {
confirmSet.remove(deliveryTag);
}
}
});
String msg = "Hello QUEUE !";
//while (true) {
for (int i = 0; i <10 ; i++) {
long nextSeqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
confirmSet.add(nextSeqNo);
}
//}
}
}
/**
* Created by py
* 2020/4/19
*/
public class RecRabbit {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
/*//老版本監聽,已過時
oldGet(channel);*/
//聲明隊列 如果能確定是哪一個隊列 這邊可以刪掉,不去掉 這裏會忽略創建
channel.basicQos(1);
channel.queueDeclare("QUEUE_simple_confirm_aync",false,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.err.println("new==Received '" + message + "'");
}
};
channel.basicConsume("QUEUE_simple_confirm_aync",true,defaultConsumer);
}
}
addReturnListener:監聽是否有推送到隊列中(信息可能推送到rabbit中了但是沒有推送進指定的隊列中)
/**
* Created by py
* 2020/4/19
*/
public class SendRabbit {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
/* * 獲取一個連接 */
Connection instance = RabbitUtils.getInstance();
/** 從連接中創建通道*/
Channel channel = instance.createChannel();
/** 在通道中聲明一個隊列
* 因爲我們要往隊列裏面發送消息,這是後就得知道往哪個隊列中發送,就好比在哪個管子裏面放水
**/
boolean durable=false;
boolean exclusive=false;
boolean autoDelete=false;
//channel.queueDeclare("simple_queue",durable,exclusive,autoDelete,null);
channel.exchangeDeclare("test_exchange_topic","topic",false);
//第一個參數是exchangeName(默認情況下代理服務器端是存在一個""名字的exchange的,
//因此如果不創建exchange的話我們可以直接將該參數設置成"",如果創建了exchange的話
//我們需要將該參數設置成創建的exchange的名字,第二個參數是路由鍵
/*channel.confirmSelect();*/
String msg = "This is simple queque!";
//channel.basicPublish("","simple_queue",null,msg.getBytes());
//basicPublish(String exchange, String routingKey, boolean mandatory, BasicProperties props, byte[] body)
/**
* exchange:交換機名稱
* routingKey:交換機與隊列綁定的key
* mandatory:消息爲傳送到指定隊列時broker的處理規則,
* true:由生產者定義的returnListener接收,false:broker直接刪除該條信息,
* todo 默認爲false
* props:請求時攜帶的參數配置;如:配置死信隊列
* body:傳輸的信息
*/
channel.basicPublish("test_exchange_topic","audit.saas.534351",true,null,msg.getBytes());
/*if(channel.waitForConfirms()){
System.out.println("信息發送成功");
}else{
System.out.println("信息發送失敗");
}*/
CustomerReturnListener customerReturnListener = new CustomerReturnListener();
channel.addReturnListener(customerReturnListener);
channel.close();
instance.close();
}
}
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.ReturnListener;
import java.io.IOException;
/**
* Created by py
* 2020/4/22
*/
public class CustomerReturnListener implements ReturnListener {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
System.out.println("i:"+i);
System.out.println("s:"+s);
System.out.println("s1:"+s1);
System.out.println("s2:"+s2);
String body = new String(bytes,"utf-8");
System.out.println("body:"+body);
}
}
import com.example.springcloud.eurekaclinet1demo.uitl.RabbitUtils;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* Created by py
* 2020/4/19
*/
public class RecMessage {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
Connection instance = RabbitUtils.getInstance();
Channel channel = instance.createChannel();
//聲明隊列 如果能確定是哪一個隊列 這邊可以刪掉,不去掉 這裏會忽略創建
channel.queueDeclare("simple_queue",false,false,false,null);
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
super.handleDelivery(consumerTag, envelope, properties, body);
String message = new String(body, "UTF-8");
System.err.println("new==Received '" + message + "'");
}
};
channel.basicConsume("simple_queue",true,defaultConsumer);
}
}
PS:basicPublish()中mandatory參數必須設置爲true纔會返回爲投遞成功的信息給生產者,設置爲false時rabbit會自動丟棄該條信息。