RabbitMq 消息发送确认(可靠生产和推送确认)
此文档只是本人在项目中碰到的一些问题而产生的个人相关总结,实际上的消息确认机制可以做得更多(比如分布式事务等,但此处不做阐述)。
一.消息发送确认是什么:
是RabbitMq确认消息是否成功投递到交换机或者队列中的一种机制。
有两种确认方式:
1.确认消息是否到达交换机中。(Producer -----> Exchange)
实现RabbitTemplate类的一个内部接口ConfirmCallback,
这个接口的作用是生产者把消息发送到交换机的结果回调。
/**
* A callback for publisher confirmations.
*
*/
@FunctionalInterface
public interface ConfirmCallback {
/**
* Confirmation callback.
* @param correlationData correlation data for the callback.
* @param ack true for ack, false for nack
* @param cause An optional cause, for nack, when available, otherwise null.
*/
参数说明:
CorrelationData correlationData:可以封装业务ID信息,需要在发送消息时传入此参数,
这里才能接收到,否则是null
boolean ack:消息发送的结果状态,发送成功是true,失败是false
String cause:发送失败的描述信息,如果发送成功是null。
void confirm(@Nullable CorrelationData correlationData, boolean ack, @Nullable String cause);
}
2.确认消息是否从交换机转发到了具体队列中。(Exchange-----> Queue)
实现RabbitTemplate类的内部接口ReturnCallback
这个接口的作用是消息从交换机到队列的结果回调。
/**
* A callback for returned messages.
*
*/
@FunctionalInterface
public interface ReturnCallback {
/**
* Returned message callback.
* @param message the returned message.
* @param replyCode the reply code.
* @param replyText the reply text.
* @param exchange the exchange.
* @param routingKey the routing key.
*/
参数说明:
Message message:发送的消息内容 + 发送消息的配置信息
int replyCode:状态码,发送成功是200
String replyText:发送消息失败的描述信息
String exchange:消息使用的交换机
String routingKey:消息使用的路由键
void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey);
}
二.为什么使用消息确认:
对于使用RabbitMq推送数据,很多时候我们的关注点在于消息是否发送。而发送过程是否出现异常,发送是否真的成功则不太关心。 但是在处理项目的业务核心数据时,一条数据发送失败往往会导致后续业务也受到影响,且对于这种是否发送的数据的筛选也是非常的麻烦,需要来回校验数据,还有是否需要重发、到底是消息生产端出现了问题、还是消费端出现了问题等等…对于上述问题,都可使用RabbitMq的消息确认机制来进行处理。
三.消息确认能解决上述问题中的哪些问题
1.问题的定位范围缩小
能够确保生产端生产数据正常,消息未被正常处理时,只需要关注后续的消费即可。
2.生产失败的消息容易筛选
消息生产失败时,可直接在确认的机制中进行其他兜底处理:入库或者推入死信等,
然后根据业务场景进行数据的重新生产推送。不再需要重新筛选数据
3.可及时发现问题,减少业务异常
每天只需查看是否有生产失败的数据,然后针对这些数据进行优化即可。
4.缺点:
队列性能会受到一定程度的影响,且业务复杂度也上升了。
因此在使用的时候切忌大面积使用,只需针对非常核心的业务进行处理即可。
四.使用步骤
1.yml配置
# 本服务端口
server:
port: xxxxx
# 本服务应用名称
spring:
application:
name: xxxx-xxxx
# Nacos配置地址
cloud:
nacos:
discovery:
server-addr: xxxxx
#RabbitMq配置
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 此处写你的RabbitMq服务地址
port: 此处写你的RabbitMq端口
publisher-confirm-type: correlated #交换机是否收到消息确认,该配置有三个选项,具体看下面描述
publisher-returns: true # 队列是否收到消息确认,默认false
#publisher-confirm-type的三个属性
#NONE值是禁用发布确认模式,是默认值
#CORRELATED值是发布消息成功到交换器后会触发回调方法,如1示例
#SIMPLE值经测试有两种效果,其一效果和CORRELATED值一样会触发回调方法,
#其二在发布消息成功后使用rabbitTemplate调用waitForConfirms或waitForConfirmsOrDie方法等待broker节点返回发送结果,
#根据返回结果来判定下一步的逻辑,要注意的点是waitForConfirmsOrDie方法如果返回false则会关闭channel,则接下来无法发送消息到broker;
2.消息生产
package com.rabbitmq.nacos.producer.confirm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.UUID;
/**
* RabbitMq消息确认
*/
@Slf4j
@Component
public class RabbitMqPushMessageConfirm {
@Resource
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void messageConfirm() {
//1.确认消息是否到达交换机中。(Producer -----> Exchange)
//通过实现 ConfirmCallback 接口,确认消息是否正确到达Exchange中
//实现RabbitTemplate类的一个内部接口ConfirmCallback,
//注意:所有推送到该交换机的消息都会进入该方法中,推送ack参数来判断该交换机是否确切收到消息
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
//如果ack为true则代表Exchange已经收到消息
if (!ack) {
log.warn("消息发送到exchange失败,correlationData={}, cause={}", correlationData, cause);
//解析ID存储数据库兜底
String cid = correlationData.getId();
//模拟消息确认失败的数据入库场景
// failedMsgMapper.insertSelective(mqFailed);
}
log.info("消息{},交换机已收到", correlationData);
});
//2.确认消息是否从交换机转发到了具体队列中。(Exchange-----> Queue)
//实现RabbitTemplate类的一个内部接口ReturnCallback,启动消息失败返回,
//只要消息没有从交换机转发到对应的队列中去就会进入该方法
// 消息从交换到队列失败,失败原因可能是路由键不存在,通道未绑定等等,一般都跟配置有关系。
rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> {
//模拟消息确认失败的数据入库场景
// failedMsgMapper.insertSelective(message);
log.info("未找到路由为{}的队列", routingKey);
});
}
/**
* 说明:消息发送,使用该方法发送消息才可实现消息确认的效果。
*
* @param exchange 交换机
* @param routingKey 路由Key
*/
public void pushMessageByConfirm(String exchange, String routingKey) {
// 模拟发送邮件
String emailmsg = UUID.randomUUID().toString();
log.info("消息发送确认开始:给交换机:" + exchange + ",路由键为:" + routingKey + ",发送消息:" + emailmsg);
//CorrelationData可以携带一个字符串信息,协同需要生产的消息一起推送给交换机
//在消息确认中,可以针对CorrelationData对象携带字符串进行相关的业务操作
//比如携带字符串为:order=123456,则在消息确认时就能知道是order队列发送的消息。
//如果ack为false确认失败,也可以将其存起来,用以和上游业务比对数据
rabbitTemplate.convertAndSend(exchange, routingKey, emailmsg, new CorrelationData(emailmsg));
}
}