RabbitMQ:生产者确认

在使用RabbitMQ的时候,可以通过消息持久化操作来解决因为服务器的异常崩溃而导致的消息丢失。除此之外,我们还会遇到一个问题,当消息的生产者将消息发送出去之后,消息到底有没有正确地到达服务器呢?如果不进行特殊配置,默认情况下发送消息的操作是不会返回任何信息给生产者的,也就是默认情况下生产者是不知道消息有没有正确地到达服务器。如果在消息到达服务器之前已经丢失,持久化操作也解决不了这个问题,因为消息根本没有到达服务器,怎么持久化?RabbitMQ针对这个问题,提供了两种解决方案:

  • 通过事务机制实现。
  • 通过生产者确认机制实现。

1. 事务机制

RabbitMQ客户端中与事务机制相关的方法有三个:channel.txSelectchannel.txCommitchannel.txRollback

  • channel.txSelect:用于将当前的信道设置成事务模式。
  • channel.txCommit:用于提交事务。
  • channel.txRollback:用于事务回滚。

1.1 事务正常提交

当执行channel.txCommit那么消息一定到达服务器,此时消费者接收到消息进行消费。

channel.exchangeDeclare("demo.direct.exchange", "direct", true, false, null);
channel.queueDeclare("demo.queue", true, false, false, null);
channel.queueBind("demo.queue", "demo.direct.exchange", "demo.routingKey");

try {
    channel.txSelect();
    channel.basicPublish("demo.direct.exchange", "demo.routingKey",
                         MessageProperties.PERSISTENT_TEXT_PLAIN, "transaction message".getBytes());
    channel.txCommit();
    System.out.println("commit successful!");
} catch (Exception e) {
    System.out.println("rollback!");
    channel.txRollback();
}

在这里插入图片描述

1.2 事务回滚

channel.exchangeDeclare("demo.direct.exchange", "direct", true, false, null);
channel.queueDeclare("demo.queue", true, false, false, null);
channel.queueBind("demo.queue", "demo.direct.exchange", "demo.routingKey");

try {
    channel.txSelect();
    channel.basicPublish("demo.direct.exchange", "demo.routingKey",
                         MessageProperties.PERSISTENT_TEXT_PLAIN, "transaction message".getBytes());
    int result = 10 / 0;
    channel.txCommit();
    System.out.println("commit successful!");
} catch (Exception e) {
    System.out.println("rollback!");
    channel.txRollback();
}

在这里插入图片描述

2. 生产者确认

事务机制虽然能够解决消息是否真正地到达了RabbitMQ,但是采用事务机制实现会严重降低RabbitMQ的消息吞吐量,而生产者确认方案则相对来说更加的轻量级。

生产者将信道设置成confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的id,一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者,这就使得生产者知晓消息已经正确到达目的地了。如果消息的和队列是可持久化的,那么确认消息会在消息写入磁盘之后发出。如果RabbitMQ因为自身内部错误导致消息丢失,就会发送一条Basic.Nack消息给生产者。

事务机制在一条消息发送之后会使发送端阻塞,以等待RabbitMQ的回应,之后才能继续发送下一条消息。相比之下,发送方确认机制最大的好处在于它是异步的,一旦发布一条消息,生产者应用程序就可以在等信道返回确认的同时继续发送下一条消息。当消息得到确认之后,再通过回调方法来处理该确认消息。

2.1 普通confirm模式

普通的confirm模式是一种串行同步等待的方式,和事务机制一样,发送消息之后等待服务端确认,之后再发送消息。

channel.exchangeDeclare("demo.direct.exchange", "direct", true, false, null);
channel.queueDeclare("demo.queue", true, false, false, null);
channel.queueBind("demo.queue", "demo.direct.exchange", "demo.routingKey");

try {
    channel.confirmSelect();
    channel.basicPublish("demo.direct.exchange", "demo.routingKey",
                         MessageProperties.PERSISTENT_TEXT_PLAIN, "confirm message".getBytes());
    if (channel.waitForConfirms() == false) {
        System.out.println("send message failed");
    }
} catch (Exception e) {
    System.out.println("error!");
}

2.2 批量confirm模式

客户端在批量发送一批消息后,再调用channel.waitForConfirms来等待RabbitMQ的确认返回。相比普通的confirm模式,其极大地提升了confirm的效率,但是问题在于出现返回Basic.Nack或者超时情况时,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息数量,并且当消息经常丢失时,批量confirm的性能应该是不升反降的。

List<String> waitingForConfirms = new ArrayList<>();

try {
    channel.confirmSelect();
    int index = 0;
    while (true) {
        String message = "confirm message " + index;
        waitingForConfirms.add(message);
        channel.basicPublish("demo.direct.exchange", "demo.routingKey",
                             MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());
        if (++index >= 10) {
            index = 0;
            try {
                if (channel.waitForConfirms()) {
                    // 当前批次消息已经全部发送成功,清空缓存,进行下一批次的消息发送。
                    waitingForConfirms.clear();
                } else {
                    // 当前批次消息确认失败,重新发送
                }
            } catch (Exception e) {
                // 当前批次消息确认失败,重新发送
            }
        }
    }
} catch (Exception e) {
    System.out.println("error!");
}

2.3 异步confirm模式

提供ConfirmListener这个回调接口即可。

SortedSet<Long> confirmSet = new TreeSet<>();
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
    @Override
    public void handleAck(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("Ack,SeqNo:" + deliveryTag + ",multiple:" + multiple);
        if (multiple) {
            confirmSet.headSet(deliveryTag - 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
    }

    @Override
    public void handleNack(long deliveryTag, boolean multiple) throws IOException {
        System.out.println("Nack,SeqNo:" + deliveryTag + ",multiple:" + multiple);
        if (multiple) {
            confirmSet.headSet(deliveryTag - 1).clear();
        } else {
            confirmSet.remove(deliveryTag);
        }
        // 处理消息重发的业务逻辑
    }
});
while (true) {
    TimeUnit.SECONDS.sleep(10);
    long nextSqlNo = channel.getNextPublishSeqNo();
    String message = "confirm message " + nextSqlNo;
    channel.basicPublish("demo.direct.exchange", "demo.routingKey",
                         MessageProperties.PERSISTENT_TEXT_PLAIN, message.getBytes());

    confirmSet.add(nextSqlNo);
}
——End——
更多精彩分享,可扫码关注微信公众号哦。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值