RabbitMQ的消息确认机制(事务+confirm)
两种方式:
AMQP:实现了事务机制
Confirm模式
事务机制
txSelect:用于将当前channel设置成transation模式
txCommit:用于事务提交
txRollback:回滚事务
代码实操:
生产者:
public class TxSend {
private static String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws Exception {
//获取一个连接
Connection connection = ConnectionUtils.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String msg = "hello tx";
try {
channel.txSelect();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
channel.txCommit();
} catch (Exception e) {
channel.txRollback();
System.out.println("send msg rollback");
}
channel.close();
connection.close();
}
}
消费者:
public class TxRecv {
private static String QUEUE_NAME = "test_queue_tx";
public static void main(String[] args) throws Exception {
//获取一个连接
Connection connection = ConnectionUtils.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.out.println("recv tx msg:"+new String(body));
}
});
}
}
测试:
出错后,回滚,消费者接收不到:
缺点:降低吞吐量
Confirm模式:
生产者端confirm模式的实现原理:
生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了,如果消息和队列是可持久化的,那么确认消息就会将消息写入磁盘后发出,broker回传给生产者的确认消息中deliver-tag域包含了确认消息的序列号,此外broker也可以设置basic.ack的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
Confirm模式最大的好处在于异步。
开启confirm模式:channel.confirmSelect();
编程模式:
普通模式:发一条 waitForConfirms();
代码实操:
/*
* 普通模式
*/
public class Send1 {
private static String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws Exception {
//获取一个连接
Connection connection = ConnectionUtils.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//生产者调用confirmSelect(),将channel设置为confirm模式
channel.confirmSelect();
String msg = "hello confirm1";
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
if(!channel.waitForConfirms()){
System.out.println("msg send fail");
}else{
System.out.println("msg send succ");
}
channel.close();
connection.close();
}
}
消费者:
public class Recv1 {
private static String QUEUE_NAME = "test_queue_confirm1";
public static void main(String[] args) throws Exception {
//获取一个连接
Connection connection = ConnectionUtils.getConnection();
//从连接中获取一个通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicConsume(QUEUE_NAME, true,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, BasicProperties properties, byte[] body)
throws IOException {
System.out.println("recv confirm msg:"+new String(body));
}
});
}
}
测试:
消费者收到消息:
生产者收到消息回执:
批量模式:发一批 waitForConfirms
每次批量发送多条消息,消费者全部收到后,再通知生产者。这里就不贴代码了。
异步confirm模式:提供一个回调方法
Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Channel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应一条(multiple=false)或多条(multiple=true)记录。
代码实操:
生产者:
public class Send3 {
private static final String QUEUE_NAME = "test_queue_confirm3";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtils.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//生产者调用confirmSelect将channel设置为confirm模式
channel.confirmSelect();
//存放未确认的消息标识
final SortedSet<Long> confirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
//通道添加监听
channel.addConfirmListener(new ConfirmListener() {
//handleNack,失败的话,看业务场景如何处理
public void handleNack(long deliveryTag, boolean multiple) throws
IOException{
if(multiple){
System.out.println("-----handleNack-------multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("---handleNac-----multiple false");
confirmSet.remove(deliveryTag);
}
}
//没有问题的handleAck
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
if(multiple){
System.out.println("-----handleAck-------multiple");
confirmSet.headSet(deliveryTag+1).clear();
}else{
System.out.println("---handleAck-----multiple false");
confirmSet.remove(deliveryTag);
}
}
});
String msg = "ssss";
while(true){
long seqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
//向set中添加没有回执的消息的标识
confirmSet.add(seqNo);
}
}
}
消费者代码和之前一样,此处就略过了。
测试: