本文为代码篇,建议先看理论篇,点击跳转
实现可靠性传输的理论篇对应的实操代码。
业务相关的都用了伪代码,方便理解。
环境:
springboot 2.1.9.RELEASE + amqp-client-5.4.3.jar
生产者确认模式
application.properties
spring.rabbitmq.publisher-confirms=true
主动分配唯一Id:(这里其实需要预处理器MessagePostProcessor
,在持久化的内容中有讲)
public void sendMessage(String msg) {
CorrelationData correlationData = new CorrelationData();
correlationData.setId(唯一Id);
redis设置Id 与 msg 一一对应
rabbitTemplate.convertAndSend(交换机, 路由键, msg, correlationData);
}
confirmCallback接口,监听Broker发送的通知
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// correlationData 与 主动分配的唯一Id有关
String uniqueId = correlationData.getId();
if(ack){
根据uniqueId,删除redis中的记录
}else {
// ack = false ,相当于回复nack
不处理,由定时任务进行重发
}
}
});
定时任务:
@EnableScheduling
@Component
public class FixedTimeTask {
@Scheduled(cron = "*/30 * * * * ?")
public void execute() {
扫描redis中,所有未发送的键。
if(重发未超次数限制){
重发
}else{
入库,记录消息,人工处理。
注意删除redis中的相关键
}
}
}
无法被路由的消息处理
-
失败确认:
application.properties
spring.rabbitmq.publisher-returns=true
// 两者都必须设置,才能触发returnCallBack rabbitTemplate.setMandatory(true); rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() { @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { 相关处理代码... } });
-
声明交换机时,指定备份的交换机 , eg:
@Bean public DirectExchange secKillExchange() { Map<String, Object> argsMap = new HashMap<String, Object>(); argsMap.put("alternate-exchange", MIAOSHA_EXCHANGE_BAK); DirectExchange directExchange = new DirectExchange(MIAOSHA_EXCHANGE,true,false,argsMap); return directExchange; }
拓:若方案1、2一起使用,方案2生效,方案1失效
如何持久化消息队列
设置持久化,两个步骤:必须都设置。
-
创建 exchange、queue 时,设置为持久化
durable = true
:(不演示,默认就是true)
-
发送消息时,将消息Properties的
deliveryMode=PERSISTENT
:MessagePostProcessor
的原理可见另一篇博文,点击跳转
rabbitTemplate.setBeforePublishPostProcessors(correlationIdProcessor());
@Bean
/***
* @Description: 绑定correlationId、以及消息持久化
**/
public MessagePostProcessor correlationIdProcessor() {
MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message, Correlation correlation) {
MessageProperties messageProperties = message.getMessageProperties();
if (correlation instanceof CorrelationData) {
String correlationId = ((CorrelationData) correlation).getId();
messageProperties.setCorrelationId(correlationId);
}
// 持久化处理
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
@Override
public Message postProcessMessage(Message message) throws AmqpException {
MessageProperties messageProperties = message.getMessageProperties();
messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
};
return messagePostProcessor;
}
如何保证消费成功(或限流处理)
application.properties
# manual ,手动确认
spring.rabbitmq.listener.direct.acknowledge-mode=manual
# prefetch,预抓取。 等价于缓冲区大小 。 限流关键
spring.rabbitmq.listener.direct.prefetch=100
@RabbitListener(queues = 队列名)
@RabbitHandler
public void receiveDeadMsg(String msg, Channel channel, Message messages) throws Exception {
try {
业务代码...
// 回发ack,第二个参数multiple,表示是否批量确认,具体请百度,不赘述
channel.basicAck(messages.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 回发Nack,且重回队列为false,防死循环
channel.basicNack(messages.getMessageProperties().getDeliveryTag(), false, false);
}
}
此处有需要,可以阅读另一篇博文:RabbitMQ 消费者确认auto 和 manual 模式对异常的处理区别(含重试、requeue的影响)
本文完,有误欢迎指出。