方案一:spring提供的retry
配置文件:
server: port:8080 spring: rabbitmq: host: xxx.xxx.xxx.xxx port: 5672 username: xxxx password: xxx publisher-confirm-type: correlated listener: simple: acknowledge-mode: manual #开启手动确认 retry: enabled: true max-attempts: 5 #重试次数 initial-interval: 5000 #重试间隔时间(单位毫秒) max-interval: 10000 #重试最大时间间隔(单位毫秒) stateless: true
错误的例子:
**开了失败重试后,在消失消费失败时,不能使用否定确认,会陷入死循环(重试机制就不生效了)**
消息消费失败了,消息回退到queue -> queue再次下发消息 -> 消费失败->消息回退到queue -> queue再次下发消息...
@RabbitListener(queues = HISTORY_QUEUE) public void receiveMsg1(Message message, Channel channel) { message.getMessageProperties(); String msg = new String(message.getBody()); LOGGER.info(HISTORY_QUEUE + " 接受到消息1:{}", msg); try { if ("test".equals(msg)) { throw new RuntimeException(); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工确认,可接下一条 LOGGER.info(HISTORY_QUEUE + " 消费成功1"); } catch (Exception e) { LOGGER.info(HISTORY_QUEUE + " 消费失败1,param:{}", msg, e.getStackTrace()); try { // channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);//失败,则直接忽略此消息 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);//失败,将消息放回队列 // channel.basicRecover(true); //消息将被重新排队,并可能被传递给不同的消费者。 } catch (IOException ioException) { LOGGER.error(HISTORY_QUEUE + " 消息反馈失败1,param:{}", msg, ioException.getStackTrace()); } } }
正确的用法:
**只确认消息消费正确的消息,消费异常的不处理;**
为什么用了try,还要手动抛异常?因为我想拿日志
@RabbitListener(queues = HISTORY_QUEUE) public void receiveMsg1(Message message, Channel channel) { message.getMessageProperties(); String msg = new String(message.getBody()); LOGGER.info(HISTORY_QUEUE + " 接受到消息1:{}", msg); try { if ("test".equals(msg)) { throw new RuntimeException(); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);//手工确认,可接下一条 LOGGER.info(HISTORY_QUEUE + " 消费成功1"); } catch (Exception e) { LOGGER.info(HISTORY_QUEUE + " 消费失败1,param:{}", msg, e.getMessage()); throw new RuntimeException(); // try { //// channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);//失败,则直接忽略此消息 // channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);//失败,将消息放回队列 // channel.basicRecover(true); //消息将被重新排队,并可能被传递给不同的消费者。 // } catch (IOException ioException) { // LOGGER.error(HISTORY_QUEUE + " 消息反馈失败1,param:{}", msg, ioException.getStackTrace()); // } } }
方案2:死信交换机和消息头
需求:
发送消息推送广告,通过mq解耦;mq失败后重发三次,三次失败就丢弃消息;
配置:
server: port:8080 spring: rabbitmq: host: xxx.xxx.xxx.xxx port: 5672 username: xxxx password: xxx publisher-confirm-type: correlated listener: simple: acknowledge-mode: manual #开启手动确认
package com.example.demo.config; import org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_EXCHANGE; import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_ROUTING_KEY; @Configuration public class AgainMagSedConfig { public static final String AGAIN_QUEUE = "again.queue"; public static final String AGAIN_EXCHANGE = "again.exchange"; public static final String AGAIN_ROUTING_KEY = "again.routing.key"; @Bean public Queue againQueue() { Map<String, Object> map = new HashMap<>(); //正常队列设置死信交换机 参数 key 是固定值 map.put("x-dead-letter-exchange", DEAD_EXCHANGE); //正常队列设置死信 routing-key 参数 key 是固定值 map.put("x-dead-letter-routing-key", DEAD_ROUTING_KEY); return new Queue(AGAIN_QUEUE, true, false, false, map); } @Bean public Exchange againExchange() { return new DirectExchange(AGAIN_EXCHANGE, true, false); } @Bean public Binding againRoutingKey(Queue againQueue, Exchange againExchange) { return BindingBuilder.bind(againQueue).to(againExchange).with(AGAIN_ROUTING_KEY).noargs(); } public static final String DEAD_QUEUE = "dead.queue"; public static final String DEAD_EXCHANGE = "dead.exchange"; public static final String DEAD_ROUTING_KEY = "dead.routing.key"; @Bean public Queue deadQueue() { return new Queue(DEAD_QUEUE, true); } @Bean public Exchange deadExchange() { return new DirectExchange(DEAD_EXCHANGE, true, false); } @Bean public Binding deadRoutingKey(Queue deadQueue, Exchange deadExchange) { return BindingBuilder.bind(deadQueue).to(deadExchange).with(DEAD_ROUTING_KEY).noargs(); } }
回调方法:
package com.example.demo.config; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; @Component public class HistoryCallBack implements RabbitTemplate.ConfirmCallback { private static final Logger LOGGER = LoggerFactory.getLogger(HistoryCallBack.class); /** * 交换机不管是否收到消息的一个回调方法 * * @param correlationData CorrelationData 回调的信息对象 * @param ack 交换机是否成功接收消息 * @param cause 交换机没收到消息的原因 */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { String id = correlationData != null ? correlationData.getId() : ""; if (ack) { LOGGER.info("exchange receive msg, id:{}", id); } else { LOGGER.info("exchange not received msg, id:{},cause:{}", id, cause); } } }
消息生产者:
package com.example.demo.controller; import com.example.demo.config.HistoryCallBack; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import static com.example.demo.config.AgainMagSedConfig.AGAIN_EXCHANGE; import static com.example.demo.config.AgainMagSedConfig.AGAIN_ROUTING_KEY; @RestController @RequestMapping("/") public class HelloController { @Autowired private RabbitTemplate rabbitTemplate; @Autowired private HistoryCallBack historyCallBack; //依赖注入 rabbitTemplate 之后再设置它的回调对象 @PostConstruct public void init() { rabbitTemplate.setConfirmCallback(historyCallBack); } @GetMapping("/hello/{msg}") public Boolean sendMq(@PathVariable("msg") String msg) { //定义发布回调的id CorrelationData correlationData = new CorrelationData(msg); MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) { message.getMessageProperties().setHeader("num", 1); return message; } }; rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, msg.getBytes(StandardCharsets.UTF_8), messagePostProcessor); // rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, msg.getBytes(StandardCharsets.UTF_8), messagePostProcessor, correlationData); return true; } }
消费者:
package com.example.demo.mqlistener; import com.rabbitmq.client.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessagePostProcessor; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.util.Map; import static com.example.demo.config.AgainMagSedConfig.*; import static com.example.demo.config.HistoryDirectRabbitConfig.DEAD_QUEUE; @Component public class AgainListener { private static final Logger LOGGER = LoggerFactory.getLogger(AgainListener.class); @Autowired private RabbitTemplate rabbitTemplate; /** * 消费队列 * @param channel * @param message * @throws IOException */ @RabbitListener(queues = AGAIN_QUEUE) public void acceptAgainQueue(Channel channel, Message message) throws IOException { String s = new String(message.getBody()); Map<String, Object> headers = message.getMessageProperties().getHeaders(); LOGGER.info("队列:{},接受的消息是:{},请求头的num是:{}", AGAIN_QUEUE, s, headers.get("num")); if (s.equals("test")) { channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false); } else { channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } } /** * 死信队列 * @param channel * @param message * @throws IOException */ @RabbitListener(queues = DEAD_QUEUE) public void acceptDeadQueue(Channel channel, Message message) throws IOException { String s = new String(message.getBody()); Map<String, Object> headers = message.getMessageProperties().getHeaders(); LOGGER.info("死信队列:{},接受的消息是:{},请求头的num是:{}", DEAD_QUEUE, s, headers.get("num")); Integer num = (Integer) headers.get("num"); final Integer num1 = ++num; if (num <= 3) { MessagePostProcessor messagePostProcessor = new MessagePostProcessor() { @Override public Message postProcessMessage(Message message) { message.getMessageProperties().setHeader("num", num1); return message; } }; rabbitTemplate.convertAndSend(AGAIN_EXCHANGE, AGAIN_ROUTING_KEY, s, messagePostProcessor); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } }
效果: