RabbitMQ学习(四):消息确认机制——事务模式,Confirm模式

目录

消息确认机制

事务模式

Confirm模式

1、普通Confirm模式

2、批量Confirm模式

3、异步Confirm模式

总结


消息确认机制

在之前的工作模式中,我们会发现,所有的生产者在推送完消息后就结束或者执行其他任务,并不知晓消息是否发送成功。如果要保证消息的可靠性,需要对消息进行持久化处理。除了设置持久化相关代码外,我们还要保证消息是被推送到代理服务器(broker)里。正常情况下,如果消息经过交换机进入队列就可以完成消息的持久化,但如果消息在没有到达broker之前出现意外,那就造成消息丢失。

RabbitMQ有两种方式来解决这个问题:

  1. 事务模式
  2. Confirm模式

事务模式

  1. channel.txSelect();声明事务模式
  2. channel.txCommit();提交事务
  3. chnnel.txRollback();回滚事务

生产者

public class Producer {
    private static final String QUEUE_NAME = "queue_tx";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.txSelect();

        try {
            channel.basicPublish("", QUEUE_NAME, null, "hello1".getBytes());
            channel.basicPublish("", QUEUE_NAME, null, "hello2".getBytes());
            // 模拟错误
            int i = 1/0;
            channel.txCommit();
        } catch (Exception e) {
            channel.txRollback();
        } finally {
            channel.close();
            connection.close();
        }

    }
}

执行之后,我们会发现执行的推送代码里的两条消息,在消费者并没有接收到。

Confirm模式

通过设置生产者Channel为comfirm模式,该Channel上发布的所有消息都会被指派一个唯一ID(每次从1开始累加),当消息到达生产者指定的消息队列后,broker会返回一个确认给生产者(包含之前的ID),这样生产者就能知道哪条消息成功发送了。

1、普通Confirm模式

每发送一条消息,就调用waitForConfirms()方法接收确认消息,返回ture成功,返回false或超时未返回代表失败。使用waitForConfirmsOrDie()失败时会抛出异常。

  1. channel.confirmSelect();声明Confirm模式(如果Channel已经被设置为事务模式,是不能修改的
  2. channel.waitForConfirms();接收确认,括号内可设置超时时间

生产者

public class Producer {
    private static final String QUEUE_NAME = "queue_confirm";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.confirmSelect();
        for (int i = 0; i < 10; i++) {
            channel.basicPublish("", QUEUE_NAME, null, ("hello" + i).getBytes());
            if (channel.waitForConfirms()) {
                System.out.println("成功发送");
            } else {
                // 重新发送
            }
        }
        channel.close();
        connection.close();
    }
}

普通Confirm模式由于waitForConfirms()方法内部使用了同步代码块,是串行化的,每发一条消息就要等待服务器的确认,会阻止后续消息的发布。因此会降低吞吐量,带来效率的下降。

2、批量Confirm模式

批量Confirm模式是指在一批消息发完之后,再调用waitForConfirms()方法,等待服务器确认,大大提高了confirm的吞吐量。但是这一批消息出问题时我们不能定位是哪条消息出问题了,因此所有消息都要重发,也可能会带来效率的下降。

另外,还能够使用waitForConfirmsOrDie()方法来等待服务器确认,该方法会等到最后一条消息得到确认或者得到nack才会结束,也就是说在waitForConfirmsOrDie处会造成当前程序的阻塞。

for (int i = 0; i < 500; i++) {
    channel.basicPublish("", QUEUE_NAME, null, ("hello" + i).getBytes());
}

if (channel.waitForConfirms()) {
    System.out.println("成功发送");
} else {
    // 重新发送
}

------------------或者-------------------

try {
    channel.waitForConfirmsOrDie();
} catch (Exception e) {
    // 重新发送
}

3、异步Confirm模式

之前的普通和批量模式,实际上都是同步的,会降低服务器的吞吐量。并且假设我们还想针对每个消息进行特定处理时。我们可以通过向Channel添加confirm的监听器来实现异步Confirm模式。这时我们需要自己去维护每个消息的唯一ID(deliveryTag)。

public class Producer {
    private static final String QUEUE_NAME = "queue_confirm_async";

    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        Connection connection = ConnectionUtils.getConnection();

        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);

        channel.confirmSelect();

        // 存放还未确认消息对应的唯一ID
        SortedSet<Long> unConfirmSet = Collections.synchronizedSortedSet(new TreeSet<Long>());
        channel.confirmSelect();

        //添加监听器
        channel.addConfirmListener(new ConfirmListener() {

            /**
             * // 消息发送确认的回调
             * @param deliveryTag 消息唯一ID
             * @param multiple 是否是批量确认,如果是true,代表包含当前返回的deliveryTag之前的所有消息都已确认
             * @throws IOException
             */
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                if (multiple) {
                    /**
                     * 多条批量清除
                     * headSet()返回的是指定元素之前的所有元素,因为deliveryTag是累加的,加1为了将其本身包含进来
                     * clear()从集合中清除headSet所返回的元素
                     */
                    unConfirmSet.headSet(deliveryTag + 1L).clear();
                    System.out.println("批量清除:" + deliveryTag + "以前的消息," + "剩余" + unConfirmSet.toString());
                } else {
                    // 若是单条确认,再集合中删除对应id
                    unConfirmSet.remove(deliveryTag);
                    System.out.println("单独清除:" + deliveryTag + "消息," + "剩余" + unConfirmSet.toString());
                }
            }

            // 消息发送失败的回调
            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                // 重发逻辑
            }
        });
        for(int i = 0; i < 10; i++){
            // 在发布之前使用getNextPublishSeqNo()获取消息对应唯一ID
            long nextSeqNo = channel.getNextPublishSeqNo();
            channel.basicPublish("", QUEUE_NAME, null, (" Confirm模式, 第" + (i + 1) + "条消息").getBytes());

            // 添加到未确认集合中
            unConfirmSet.add(nextSeqNo);
            System.out.println("新增消息后剩余:" + unConfirmSet.toString());
        }
    }
}

从执行结果我们可以看出,消息的发送和服务器确认是同时异步的,同时进行的。这样就能达到最佳性能和资源使用。

总结

  • 分别发布消息,同步等待确认:简单,但吞吐量非常有限。
  • 批量发布消息,同步等待批量确认:简单,合理的吞吐量,但是很难推断出什么时候出了问题。
  • 异步处理:最佳性能和资源使用,在出现错误的情况下可以很好地控制。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值