RabbitMQ学习总结
前面讲到了使用消息队列解耦时,生产端只需负责将消息投递到消息队列就完事了,那么必须要考虑的问题就是如何保证生产端将消息可靠的投递到了MQ中
AMQP协议以及RabbitMQ考虑到了这一问题,因此在设计时添加了Confirm消息确认机制和Return机制,首先先讲一下这两个机制
一. Confirm消息确认机制和Return机制
Confirm消息确认机制: 生产者向MQ投递完消息后,要求MQ返回一个应答,生产者异步接收该应答,用来确定该消息是否正常的发送到了Broker, 从而保障消息的可靠性投递
Return消息返回机制:该机制用于处理一些不可路由的消息。如果生产在发送消息时,发现当前的exchange不存在或者指定的路由key找不到时,生产者可以开启该模式来监听这种不可达的消息,以进行后续。(如果不开启的话,broker会自动删除该消息)
这里要注意的是,只要消息到达了MQ就换返回Confirm消息,接下来MQ再去判断能不能找到路由方式,找不到再返回Return消息
Confirm消息确认机制的实现
- 首先在配置文件中设置
spring:
rabbitmq:
publisher-confirm-type: correlated
开启消息确认模式
- 实现ConfirmCallback接口
RabbitTemplate.ConfirmCallback confirmCallback = new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack){
log.info(correlationData.toString() + "发送成功");
}else {
log.error(correlationData.toString() + "发送失败, 原因: " + cause);
}
}
};
其中重写的方法包含三个参数:
- correlationData:发送消息时设置的correlationData。由于confirm消息是异步监听的,因此需要在发送消息时传递一个correlationData,从而在返回confirm消息时判断其属于哪个消息,所以correlationData通常设置为消息的唯一ID;
- ack:broker返回的应答,如果broker成功接收消息,则返回true代表接收成功,如果因为各种原因没有成功接收(如消息队列满了),则返回false
这里要注意,由于各种原因(如网络波动),生产端可能并没有收到confirm消息,因此不能将后续的补偿处理仅仅寄希望于在else内完成,else内做的补偿仅仅是在生产端收到confirm消息后nack的情况
- cause: 如果没有被成功接收,则返回原因
- 为rabbitTemplate添加刚刚的Confirm监听器
rabbitTemplate.setConfirmCallback(confirmCallback());
- 利用该rabbitTemplate发送消息即可
rabbitTemplate.convertAndSend(exchange, routingkey, order, new CorrelationData(order.getId());
Return消息返回机制的实现
Return消息返回机制的实现与上面的Confirm消息确认机制的实现类似
- 在配置文件中设置
spring:
rabbitmq:
publisher-returns: true
template:
mandatory: true
注意,mandatory一定要设置为true,否则找不到路由规则的消息会被broker直接抛弃
- 实现returnCallback接口
RabbitTemplate.ReturnCallback returnCallback = new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyTest, String exchange, String routingKey) {
log.error("消息{}路由失败,失败原因:{}",message.getMessageProperties().getMessageId(),replyTest);
}
};
- 为rabbitTemplate添加刚刚的returnCallback监听器
rabbitTemplate.setReturnCallback(returnCallback);
- 利用该rabbitTemplate发送消息即可
rabbitTemplate.convertAndSend(exchange, routingkey, order, new CorrelationData(order.getId());
消息可靠性投递方案
方案一:利用Confirm消息确认机制
前面讲完了RabbitMQ自带的Confirm消息确认机制和Return机制,而实现消息可靠性投递的第一个方案就是利用该确认机制
实现思路如下:
- 在生产端向消息队列发送消息前,首先将业务信息和对应的消息信息入库(如生成订单时,需要修改数据库中的订单表和订单消息表),其中订单消息表中有一个记录该消息是否发送成功的字段
- 向消息队列发送该消息,并在发送前设置好Confirm消息的监听器
- 如果收到Confirm消息,代表该消息已发送成功,那么就可以将订单消息表中的发送状态改为发送成功
- 设置一定时任务去抓取订单消息表中没有没有发送成功的消息,并进行重新发送
- 如果重新发送了几次后消息都没有发送成功,则将其状态修改为发送失败,后续进行人工补偿
该方案的缺点是在发送消息前,需要进行两次落库操作(修改数据库中的订单表和订单消息表),因此会对性能造成一定影响
方案二:消息延迟投递,做二次确认,回调检查
实现思路如下:
- 生产者消息发送前,只需要将业务信息入库(如修改订单表)
- 向MQ发送该消息以及一条延迟消息(其中第一条消息由消费者接收,第二条消息由独立的Callback服务接收)
- 消费者收到第一条消息后,将接收成功的消息回送给MQ(该回送消息也由Callback服务接收)
- Callback服务收到消费者接收成功的消息,将该消息入库
- 一段时间后,Callback服务又收到生产者的延迟消息,它根据该延迟消息的id信息去查找数据库中有没有该条记录
- 如果查到了,说明该消息已成功投递且被消费者成功消费
- 如果没查到,说明该消息没有被消费者成功消费,可能是没有投递成功,这时Callback服务再去远程调用生产者告知其重新发送消息
该方案优点是在生产者端只需要入库一次,而将消息的入库操作独立到了Callback服务中去,提升了生产者端的性能。但是该方案实现较复杂,里面还有很多的细节值得考虑