Springboot集成RabbitMQ

RabbitMQ配置

引入依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

application.yml配置

yaml 复制代码
spring:
  rabbitmq:
    host: 42.194.132.44
    port: 5672
    username: guest
    password: guest

RabbitmqConfig配置

java 复制代码
package com.angel.ocean.rabbitmq.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class RabbitmqConfig implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * 单条消费
     */
    @Bean(name = "singleContainerFactory")
    public SimpleRabbitListenerContainerFactory singleContainerFactory() {
        SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
        containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
        containerFactory.setMaxConcurrentConsumers(1);
        containerFactory.setConcurrentConsumers(1);
        containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        containerFactory.setConsumerBatchEnabled(false);
        containerFactory.setBatchListener(false);
        containerFactory.setAutoStartup(false);
        containerFactory.setPrefetchCount(100);
        return containerFactory;
    }

    /**
     * 批量消费
     */
    @Bean(name = "batchContainerFactory")
    public SimpleRabbitListenerContainerFactory batchContainerFactory() {
        SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
        containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
        containerFactory.setMaxConcurrentConsumers(1);
        containerFactory.setConcurrentConsumers(1);
        containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
        containerFactory.setBatchListener(true);
        containerFactory.setConsumerBatchEnabled(true);
        containerFactory.setBatchSize(50);
        containerFactory.setAutoStartup(false);
        containerFactory.setPrefetchCount(100);
        return containerFactory;
    }
}

RabbitMQ 队列声明、启动消费者设置

java 复制代码
package com.angel.ocean.rabbitmq.runner;

import com.angel.ocean.rabbitmq.util.RabbitmqUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;

@Slf4j
@Component
public class RabbitmqQueueRunner implements CommandLineRunner {

    @Resource
    private AmqpAdmin amqpAdmin;

    @Resource
    private RabbitListenerEndpointRegistry endpointRegistry;

    @Override
    public void run(String... args) throws Exception {
        log.info("动态创建MQ配置信息...");
        RabbitmqUtil.declareRabbitmqQueue(amqpAdmin, "ocean");
        // 启动消费者
        endpointRegistry.start();
    }
}

RabbitmqUtil 工具类

java 复制代码
package com.angel.ocean.rabbitmq.util;

import org.springframework.amqp.core.*;

public class RabbitmqUtil {

    /**
     * 交换机
     */
    public static Exchange getExchange(String key) {
        String name = key + "_exchange";
        return new DirectExchange(name, true, false);
    }

    /**
     * 队列
     */
    public static Queue getQueue(String key) {
        String name = key + "_queue";
        return new Queue(name, true, false, false);
    }

    /**
     * 绑定
     */
    public static Binding getBinding(String key) {
        String routingKey = key + "_routing_key";
        return new Binding(getQueue(key).getName(), Binding.DestinationType.QUEUE,
                getExchange(key).getName(), routingKey, null);
    }

    /**
     * 创建RabbitMQ队列
     */
    public static void declareRabbitmqQueue(AmqpAdmin amqpAdmin, String key) {

        // 获取队列
        Queue queue = RabbitmqUtil.getQueue(key);

        // 获取交换机
        Exchange exchange = RabbitmqUtil.getExchange(key);

        // 绑定关系
        Binding binding = RabbitmqUtil.getBinding(key);

        // 创建队列
        amqpAdmin.declareQueue(queue);
        amqpAdmin.declareExchange(exchange);
        amqpAdmin.declareBinding(binding);
    }
}

RabbitMQ 使用验证

生产者

java 复制代码
package com.angel.ocean.rabbitmq;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson2.JSON;
import com.angel.ocean.rabbitmq.domain.UserInfo;
import com.angel.ocean.rabbitmq.service.RabbitmqSenderService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.Date;

@Slf4j
@SpringBootTest
class ApplicationTest {

    @Resource
    private RabbitmqSenderService rabbitmqSenderService;

    @Test
    void test() {
        for (int i = 0; i < 500; i++) {
            UserInfo userInfo = new UserInfo(i, "Name" + i, RandomUtil.randomString(32), new Date());
            rabbitmqSenderService.send(JSON.toJSONString(userInfo));
        }
    }
}
java 复制代码
package com.angel.ocean.rabbitmq.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class RabbitmqSenderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String message) {
        rabbitTemplate.convertAndSend("ocean_exchange", "ocean_routing_key", message);
    }
}

消费者

java 复制代码
package com.angel.ocean.rabbitmq.listener;

import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.List;

@Slf4j
@Component
public class RabbitmqListener {

