rabbitmq 事务消息

rabbitmq支持两种模式的事务消息:

  • AMQP
  • confirm

AMQP 事务消息

代码比较简单,开启事务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 事务消息

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都执行一次,会有批量回执。

疑问:回执是否是按照发送序号有序回调我们的监听函数的???

发布了234 篇原创文章 · 获赞 227 · 访问量 95万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie

分享到微信朋友圈

×

扫一扫,手机浏览