解决问题
1.队列消息积压
2.消息丢失,IO波动导致消费失败缺乏重试机制
RabbitMq队列配置
/**
* RabbitMq配置
* @author ChenYu ren
* @date 2024/9/3
*/
@Configuration
public class TestRabbitMqConfig {
/**
* 交换机名称
*/
public static final String EXCHANGE_NAME = "TestExchange";
/**
* pushMessage to exchange route queue 【RoutingKey】
*/
public static final String ROUTING_KEY = "TestRk";
/**
* 队列名称
*/
public static final String QUEUE = "TestQueue";
@Bean("testQueue")
public Queue queue() {
//第二个参数表示是否持久化
return new Queue(QUEUE, true);
}
@Bean("testExchange")
public DirectExchange directExchange() {
return new DirectExchange(EXCHANGE_NAME);
}
/**
* 交换机与队列绑定
* @return 绑定关系
*/
@Bean("queueBindExchange")
public Binding healthyBindStaExchange(@Qualifier("testExchange")DirectExchange exchange,
@Qualifier("testQueue")Queue queue) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
@Bean("testRabbitListenerContainerFactory")
public RabbitListenerContainerFactory<SimpleMessageListenerContainer> rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 启用批量消费
factory.setBatchListener(true);
//用于在容器中创建消息批处理
factory.setConsumerBatchEnabled(true);
// 批量大小
factory.setBatchSize(3);
// 手动确认
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
//接收等待时长超时时间设置
//1.我们设置了BatchSize=3,如果等待了30s还消息数量<3,则会直接给消费者推送
factory.setReceiveTimeout(30 * 1000L);
return factory;
}
}
消费者(Consumer)
/**
* RabbitMq消费者
* @author ChenYu ren
* @date 2024/9/3
*/
@Slf4j
@Component
public class TestRabbitMqConsumer {
@Resource
private AmqpTemplate rabbitTemplate;
@RabbitListener(queues = TestRabbitMqConfig.QUEUE,containerFactory = "testRabbitListenerContainerFactory")
public void consumerMessage(Channel channel, List<Message> messageList) {
messageList.forEach(message -> {
String messageJson = new String(message.getBody(), StandardCharsets.UTF_8);
try {
log.info("consumer begin message -> {}",messageJson);
//业务处理
handleMessage(messageJson);
// 消息处理完成ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
try {
log.error("消息消费失败,开始重试或入库人工介入 message -> {}, errorMsg -> {}",messageJson,e.getMessage());
Integer retryCount = (Integer) message.getMessageProperties().getHeaders().getOrDefault("x-retry-count", 0);
retryCount = retryCount + 1;
message.getMessageProperties().getHeaders().put("x-retry-count", retryCount);
if (retryCount >= 3) {
//将消息发送的死信队列 | 入库报警人工介入
sendDeadQueueOrSaveLog(messageJson);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}else {
log.error("消费失败,发布消息重试准备第{}次重试 message ->{}",retryCount,messageJson);
//重试次数重置,拒绝消息等待重新消费
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
}
}catch (Exception exception){
log.error("消息消费失败,重新发送消息失败,或入库失败 message -> {} errorMsg -> {}",messageJson,exception.getMessage());
}
}
});
}
/**
* 处理消息
* @param message 消息
*/
private void handleMessage(String message){
JSONObject jsonObject = JSONObject.parseObject(message);
if (jsonObject.getInteger("userId") == 1){
throw new CustomException("模拟业务异常");
}
log.info("handleMessage consumer success message -> {} ",message);
}
/**
* 消息消费重试次数达到最大上限,发送到死信队列或入库人工介入
* @param message 消息
*/
private void sendDeadQueueOrSaveLog(String message){
log.info("消息消费失败,重试次数达到上限。写入死信队列或入库 成功 message -> {} ",message);
}
}
存在问题: 死循环
原因: 消息的
x-retry-count
标记没有被正确更新,这导致每次从队列中取出消息时,重试计数(retryCount
)总是从 0 开始。这是因为 RabbitMQ 并不会自动将消息的属性(例如自定义头x-retry-count
)持久化到消息队列中。原因分析: RabbitMQ 的消息头信息(包括你添加的
x-retry-count
)在消息被重新入队(requeue = true
)时,不会被修改或更新。这意味着每次你重新入队消息时,x-retry-count
的值不会被保留。
输出:
2024-09-14 16:24:18.484 INFO 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 16:24:18.491 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 16:24:18.492 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消费失败,发布消息重试准备第1次重试 message ->{"userId":1}
2024-09-14 16:24:21.518 INFO 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 16:24:21.526 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 16:24:21.527 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消费失败,发布消息重试准备第1次重试 message ->{"userId":1}
2024-09-14 16:24:24.551 INFO 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 16:24:24.551 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 16:24:24.552 ERROR 53942 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消费失败,发布消息重试准备第1次重试 message ->{"userId":1}
消费者(Consumer) - 改进
/**
* RabbitMq消费者
* @author ChenYu ren
* @date 2024/9/3
*/
@Slf4j
@Component
public class TestRabbitMqConsumer {
@Resource
private AmqpTemplate rabbitTemplate;
@RabbitListener(queues = TestRabbitMqConfig.QUEUE,containerFactory = "testRabbitListenerContainerFactory")
public void consumerMessage(Channel channel, List<Message> messageList) {
messageList.forEach(message -> {
String messageJson = new String(message.getBody(), StandardCharsets.UTF_8);
try {
log.info("consumer begin message -> {}",messageJson);
//业务处理
handleMessage(messageJson);
// 消息处理完成ack
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
try {
log.error("消息消费失败,开始重试或入库人工介入 message -> {}, errorMsg -> {}",messageJson,e.getMessage());
Integer retryCount = (Integer) message.getMessageProperties().getHeaders().getOrDefault("x-retry-count", 0);
retryCount = retryCount + 1;
message.getMessageProperties().getHeaders().put("x-retry-count", retryCount);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
if (retryCount >= 3) {
//将消息发送的死信队列 | 入库报警人工介入
sendDeadQueueOrSaveLog(messageJson);
}else {
log.error("消费失败,发布消息重试准备第{}次重试 message ->{}",retryCount,messageJson);
//重试次数重置,消息发到队尾等待重新消费
rabbitTemplate.convertAndSend(TestRabbitMqConfig.EXCHANGE_NAME,TestRabbitMqConfig.ROUTING_KEY,message);
}
}catch (Exception exception){
log.error("消息消费失败,重新发送消息失败,或入库失败 message -> {} errorMsg -> {}",messageJson,exception.getMessage());
}
}
});
}
/**
* 处理消息
* @param message 消息
*/
private void handleMessage(String message){
JSONObject jsonObject = JSONObject.parseObject(message);
if (jsonObject.getInteger("userId") == 1){
throw new CustomException("模拟业务异常");
}
log.info("handleMessage consumer success message -> {} ",message);
}
/**
* 消息消费重试次数达到最大上线,发送到死信队列或入库人工介入
* @param message 消息
*/
private void sendDeadQueueOrSaveLog(String message){
log.info("消息消费失败,重试次数达到上限。写入死信队列或入库 成功 message -> {} ",message);
}
}
输出:
2024-09-14 15:59:14.203 INFO 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 15:59:14.207 ERROR 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 15:59:14.209 ERROR 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消费失败,发布消息重试准备第1次重试 message ->{"userId":1}
2024-09-14 15:59:17.235 INFO 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 15:59:17.237 ERROR 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 15:59:17.239 ERROR 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消费失败,发布消息重试准备第2次重试 message ->{"userId":1}
2024-09-14 15:59:20.280 INFO 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : consumer begin message -> {"userId":1}
2024-09-14 15:59:20.282 ERROR 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,开始重试或入库人工介入 message -> {"userId":1}, errorMsg -> 模拟业务异常
2024-09-14 15:59:20.293 INFO 52966 --- [tContainer#12-1] c.q.p.s.h.consumer.TestRabbitMqConsumer : 消息消费失败,重试次数达到上线。写入死信队列或入库 成功 message -> {"userId":1}