    /**
     *  单条消费
     */
    @RabbitListener(queues = "ocean_queue", containerFactory = "singleContainerFactory")
    public void receive(Message message, Channel channel) throws IOException {

        try {
            log.info("Received data: {}", new String(message.getBody()));
            // 消费完成后,手动ACK
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            int sleepTime = RandomUtil.randomInt(2);
            Thread.sleep(sleepTime);
        } catch (IOException e) {
            // 处理消费失败的情况
            log.error("RabbitmqListener.receive() IOException, {}", e.getMessage(), e);
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        } catch (InterruptedException e) {
            log.error("RabbitmqListener.receive() InterruptedException, {}", e.getMessage(), e);
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }

    /**
     *  批量消费
     */
    @RabbitListener(queues = "ocean_queue", containerFactory = "batchContainerFactory")
    public void batchReceive(List<Message> messages, Channel channel) throws IOException {

        log.info("batchReceive, size:{}", messages.size());

        // 消费完成后,手动ACK
        for(Message message: messages) {
            try {
                // TODO 消费消息的逻辑
                log.info("batchReceive data: {}", new String(message.getBody()));
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            } catch (IOException e) {
                // 处理消费失败的情况
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            }
        }
    }
}

RabbitMQ 使用注意事项

  1. 为了保证数据一致性,设置RabbitMQ的消息确认机制ack为手动确认
java 复制代码
containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);

或者

  1. 如果要保证消息顺序消费,不能设置并发消费
java 复制代码
containerFactory.setConcurrentConsumers(1);

或者

如果设置成并发消费,消费结果如下图,可以看出消息消费出现了乱序。

  1. 批量消费设置,可以根据实际业务设置批次大小

1)批量消费设置

java 复制代码
/**
 * 批量消费
  */
 @Bean(name = "batchContainerFactory")
 public SimpleRabbitListenerContainerFactory batchContainerFactory() {
     SimpleRabbitListenerContainerFactory containerFactory = new SimpleRabbitListenerContainerFactory();
     containerFactory.setConnectionFactory(applicationContext.getBean(ConnectionFactory.class));
     containerFactory.setMaxConcurrentConsumers(1);
     containerFactory.setConcurrentConsumers(1);
     containerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
     containerFactory.setBatchListener(true);
     containerFactory.setConsumerBatchEnabled(true);
     containerFactory.setBatchSize(50);
     containerFactory.setAutoStartup(false);
     containerFactory.setPrefetchCount(100);
     return containerFactory;
 }

2)@RabbitListener配置,使用containerFactory = "batchContainerFactory(见RabbitmqConfig)

java 复制代码
@RabbitListener(queues = "ocean_queue", containerFactory = "batchContainerFactory")
public void batchReceive(List<Message> messages, Channel channel) throws IOException {

    log.info("batchReceive, size:{}", messages.size());

    // 消费完成后,手动ACK
    for(Message message: messages) {
        try {
            // TODO 消费消息的逻辑
            log.info("batchReceive data: {}", new String(message.getBody()));
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        } catch (IOException e) {
            // 处理消费失败的情况
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
        }
    }
}

验证结果如下图:
4. @RabbitListener先创建队列,再消费配置

如果使用默认配置,即设置containerFactory.setAutoStartup(true),而且队列又不存在,就会报如下错误:

java 复制代码
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'ocean_queue' in vhost '/', class-id=50, method-id=10)

1)设置消费者监听自动启动为false
2)服务启动后,启动消费者监听

相关推荐
上上迁3 小时前
分布式生成 ID 策略的演进和最佳实践,含springBoot 实现(Java版本)
java·spring boot·分布式
秋千码途3 小时前
小架构step系列07:查找日志配置文件
spring boot·后端·架构
seventeennnnn6 小时前
谢飞机的Java高级开发面试:从Spring Boot到分布式架构的蜕变之旅
spring boot·微服务架构·java面试·分布式系统·电商支付
超级小忍7 小时前
服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
java·spring boot·后端
时间会给答案scidag8 小时前
报错 400 和405解决方案
vue.js·spring boot
Wyc724098 小时前
SpringBoot
java·spring boot·spring
ladymorgana10 小时前
【Spring Boot】HikariCP 连接池 YAML 配置详解
spring boot·后端·mysql·连接池·hikaricp
GJCTYU11 小时前
spring中@Transactional注解和事务的实战理解附代码
数据库·spring boot·后端·spring·oracle·mybatis
风象南12 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
写不出来就跑路13 小时前
暑期实习感悟与经验分享:从校园到职场的成长之路
java·开发语言·经验分享·spring boot