项目中需要对操作记录进行保存,使用MQ做异步。在消费时遇到了消费异常导致消息不断重试的问题,所以针对此问题的解决方案做了记录。可以使用spring的rabbit配置项进行实现,本文主要在配置类中进行配置,可以更直观感受配置项是如何在代码中生效的,可以更加灵活、定制化使用。
1.mq配置类
@Configuration
@EnableAsync
@EnableRabbit
@Slf4j
public class MqConfig {
@Autowired
RabbitProperties properties;
@Bean
public ConnectionFactory rabbitConnectionFactory(@Value("${rabbit.uri}") String uriStr){
log.info("rabbit配置为:{}", uriStr);
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(URI.create(uriStr));
connectionFactory.setChannelCacheSize(200);
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
return rabbitAdmin;
}
@Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory){
SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory = new SimpleRabbitListenerContainerFactory();
simpleRabbitListenerContainerFactory.setConnectionFactory(connectionFactory);
simpleRabbitListenerContainerFactory.setConcurrentConsumers(Runtime.getRuntime().availableProcessors());
simpleRabbitListenerContainerFactory.setAdviceChain(
RetryInterceptorBuilder
.stateless()
.recoverer(new RejectAndDontRequeueRecoverer())
.retryOperations(rabbitRetryTemplate())
.build()
);
return simpleRabbitListenerContainerFactory;
}
@Bean
public RetryTemplate rabbitRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 设置监听(不是必须)
retryTemplate.registerListener(new RetryListener() {
@Override
public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
// 执行之前调用 (返回false时会终止执行)
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
// 重试结束的时候调用 (最后一次重试 )
}
@Override
public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
// 异常 都会调用
log.error("-----第{}次调用", retryContext.getRetryCount());
}
});
// 个性化处理异常和重试 (不是必须)
/* Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
//设置重试异常和是否重试
retryableExceptions.put(AmqpException.class, true);
//设置重试次数和要重试的异常
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(5,retryableExceptions);*/
retryTemplate.setBackOffPolicy(backOffPolicyByProperties());
retryTemplate.setRetryPolicy(retryPolicyByProperties());
return retryTemplate;
}
@Bean
public ExponentialBackOffPolicy backOffPolicyByProperties() {
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
long maxInterval = properties.getListener().getSimple().getRetry().getMaxInterval().getSeconds();
long initialInterval = properties.getListener().getSimple().getRetry().getInitialInterval().getSeconds();
double multiplier = properties.getListener().getSimple().getRetry().getMultiplier();
// 重试间隔
backOffPolicy.setInitialInterval(initialInterval * 1000);
// 重试最大间隔
backOffPolicy.setMaxInterval(maxInterval * 1000);
// 重试间隔乘法策略
backOffPolicy.setMultiplier(multiplier);
return backOffPolicy;
}
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
return rabbitTemplate;
}
@Bean
public SimpleRetryPolicy retryPolicyByProperties() {
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
int maxAttempts = properties.getListener().getSimple().getRetry().getMaxAttempts();
retryPolicy.setMaxAttempts(maxAttempts);
return retryPolicy;
}
}
2.配置类说明
-
在rabbitListenerContainerFactory中设置消息处理策略和重试操作
simpleRabbitListenerContainerFactory.setAdviceChain( RetryInterceptorBuilder .stateless() .recoverer(new RejectAndDontRequeueRecoverer()) .retryOperations(rabbitRetryTemplate()) .build() );
消息处理策略:
- ImmediateRequeueMessageRecoverer
- 立即重新返回队列
- 存在其他消费者,此消息可被其他消费者重试
- 消息会一直重试下去,需要自己处理,跳出重试循环
- RejectAndDonateQueueRecover
- 拒绝消息,并且不会重新入队
- 消息会被投递到死信交换器/队列,如果当前队列有相关配置
- 仅在本节点重试,直到失败
- RepublishMessageRecover
- 重新发布出去,且消息头会携带异常堆栈信息,x-exception
- 可以指定exchange,如果不指定将使用一些默认值
- 可以考虑异步处理失败消息
- ImmediateRequeueMessageRecoverer
-
重试机制
-
设置监听、重试间隔机制、重试机制
@Bean public RetryTemplate rabbitRetryTemplate() { RetryTemplate retryTemplate = new RetryTemplate(); // 设置监听(不是必须) retryTemplate.registerListener(new RetryListener() { @Override public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) { // 执行之前调用 (返回false时会终止执行) return true; } @Override public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) { // 重试结束的时候调用 (最后一次重试 ) } @Override public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) { // 异常 都会调用 log.error("-----第{}次调用", retryContext.getRetryCount()); } }); // 个性化处理异常和重试 (不是必须) /* Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>(); //设置重试异常和是否重试 retryableExceptions.put(AmqpException.class, true); //设置重试次数和要重试的异常 SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(5,retryableExceptions);*/ retryTemplate.setBackOffPolicy(backOffPolicyByProperties()); retryTemplate.setRetryPolicy(retryPolicyByProperties()); return retryTemplate; }
- 重试开始前回调
- 重试失败时回调
- 达到最大重试次数回调
-
设置重试间隔机制
@Bean public ExponentialBackOffPolicy backOffPolicyByProperties() { ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); long maxInterval = properties.getListener().getSimple().getRetry().getMaxInterval().getSeconds(); long initialInterval = properties.getListener().getSimple().getRetry().getInitialInterval().getSeconds(); double multiplier = properties.getListener().getSimple().getRetry().getMultiplier(); // 重试间隔 backOffPolicy.setInitialInterval(initialInterval * 1000); // 重试最大间隔 backOffPolicy.setMaxInterval(maxInterval * 1000); // 重试间隔乘法策略 backOffPolicy.setMultiplier(multiplier); return backOffPolicy; }
- 重试间隔
- 间隔时间幂因子
- 重试最大间隔
-
设置重试次数
@Bean public SimpleRetryPolicy retryPolicyByProperties() { SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(); int maxAttempts = properties.getListener().getSimple().getRetry().getMaxAttempts(); retryPolicy.setMaxAttempts(maxAttempts); return retryPolicy; }
-
后续补充说明mq接收消息功能的实现