RabbitMQ的可靠性投递

RabbitMQ工作模型

生产者将消息发送到Broker。

生产者把消息发到Broker 之后,怎么知道自己的消息有没有被Broker 成功接收?

在RabbitMQ 里面提供了两种机制服务端确认机制,Transaction(事务)模式与Confirm(确认)模式。即生产者发送消息给RabbitMQ 的服务端的时候,服务端会通过某种方式返回一个应答,只要生产者收到了这个应答,就知道消息发送成功了。

1.Transaction(事务)模式

生产者通过channel.txSelect()的方法把channel设置成事务模式,当给RabbitMQ服务端发消息时,如果channel.txCommit()方法调用成功,就说明事务提交成功,如果在事务提交执行之前由于RabbitMQ 异常崩溃或者其他原因抛出异常,这个时候可以将其捕获,进而通过执行channel.txRollback()方法来实现事务回滚。

事务模式有一个缺点,它是阻塞的,一条消息没有发送完毕,不能发送下一条消息,效率较低,所以一般很少使用。

2.Confirm(确认)模式

生产者通过channel.confirmSelect()方法将channel设置为Confirm模式,发送消息后,一旦消息被正确投递,RabbitMQ 就会发送一个确认(Basic.Ack)给生产者,即调用channel.waitForConfirms()返回true,这样生产者就知道消息被服务端接收了。

为了提高效率,可以采用批量确认的方式,但是也有两个问题,第一,批量数量的选择;第二,如果1000条消息,前999条成功,最后1条失败,那么所有消息都要重发。

为了解决上面的两种问题,可以采用异步确认模式,异步确认模式需要添加一个ConfirmListener,并且用一个SortedSet 来维护没有被确认的消息。

rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if (!ack) {
            System.out.println("发送消息失败:" + cause);
            throw new RuntimeException("发送异常:" + cause);
        }
    }
});

消息从Exchange路由到Queue

Exchange是一个绑定列表,如果消息没有办法路由到正确的队列,会发生什么事情?应该怎么处理?

我们有两种方式处理无法路由的消息,一种就是让服务端重发给生产者,另一种是让交换机路由到另一个备份的交换机。

消息回发的方式:使用mandatory 参数和ReturnListener(在Spring AMQP 中是ReturnCallback)。

rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback(){
    public void returnedMessage(Message message,
        int replyCode,
        String replyText,
        String exchange,
        String routingKey){
            System.out.println("回发的消息:");
            System.out.println("replyCode: "+replyCode);
            System.out.println("replyText: "+replyText);
            System.out.println("exchange: "+exchange);
            System.out.println("routingKey: "+routingKey);
        }
});

消息路由到备份交换机的方式:在创建交换机的时候,从属性中指定备份交换机。

Map<String,Object> arguments = new HashMap<>();
arguments.put("alternate-exchange","ALTERNATE_EXCHANGE"); // 指定交换机的备份交换机
channel.exchangeDeclare("TEST_EXCHANGE","topic", false, false, false, arguments);

消息在Queue 中存储

队列是一个独立运行的服务,有自己的数据库(Mnesia),它是真正用来存储消息的。如果还没有消费者来消费,那么消息要一直存储在队列里面。如果队列出了问题,消息肯定会丢失。怎么保证消息在队列稳定地存储呢?

1.队列持久化

durable属性设置为true

@Bean("TestQueue")
public Queue TestQueue() {
    // queueName, durable, exclusive, autoDelete, Properties
    return new Queue("TEST_QUEUE", true, false, false, new HashMap<>());
}

2.交换机持久化

@Bean("TestExchange")
public DirectExchange exchange() {
    // exchangeName, durable, exclusive, autoDelete, Properties
    return new DirectExchange("TEST_EXCHANGE", true, false, new HashMap<>());
}

3.消息持久化

MessageProperties messageProperties = new MessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
Message message = new Message("持久化消息".getBytes(), messageProperties);
rabbitTemplate.send("TEST_EXCHANGE", "hujy.test", message);

4.集群

如果只有一个RabbitMQ 的节点,即使交换机、队列、消息做了持久化,如果服务崩溃或者硬件发生故障,RabbitMQ 的服务一样是不可用的,所以为了提高MQ 服务的可用性,保障消息的传输,我们需要有多个RabbitMQ 的节点。

消费者订阅Queue并消费消息

队列的特性是FIFO。也就是说,只有上一条消息被消费者接收以后,才能把这一条消息从数据库删掉,继续投递下一条消息。那么Broker 怎么知道消费者已经接收了消息呢?

RabbitMQ 提供了消费者的消息确认机制(message acknowledgement),消费者可以自动或者手动地发送ACK 给服务端。

application.properties

#NONE MANUAL AUTO
spring.rabbitmq.listener.direct.acknowledge-mode=manual
spring.rabbitmq.listener.simple.acknowledge-mode=manual

NONE:自动ACK

MANUAL: 手动ACK

AUTO:如果方法未抛出异常,则发送ack。

消费者回调

在某些业务场景下,为了提高消息投递的可靠性,消费者在消费完消息后可以回调生产者API,以达到响应消息的目的。例如商业银行与人民银行二代支付通信,无论是人行收到了商业银行的消息,还是商业银行收到了人行的消息,都必须发送一条响应消息(叫做回执报文)。

补偿机制

如果生产者的API 就是没有被调用,也没有收到消费者的响应消息,怎么办?

其中原因可能是消费者处理时间太长或者网络超时。

生产者与消费者之间应该约定一个超时时间,比如5 分钟,对于超出这个时间没有得到响应的消息,可以设置一个定时重发的机制,但要发送间隔和控制次数,比如每隔2分钟发送一次,最多重发3 次,否则会造成消息堆积。

重发可以通过消息落库+定时任务来实现。

消息幂等性

如果消费者每一次接收生产者的消息都成功了,只是在响应或者调用API 的时候出了问题,会不会出现消息的重复处理?

为了避免相同消息的重复处理,必须实现消息的幂等性,可以对每一条消息生成一个唯一的业务ID,通过日志或者消息落库来做重复控制。

最终一致性

如果确实是消费者宕机了,或者代码出现了BUG 导致无法正常消费,在我们尝试多次重发以后,消息最终也没有得到处理,怎么办?

在金融系统中,都会有双方对账或者多方对账的操作,通常是在一天的业务结束之后,第二天营业之前,找到不一致的数据再进行手工平账,实现最终一致。

消息的顺序性

比如:1.发表微博;2.发表评论;3.删除微博。顺序不能颠倒。

在RabbitMQ 中,一个队列有多个消费者时,由于不同的消费者消费消息的速度是不一样的,顺序无法保证。只有一个队列仅有一个消费者的情况才能保证顺序消费(不同的业务消息发送到不同的专用的队列)。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@从入门到入土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值