spring-rabbitmq:connection&channel

本文详细介绍了RabbitMQ的高级配置策略,包括连接工厂的创建、消息模板的使用、消费者消息监听容器的配置,以及如何实现消息的确认、重试与死信队列处理。通过具体的代码示例,展示了生产者与消费者的实现方式,适用于高并发场景下的消息队列应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

不多说,先上代码,后面有空再具体写

Rabbitmq配置 

/**
 * RabbitMq配置
 * @author pig/xiaofa
 */
@Configuration
@Slf4j
public class RabbitmqConfig {

    @Value("${spring.rabbitmq.connection.limit:5}")
    private Integer connectionLimit;

    /**
     * 创建连接工厂:CacheMode=CONNECTION
     * @param properties    Spring配置文件配置的Rabbitmq属性
     * @param connectionNameStrategy    连接名的策略
     * @return  连接工厂
     */
    @Bean(name = "cacheConnectionFactory")
    public CachingConnectionFactory cacheConnectionFactory(
            RabbitProperties properties, ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = buildFactory(properties);
        factory.setConnectionLimit(connectionLimit);
        factory.setCacheMode(CachingConnectionFactory.CacheMode.CONNECTION);

        RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
        map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
        map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
        return factory;
    }

    /**
     * 创建连接工厂:CacheMode=CHANNEL
     * @param properties    Spring配置文件配置的Rabbitmq属性
     * @return  连接工厂
     */
    @Bean(name = "cacheChannelFactory")
    public ConnectionFactory cacheChannelFactory(RabbitProperties properties) {
        CachingConnectionFactory factory = buildFactory(properties);
        factory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL);
        return factory;
    }

    /**
     * 根据“cacheChannelFactory“创建消费者消息监听容器
     * @param configurer    容器配置对象
     * @param cacheChannelFactory   channel模式连接工厂
     * @return  监听容器
     */
    @Bean(name = "channelListenerContainerFactory")
    @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
    public SimpleRabbitListenerContainerFactory channelListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory cacheChannelFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, cacheChannelFactory);
        return factory;
    }

    /**
     * 使用“cacheConnectionFactory”创建RabbitTemplate
     * @param cacheConnectionFactory    connection模式连接工厂
     * @param properties    Spring配置文件配置的Rabbitmq属性
     * @return  消息模板
     */
    @Bean(name = "connectionTemplate")
    public RabbitTemplate connectionTemplate(ConnectionFactory cacheConnectionFactory, RabbitProperties properties) {
        RabbitTemplate rabbitTemplate=new RabbitTemplate(cacheConnectionFactory);
        // 使用jackson 消息转换器
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        rabbitTemplate.setEncoding("UTF-8");
        rabbitTemplate.setMandatory(properties.getTemplate().getMandatory());
        return rabbitTemplate;
    }

    /**
     * 创建连接工厂:根据Spring配置文件生成
     * @param properties    Spring配置文件配置的Rabbitmq属性
     * @param connectionNameStrategy    连接名策略
     * @return  连接工厂(CacheMode由配置文件决定)
     */
    @Bean(name = "connectionFactory")
    public CachingConnectionFactory connectionFactory(
            RabbitProperties properties, ObjectProvider<ConnectionNameStrategy> connectionNameStrategy) {
        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = buildFactory(properties);
        RabbitProperties.Cache.Connection connection = properties.getCache().getConnection();
        map.from(connection::getMode).whenNonNull().to(factory::setCacheMode);
        map.from(connection::getSize).whenNonNull().to(factory::setConnectionCacheSize);
        map.from(connectionNameStrategy::getIfUnique).whenNonNull().to(factory::setConnectionNameStrategy);
        return factory;
    }

    /**
     * 根据“cacheChannelFactory“创建消费者消息监听容器
     * @param configurer    容器配置对象
     * @param cacheChannelFactory   channel模式连接工厂
     * @return  监听容器
     */
    @Bean(name = "rabbitListenerContainerFactory")
    @ConditionalOnMissingBean(name = "rabbitListenerContainerFactory")
    @ConditionalOnProperty(prefix = "spring.rabbitmq.listener", name = "type", havingValue = "simple", matchIfMissing = true)
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            SimpleRabbitListenerContainerFactoryConfigurer configurer, ConnectionFactory cacheChannelFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        configurer.configure(factory, cacheChannelFactory);
        return factory;
    }

    /**
     * 创建RabbitTemplate
     * @param connectionFactory    连接工厂
     * @param properties    Spring配置文件配置的Rabbitmq属性
     * @return  消息模板
     */
    @Bean(name = "rabbitTemplate")
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, RabbitProperties properties) {
        RabbitTemplate rabbitTemplate=new RabbitTemplate(connectionFactory);
        // 使用jackson 消息转换器
        rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
        rabbitTemplate.setEncoding("UTF-8");
        rabbitTemplate.setMandatory(properties.getTemplate().getMandatory());
        return rabbitTemplate;
    }

    private CachingConnectionFactory buildFactory(RabbitProperties properties) {
        PropertyMapper map = PropertyMapper.get();
        CachingConnectionFactory factory = new CachingConnectionFactory();
        map.from(properties::determineAddresses).to(factory::setAddresses);
        map.from(properties::determineUsername).to(factory::setUsername);
        map.from(properties::determinePassword).to(factory::setPassword);
        map.from(properties::determineVirtualHost).whenNonNull().to(factory::setVirtualHost);
        map.from(properties::isPublisherConfirms).whenNonNull().to(factory::setPublisherConfirms);
        map.from(properties::isPublisherReturns).whenNonNull().to(factory::setPublisherReturns);

        RabbitProperties.Cache.Channel channel = properties.getCache().getChannel();
        map.from(channel::getSize).whenNonNull().to(factory::setChannelCacheSize);
        map.from(channel::getCheckoutTimeout).whenNonNull().as(Duration::toMillis).to(factory::setChannelCheckoutTimeout);
        return factory;
    }
}

