rabbitmq支持两种模式的事务消息:
代码比较简单,开启事务txSelect,提交txCommit,回滚txRollback。
实例:
public class Send {
private final static String QUEUE_NAME = "q_test_transaction_01";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
try {
channel.txSelect();//开启事务
// 消息内容
String message = "Hello rabbitmq!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(ConnectionUtil.getTime() + " [x] Sent '" + message + "'");
// System.out.println(1/0);//模拟出错回滚
TimeUnit.SECONDS.sleep(3);//模拟3秒耗时,消费者应该在3秒后消息提交后才能收到消息
channel.txCommit();//提交事务
} catch (Exception e) {
System.out.println("事务回滚!");
channel.txRollback();//事务回滚
e.printStackTrace();
}
channel.close();
connection.close();
}
}
public class Recv {
private final static String QUEUE_NAME = "q_test_transaction_01";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//channel监听队列。
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(ConnectionUtil.getTime() + " [x] Received '" + message);
}
});
}
}
上面代码中我让生产者发送消息后等待3秒在提交,验证事务提交前的消息消费者是无法消费到的。
运行消费者与生产者,观察日志:
另外加入了1/0
来模拟失败回滚。
confirm 事务消息是异步的,不需要阻塞等待。
代码编写比较简单,首先将channel设置为confirm模式channel.confirmSelect()
,然后发布消息(支持批量),最后通过channel.waitForConfirms()
来确认消息是否发送成功。
代码:
public class Send {
private final static String QUEUE_NAME = "q_test_transaction_confirm_01";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.confirmSelect(); //开启事务,将channel设置为confirm模式. 注意confirm模式是异步的
// 消息内容
// 支持批量发送。这里加个for循环就是批量发送,下面waitForConfirms确认还是只确认一次
String message = "Hello rabbitmq!";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(ConnectionUtil.getTime() + " [x] Sent '" + message + "'");
//模拟3秒耗时,由于发送消息是异步的,所以消费者并不会在3秒后才收到消息
TimeUnit.SECONDS.sleep(3);
if(!channel.waitForConfirms()){
System.out.println("msg send ERROR!");
}else{
System.out.println("msg send ok.");
}
channel.close();
connection.close();
}
}
public class Recv {
private final static String QUEUE_NAME = "q_test_transaction_confirm_01";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
//channel监听队列。
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
String message = new String(body, "UTF-8");
System.out.println(ConnectionUtil.getTime() + " [x] Received '" + message);
}
});
}
}
运行消费者与发送者,控制台输出:
可以看到,由于是异步发送,消费者第一时间收到了消息。
如果想批量发送,只需要循环channel.basicPublish
方法发送想消息即可,其他不变。批量发送只会确认一次。
上面代码还是需要在代码中channel.waitForConfirms()
进行等待。
confirm还支持监听回调的方式,在收到服务器响应回执后进行。这里回调是异步操作。
生产者代码:
public class Send {
private final static String QUEUE_NAME = "q_test_transaction_confirmasync_01";
public static void main(String[] argv) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, true, false, false, null);
channel.confirmSelect(); //开启事务,将channel设置为confirm模式. 注意confirm模式是异步的
SortedSet<Object> set = Collections.synchronizedSortedSet(new TreeSet<>());
//添加通道监听器,用于监听发送结果
//疑问:接收到的回执NO是有序的吗???
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
//正确的消息
if (multiple) {
System.out.println("handleAck--multiple--deliveryTag=" + deliveryTag);
set.headSet(deliveryTag + 1).clear();
} else {
System.out.println("handleAck--not--multiple--deliveryTag=" + deliveryTag);
set.remove(deliveryTag);
}
}
@Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
//错误的消息
if (multiple) {
System.out.println("handleNack--multiple--deliveryTag=" + deliveryTag);
set.headSet(deliveryTag + 1).clear();
} else {
System.out.println("handleNack--not--multiple--deliveryTag=" + deliveryTag);
set.remove(deliveryTag);
}
}
});
// 消息内容
// 支持批量发送。这里加个for循环就是批量发送,下面waitForConfirms确认还是只确认一次
for (int i = 0; i < 10; i++) {
String message = "Hello rabbitmq!" + i;
long nextPublishSeqNo = channel.getNextPublishSeqNo();
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(ConnectionUtil.getTime() + " [x] Sent '" + message + "'");
set.add(nextPublishSeqNo);//将NO添加到msg
}
TimeUnit.SECONDS.sleep(3);//用于等待异步处理完成再退出
channel.close();
connection.close();
}
}
消费者代码不变,只是修改一下队列名称。
运行消费者与生产者,控制台输出:
可以看到,回调函数执行了3次,seqNo分别是1,2,10。当然这不是确定的,每次运行都可能不一样。
通过结果观察可以看到,回执并不会每个seqNO都执行一次,会有批量回执。
疑问:回执是否是按照发送序号有序回调我们的监听函数的???