1. 什么时候应该用MQ?2. Rabbit 基础知识3. docker 安装 RabbitMQ4. Springboot (四) RabbitMQ入门

Springboot (四) RabbitMQ消费者重试机制(spring retry实现)

项目下载地址 Springboot-RabbitMQ-demo

1. 新建配置类 ,上代码

重点在于,retryTemplate 设置重试 。

@Slf4j
@Configuration
public class RabbitRetryConfig {

    @Autowired
    ConnectionFactory rabbitConnectionFactory;

    //@Bean  缓存连接池
    //public CachingConnectionFactory rabbitConnectionFactory

    @Autowired
    RabbitProperties properties;
    
    // 存在此名字的bean 自带的容器工厂会不加载,如果想自定义来区分开 需要改变bean 的名称
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory() {
        SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
        containerFactory.setConnectionFactory(rabbitConnectionFactory);
        // 并发消费者数量
        containerFactory.setConcurrentConsumers(1);
        containerFactory.setMaxConcurrentConsumers(20);
        // 自动应答
        containerFactory.setAcknowledgeMode(AcknowledgeMode.AUTO);
        //containerFactory.setMessageConverter(new Jackson2JsonMessageConverter());
        containerFactory.setChannelTransacted(true);
        containerFactory.setAdviceChain(
                RetryInterceptorBuilder
                        .stateless()
                        .recoverer(new RejectAndDontRequeueRecoverer())
                        .retryOperations(rabbitRetryTemplate())
                        .build()
        );
        return containerFactory;
    }

    @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;
    }

}

设计思路,实现消费者拦截器的容器工厂,增加 重试类 来设置重试参数。
本例子,获取了Rabbitproperties 的配置,也可以自定义。 消息在被消费者获取后,重试3次后,将会过期,我设置了死信队列,将重试处理不了的消息 了 路由到了死信队列中 。死信队列的消息需要人工干预处理。

2. 实现例子

我们要新增一个 正常的交换机 (exchangge_normal
和 死信交换机 (exchangge_failure),绑定正常队列 (queue_normal)和死信队列(queue_failure)

开始动手使用RabbitMQ 管理页面 创建队列和交换机, routingkey 统一用 key来表示 。

队列:

正常队列 需要设置 ”x-dead-letter-exchange”参数

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_spring


死信队列 :

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_spring_02

交换机:

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_自定义_03


Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_ide_04

交换机和队列关系绑定好之后,我们上一个简单的消费者代码,实现一个简单的异常,让消息无法正常消费。
/**
     * 接受指定队列的消息
     */
    @RabbitListener(queues = {"queue_normal"})
    public void consumer2(Message message, Channel channel)  {
        // 消息内容
        String body = new String(message.getBody());
        log.error(body);
        int i = 1 / 0;
    }
3. 测试代码:

启动消费者进行监听消息,手动在Rabbit管理页面发送一条消息。

队列初始化状态,一条待消费消息。

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_spring_05


启动消费者,控制台打印日志,因为我在监听代码中加了次数日志;

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_spring_06


查看队列消息数量变化,消息被转发到了死信队列

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_ide_07


我们去看看消息的头信, 来源是正常消息队列。例子成功!

Springboot rabbitmq 配置自动重连 rabbitmq消费者自动重连_spring_08