生产者,使用connection模式,CacheModel=CONNECTION

/**
 * 生产者
 * @author: pig/xiaofa
 * @date: 2019-04-03 17:07
 */
@Component
@Slf4j
public class MessageProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(){
        SendNotifyMessageDTO dto = new SendNotifyMessageDTO();
        dto.setRequestId(UUID.randomUUID());
        dto.setMessage("hello world");
        dto.setNotifyUrl("http://localhost:8088/hello");
        //生成消息唯一标识,回调确认使用
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(RabbitMqConstants.PAYMENT_CENTER_EXCHANGE,
                RabbitMqConstants.PAYMENT_CENTER_ROUTE_KEY, dto, correlationData);
        try {
            CorrelationData.Confirm confirm = correlationData.getFuture().get(10, TimeUnit.SECONDS);
            if (confirm.isAck()) {
                log.info("MessageProducer消息发送到exchange成功,correlationDataId={}", correlationData.getId());
            } else {
                log.error("MessageProducer消息发送到exchange失败,原因: {}", confirm.getReason());
            }
        } catch (InterruptedException e) {
            log.error("MessageProducer消息发送到exchange中断异常", e);
        } catch (ExecutionException e) {
            log.error("MessageProducer消息发送到exchange执行异常", e);
        } catch (TimeoutException e) {
            log.error("MessageProducer消息发送到exchange超时异常", e);
        }
    }

}

消费者,使用channel模式,CacheMode=CHANNEL

/**
 * 消息消费者
 * @author pig/xiaofa
 * @date 2019-07-22 18:00
 */
@Component
@Slf4j
public class PayCallbackConsumer implements MessageRecoverer {

    @Value("${spring.rabbitmq.listener.simple.retry.max-attempts}")
    private Integer maxAttempts;

    private final static String X_RETRY_COUNT = "x_retry_count";

    /**
     * 消费Rabbitmq消息
     * @param channel   信道
     * @param message   消息体
     */
    @RabbitListener(bindings = {
        @QueueBinding(
            value = @Queue(
                value = RabbitMqConstants.PAYMENT_CENTER_QUEUE,
                arguments = {
                    //转发交换机
                    @Argument(name = RabbitMqConstants.X_DEAD_LETTER_EXCHANGE_ATTR, value = RabbitMqConstants.RETRY_EXCHANGE),
                    //转发路由
                    @Argument(name = RabbitMqConstants.X_DEAD_LETTER_ROUTING_KEY_ATTR, value = RabbitMqConstants.RETRY_DEAD_ROUTE_KEY),
                    //当前队列消息存活时间
                    @Argument(name = RabbitMqConstants.X_MESSAGE_TTL_ATTR, value = RabbitMqConstants.X_MESSAGE_TTL, type = "java.lang.Integer")
                }
             ),
            exchange = @Exchange(value = RabbitMqConstants.PAYMENT_CENTER_EXCHANGE),
            key = RabbitMqConstants.PAYMENT_CENTER_ROUTE_KEY
        )
    })
    public void receiveNotify(Channel channel, Message message) {
        long start = System.currentTimeMillis();
        String content = new String(message.getBody());
        log.info("--> receive notify message, message body: {}", content);
        try {
            //故意异常抛出
            int i = 3/0;
        } catch (Exception e) {
            this.reject(channel, message);
        }
        long end = System.currentTimeMillis();
        log.info("--> message processing is time-consuming:{}ms", (end-start));
    }

    /**
     * 重试队列
     * @param channel   信道
     * @param message   消息体
     */
    @RabbitListener(bindings = {
        @QueueBinding(
            value = @Queue(
                value = RabbitMqConstants.RETRY_QUEUE,
                arguments = {
                    //死信交换机
                    @Argument(name = RabbitMqConstants.X_DEAD_LETTER_EXCHANGE_ATTR, value = RabbitMqConstants.DEAD_EXCHANGE),
                    //死信路由
                    @Argument(name = RabbitMqConstants.X_DEAD_LETTER_ROUTING_KEY_ATTR, value = RabbitMqConstants.DEAD_ROUTE_KEY),
                    //当前队列消息存活时间
                    @Argument(name = RabbitMqConstants.X_MESSAGE_TTL_ATTR, value = RabbitMqConstants.X_MESSAGE_TTL, type = "java.lang.Integer")
                }
            ),
            exchange = @Exchange(value = RabbitMqConstants.RETRY_EXCHANGE),
            key = RabbitMqConstants.RETRY_ROUTE_KEY
        )
    })
    public void retryQueue(Channel channel, Message message) {
        int retryCount = 0;
        long start = System.currentTimeMillis();
        try {
            //获取消息头
            MessageProperties messageProperties = message.getMessageProperties();
            Map<String, Object> headers = messageProperties.getHeaders();
            if(headers != null && headers.containsKey(X_RETRY_COUNT)) {
                //获取已重试次数
                retryCount = (int) headers.get(X_RETRY_COUNT);
            }
            retryCount++;
            //更新重试次数
            messageProperties.setHeader(X_RETRY_COUNT, retryCount);
            String content = new String(message.getBody());
            this.doSomething(channel, message, content);
        } catch (Exception e) {
            if(retryCount > maxAttempts) {
                log.error("--> retry reaches maximum, retry count: {}, cause: {}",
                        retryCount, e.getMessage());
                this.reject(channel, message);
            } else {
                log.error("--> retry consume failure, retry count: {}, cause: {}",
                        retryCount, e.getMessage());
                throw e;
            }
        } finally {
            long end = System.currentTimeMillis();
            log.info("--> message retry processing is time-consuming:{}ms, retry count: {}", (end-start), retryCount);
        }
    }

    /**
     * 执行
     * @param channel   信道
     * @param message   消息体
     */
    private void doSomething(Channel channel, Message message, String content) {
        //模拟处理耗时
        try {
            Thread.sleep(150);
        } catch (InterruptedException e) {
            log.error("--> thread error", e);
        }
        this.ack(channel, message);
        log.info("--> message consumed successfully");
    }

    /**
     * 对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调用 <br/>
     * 注意:spring.rabbitmq.listener.retry配置的重发是在消费端应用内处理的,不是rabbitmq重发
     * @param message   消息体
     * @param cause     异常原因
     */
    @Override
    public void recover(Message message, Throwable cause) {
        log.error("--> retry reaches maximum, dropped into dead letter queue, message body: {}",
                new String(message.getBody()));
    }

    /**
     * ACK确认
     * @param channel   信道
     * @param message   消息体
     */
    private void ack(Channel channel, Message message) {
        try {
            //false只确认当前一个消息收到,true确认所有consumer获得的消息
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            throw RuntimeException(String.format("channel basicReject exception, message body: %s", new String(message.getBody()));
        }
    }

    /**
     * 拒绝消息
     * @param  channel  信道
     * @param message   消息体
     */
    private void reject(Channel channel, Message message) {
        try {
            //requeue=true,重新回到队列;requeue=false,丢弃/死信
            channel.basicReject(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            throw RuntimeException(String.format("channel basicReject exception, message body: %s", new String(message.getBody()));
        }
    }

}

配置文件:

##RabbitMq配置
spring.rabbitmq.addresses=127.0.0.1:5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
#生产者开启回调确认
spring.rabbitmq.publisher-confirms=true
#开启发送失败退回
spring.rabbitmq.publisher-returns=false
#消息route到队列失败时,是否将return给发送者,一般配合publisher-returns使用
spring.rabbitmq.template.mandatory=false
#虚拟空间
spring.rabbitmq.virtual-host=/
#开启ACK
spring.rabbitmq.listener.simple.acknowledge-mode=manual
#指定最小的消费者数量
spring.rabbitmq.listener.simple.concurrency=60
#指定最大的消费者数量
spring.rabbitmq.listener.simple.max-concurrency=120
#是否支持重试
spring.rabbitmq.listener.simple.retry.enabled=true
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#重试时间间隔
spring.rabbitmq.listener.simple.retry.initial-interval=2000
#消费者每次预取消息数,类似缓冲区
spring.rabbitmq.listener.simple.prefetch=300
#当前最大允许空闲的最大channel数,当遇到connetion error的错误时,就可以考虑增大channel cache size
spring.rabbitmq.cache.channel.size=500
#channel缓存超时时间
spring.rabbitmq.cache.channel.checkout-timeout=2000
#缓存模式
spring.rabbitmq.cache.connection.mode=connection
#当前最大允许空闲的最大connection数
spring.rabbitmq.cache.connection.size=5
#连接数限制,默认:5
spring.rabbitmq.connection.limit=5

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值