SpringBoot RabbitMq 入门指南

394 阅读11分钟

摘要:本文简单介绍rabbitmq的几种消息模式的使用方式,让开发同学能够快速接入rabbitmq进行消息开发。案例采用的是springboot+rabbitmq方式。

简介

RabbitMQ是一个开源的消息代理软件,也被称为消息队列。它支持多种消息协议,并且能够为分布式系统提供异步消息传递功能。RabbitMQ使用高级消息队列协议(AMQP)0-9-1,同时也支持其他协议,如STOMP、MQTT等。

RabbitMQ的主要特点包括:

  • 可靠性:确保消息能够被正确地发送和接收。
  • 灵活性:支持多种消息协议和多种编程语言的客户端。
  • 可扩展性:可以水平扩展以处理大量消息。
  • 支持多种交换类型:如直接交换、主题交换、扇出交换和头交换等。
  • 管理界面:提供了一个基于Web的简单管理界面,方便监控和管理。

RabbitMQ广泛应用于微服务架构中,用于解耦服务之间的通信,提高系统的可维护性和可扩展性。

基本概念

RabbitMQ是一个流行的开源消息代理,它基于AMQP协议,提供了一个可靠的消息队列服务。以下是RabbitMQ中的一些基本概念:

  • 生产者(Producer):消息的发送方,负责将消息发送到RabbitMQ。

  • 消费者(Consumer):消息的接收方,从RabbitMQ接收消息并进行处理。

  • 消息(Message):在RabbitMQ中传输的数据,它由消息头和消息体组成。

  • 队列(Queue):消息的存储地,生产者发送的消息会被放入队列中,消费者从队列中获取消息。

  • 交换器(Exchange):接收来自生产者的消息,并将消息根据路由规则发送到一个或多个队列。RabbitMQ支持多种类型的交换器,例如:

  1. 直接交换器(Direct Exchange):根据路由键将消息路由到具有匹配路由键的队列。
  2. 主题交换器(Topic Exchange):根据路由键和模式匹配将消息路由到队列。
  3. 扇出交换器(Fanout Exchange):将消息发送到所有绑定的队列,不考虑路由键。
  4. 头交换器(Headers Exchange):根据消息的头属性进行路由。
  • 绑定(Binding):交换器和队列之间的虚拟连接,它定义了消息如何从交换器路由到队列。

  • 虚拟主机(Vhost):RabbitMQ服务器的命名空间,拥有自己的队列、交换器和绑定,可以看作是消息的隔离环境。

  • 连接(Connection):客户端与RabbitMQ服务器之间的TCP连接。

  • 通道(Channel):在客户端和RabbitMQ之间的虚拟连接,大多数RabbitMQ操作都在通道上进行,如声明队列、发送和接收消息等。

  • 持久性(Durability):消息或队列的持久性设置,确保消息或队列在RabbitMQ服务器重启后依然存在。

  • 确认(Acknowledgement):消费者接收并处理消息后,向RabbitMQ发送的确认信号,表示消息已被成功处理。

  • 死信队列(Dead Letter Exchange):当消息无法被正常处理时,会被发送到死信队列,(现在用得很少了)。

常用队列使用案例

项目准备

pom.xml

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
    </dependencies>

RabbitmqApplication

@SpringBootApplication
public class RabbitmqApplication {

    public static void main(String[] args) {
        SpringApplication.run(RabbitmqApplication.class, args);
    }

}

点对点(单队列方式)

该方式只需要我们定义一个队列,然后向队列中发送消息即可,然后消费者监听该队列。

image.png

SampleConfig

该类定义队列名称

@Configuration
public class SampleConfig {

    public static final String QUEUE = "SAMPLE_QUEUE";

    @Autowired
    private AmqpAdmin amqpAdmin;

    @Bean(QUEUE)
    public Queue sampleQueue(){
        return new Queue(QUEUE);
    }

    public static final String QUEUE2 = "SAMPLE_QUEUE2";

    @Bean(QUEUE2)
    public Queue sampleQueue2(){
        return new Queue(QUEUE2);
    }
}

SampleProducer

生产者

@Component
public class SampleProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String message){
        rabbitTemplate.convertAndSend(SampleConfig.QUEUE, message);
        rabbitTemplate.convertAndSend(SampleConfig.QUEUE2, message);
    }

}

SampleConsumer

消费者,我们也可以定义多个消费者,监听同一个队列,这样就进行了负载均衡,消息会按照顺序消费,生产过程中一般也是这样使用的(只不过是利用的线程池来消费)。

@Slf4j
@Component
public class SampleConsumer {

    @RabbitListener(queues = SampleConfig.QUEUE)
    public void fire(String message){
        log.info("收到消息" + message);
    }

    @RabbitListener(queues = SampleConfig.QUEUE)
    public void fire1(String message){
        log.info("收到消息1" + message);
    }
}

TestController

测试调用者

@RestController
@RequestMapping(value = "test")
public class TestController {

    @Autowired
    private SampleProducer sampleProducer;

    @GetMapping(value = "sampleProducer")
    public Object sampleProducer(){
        sampleProducer.send("sampleProducer" + System.currentTimeMillis());
        return "SUCCESS";
    }

}

发布订阅模式(fanout

该模式就像是订阅新闻一样,我喜欢这个新闻,我订阅它,当它更新的时候就会给你推送最新的新闻;该模式需要引入交换机概念。

image.png

FanoutConfig配置类

@Configuration
public class FanoutConfig {

    public static final String QUEUE_ZS = "collect_zs";

    public static final String QUEUE_LS = "collect_ls";

    public static final String EXCHANGE_COLLECT = "exchange_collect";

    /**
     * 定义交换机
     * @return
     */
    @Bean
    public FanoutExchange fanoutExchangeCollect(){
        return new FanoutExchange(EXCHANGE_COLLECT);
    }

    /**
     * 定义队列
     * @return
     */
    @Bean
    public Queue queueCollectZs(){
        return new Queue(QUEUE_ZS);
    }

    /**
     * 定义队列
     * @return
     */
    @Bean
    public Queue queueCollectLs(){
        return new Queue(QUEUE_LS);
    }

    /**
     * 绑定队列到交换机
     * @param fanoutExchangeCollect
     * @param queueCollectZs
     * @return
     */
    @Bean
    public Binding bindingZs(FanoutExchange fanoutExchangeCollect, Queue queueCollectZs){
        return BindingBuilder.bind(queueCollectZs).to(fanoutExchangeCollect);
    }

    /**
     * 绑定队列到交换机
     * @param fanoutExchangeCollect
     * @param queueCollectLs
     * @return
     */
    @Bean
    public Binding bindingLs(FanoutExchange fanoutExchangeCollect, Queue queueCollectLs){
        return BindingBuilder.bind(queueCollectLs).to(fanoutExchangeCollect);
    }

}

FanoutProducer生产者

@Service
public class FanoutProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * 路由键设置为空字符串
     * @param message
     */
    public void send(String message){
        rabbitTemplate.convertAndSend(FanoutConfig.EXCHANGE_COLLECT, "", message);
    }

}

FanoutConsumer消费者

@Slf4j
@Service
public class FanoutConsumer {

    @RabbitListener(queues = FanoutConfig.QUEUE_ZS)
    public void fireZs(String message){
        log.info("张三收到消息" + message);
    }

    @RabbitListener(queues = FanoutConfig.QUEUE_LS)
    public void fireLs(String message){
        log.info("李四收到消息" + message);
    }
}

TestController测试入口

@RestController
@RequestMapping(value = "test")
public class TestController {


    @Autowired
    private FanoutProducer fanoutProducer;

    @GetMapping(value = "fanoutProducer")
    public Object fanoutProducer(String message){
        fanoutProducer.send("fanoutProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

}

结果

2024-07-11 09:25:19.192  INFO 1572 --- [ntContainer#0-1] c.h.rabbitmq.demo.fanout.FanoutConsumer  : 张三收到消息fanoutProducer:你好1720661119173
2024-07-11 09:25:19.193  INFO 1572 --- [ntContainer#1-1] c.h.rabbitmq.demo.fanout.FanoutConsumer  : 李四收到消息fanoutProducer:你好1720661119173
2024-07-11 09:25:24.485  INFO 1572 --- [ntContainer#0-1] c.h.rabbitmq.demo.fanout.FanoutConsumer  : 张三收到消息fanoutProducer:你好a1720661124482
2024-07-11 09:25:24.492  INFO 1572 --- [ntContainer#1-1] c.h.rabbitmq.demo.fanout.FanoutConsumer  : 李四收到消息fanoutProducer:你好a1720661124482

路由选择模式(direct

该模式下我们只需要关注我们关心的消息,比如系统发送的日志,普通的日志收集器则需要关注error、info、warning,但是报警服务则只需要关注error级别的日志,下面给出使用案例。

image.png

DirectConfig配置类

@Configuration
public class DirectConfig {

    public static final String QUEUE_LOG_ALL = "queue_log_all";

    public static final String QUEUE_LOG_ERROR = "queue_log_error";

    public static final String EXCHANGE_LOG = "log";

    public static final String ROUTING_KEY_INFO = "info";

    public static final String ROUTING_KEY_ERROR = "error";

    /**
     * 定义交换机
     * @return
     */
    @Bean
    public DirectExchange directExchangeLog(){
        return new DirectExchange(EXCHANGE_LOG);
    }

    @Bean
    public Queue queueLogAll(){
        return new Queue(QUEUE_LOG_ALL);
    }

    @Bean
    public Queue queueLogError(){
        return new Queue(QUEUE_LOG_ERROR);
    }

    /**
     * 绑定info的路由键到all队列
     * @param queueLogAll
     * @param directExchangeLog
     * @return
     */
    @Bean
    public Binding bindingInfoToAll(Queue queueLogAll, DirectExchange directExchangeLog){
        return BindingBuilder.bind(queueLogAll).to(directExchangeLog).with(ROUTING_KEY_INFO);
    }

    /**
     * 绑定error的路由键到all队列
     * @param queueLogAll
     * @param directExchangeLog
     * @return
     */
    @Bean
    public Binding bindingErrorToAll(Queue queueLogAll, DirectExchange directExchangeLog){
        return BindingBuilder.bind(queueLogAll).to(directExchangeLog).with(ROUTING_KEY_ERROR);
    }

    /**
     * 绑定error的路由键到error队列
     * @param queueLogError
     * @param directExchangeLog
     * @return
     */
    @Bean
    public Binding bindingErrorToError(Queue queueLogError, DirectExchange directExchangeLog){
        return BindingBuilder.bind(queueLogError).to(directExchangeLog).with(ROUTING_KEY_ERROR);
    }

}

DirectProducer生产者

@Service
public class DirectProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * @param message
     */
    public void send(String routingKey, String message){
        rabbitTemplate.convertAndSend(DirectConfig.EXCHANGE_LOG, routingKey, message);
    }

}

DirectConsumer消费者

@Slf4j
@Service
public class DirectConsumer {

    @RabbitListener(queues = DirectConfig.QUEUE_LOG_ALL)
    public void fireAll(String message){
        log.info("接收所有日志:" + message);
    }

    @RabbitListener(queues = DirectConfig.QUEUE_LOG_ERROR)
    public void fireError(String message){
        log.info("接收ERROR日志:" + message);
    }

}

TestController测试入口

@RestController
@RequestMapping(value = "test")
public class TestController {


    @Autowired
    private DirectProducer directProducer;

    @GetMapping(value = "directProducer")
    public Object directProducer(String routingKey, String message){
        directProducer.send(routingKey, "directProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

}

测试结果

2024-07-11 10:32:48.856  INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收所有日志:directProducer:info日志1720665168842
2024-07-11 10:32:54.364  INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收所有日志:directProducer:info日志1720665174363
2024-07-11 10:33:05.009  INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收所有日志:directProducer:error日志1720665185007
2024-07-11 10:33:05.010  INFO 20856 --- [ntContainer#0-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收ERROR日志:directProducer:error日志1720665185007
2024-07-11 10:33:08.103  INFO 20856 --- [ntContainer#0-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收ERROR日志:directProducer:error日志1720665188102
2024-07-11 10:33:08.103  INFO 20856 --- [ntContainer#1-1] c.h.rabbitmq.demo.direct.DirectConsumer  : 接收所有日志:directProducer:error日志1720665188102

通配符模式Topics

该模式其实和Direct模式差不多,只不过路由键可以配置为通配符,这样让我们配置更方便简单,在实际的业务场景中,我还没有遇到必须使用这种模式的。下面随便列了使用方式,匹配dog.*路由键和cat.*的路由键。

image.png

TopicsConfig 配置类

@Configuration
public class TopicsConfig {

    public static final String QUEUE_DOG = "dog";

    public static final String QUEUE_CAT = "cat";

    public static final String EXCHANGE_ANIMAL = "animal";

    public static final String ROUTING_KEY_DOG = "dog.*";

    public static final String ROUTING_KEY_CAT = "cat.*";

    @Bean
    public Queue queueDog(){
        return new Queue(QUEUE_DOG);
    }

    @Bean
    public Queue queueCat(){
        return new Queue(QUEUE_CAT);
    }

    @Bean
    public TopicExchange topicExchangeAnimal(){
        return new TopicExchange(EXCHANGE_ANIMAL);
    }

    @Bean
    public Binding bindingDog(Queue queueDog, TopicExchange topicExchangeAnimal){
        return BindingBuilder.bind(queueDog).to(topicExchangeAnimal).with(ROUTING_KEY_DOG);
    }

    @Bean
    public Binding bindingCat(Queue queueCat, TopicExchange topicExchangeAnimal){
        return BindingBuilder.bind(queueCat).to(topicExchangeAnimal).with(ROUTING_KEY_CAT);
    }

}

TopicsProducer生产者

@Service
public class TopicsProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * @param message
     */
    public void send(String routingKey, String message){
        rabbitTemplate.convertAndSend(TopicsConfig.EXCHANGE_ANIMAL, routingKey, message);
    }

}

TopicsConsumer 消费者

@Slf4j
@Service
public class TopicsConsumer {

    @RabbitListener(queues = TopicsConfig.QUEUE_DOG)
    public void fireDog(String message){
        log.info("狗收到消息" + message);
    }

    @RabbitListener(queues = TopicsConfig.QUEUE_CAT)
    public void fireCat(String message){
        log.info("猫收到消息" + message);
    }
}

TestController 测试发送者

@RestController
@RequestMapping(value = "test")
public class TestController {


    @Autowired
    private TopicsProducer topicsProducer;


    @GetMapping(value = "topicsProducer")
    public Object topicsProducer(String routingKey, String message){
        topicsProducer.send(routingKey, "topicsProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

}

结果

2024-07-11 12:47:42.586  INFO 21200 --- [ntContainer#6-1] c.h.rabbitmq.demo.topics.TopicsConsumer  : 狗收到消息topicsProducer:红色的狗1720673262558
2024-07-11 12:47:55.628  INFO 21200 --- [ntContainer#6-1] c.h.rabbitmq.demo.topics.TopicsConsumer  : 狗收到消息topicsProducer:蓝色的狗1720673275621
2024-07-11 12:48:04.502  INFO 21200 --- [ntContainer#7-1] c.h.rabbitmq.demo.topics.TopicsConsumer  : 猫收到消息topicsProducer:蓝色的猫1720673284499

延时消息,基于rabbitmq的延时插件

下面的案例模拟订单的取消延时。

docker镜像封装

Dockerfile文件内容

FROM rabbitmq:3-management
COPY rabbitmq_delayed_message_exchange-3.9.0.ez /plugins
RUN rabbitmq-plugins enable --offline rabbitmq_delayed_message_exchange

DelayConfig配置

@Configuration
public class DelayConfig {

    public static final String QUEUE_ORDER_CANCEL = "order_cancel";

    public static final String EXCHANGE_DELAY = "delayed";

    public static final String ROUTING_KEY_ORDER_CANCEL = "routing_order_cancel";

    @Bean
    public Queue queueOrderCancel(){
        return new Queue(QUEUE_ORDER_CANCEL);
    }

    @Bean
    public CustomExchange customExchangeDelayed(){
        Map<String, Object> args = new HashMap<>(1);
        args.put("x-delayed-type", "direct");
        return new CustomExchange(EXCHANGE_DELAY, "x-delayed-message", true, false, args);
    }

    @Bean
    public Binding bindingOrderCancel(Queue queueOrderCancel, CustomExchange customExchangeDelayed){
        return BindingBuilder.bind(queueOrderCancel).to(customExchangeDelayed).with(ROUTING_KEY_ORDER_CANCEL).noargs();
    }

}

DelayProducer生产者

@Service
public class DelayProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 发送消息
     * @param message
     * @param delay     过期时间秒
     */
    public void send(String message, long delay){
        rabbitTemplate.convertAndSend(DelayConfig.EXCHANGE_DELAY, DelayConfig.ROUTING_KEY_ORDER_CANCEL, message, me -> {
            me.getMessageProperties().setHeader("x-delay", delay * 1000);
            return me;
        });
    }
}

DelayConsumer消费者

@Slf4j
@Service
public class DelayConsumer {

    @RabbitListener(queues = DelayConfig.QUEUE_ORDER_CANCEL)
    public void fireOrderCancel(String message){
        log.info("取消订单消息 接收时间:" + System.currentTimeMillis() + "消息体:" + message);
    }

}

TestController 测试发送

@Slf4j
@RestController
@RequestMapping(value = "test")
public class TestController {

    @Autowired
    private SampleProducer sampleProducer;
    @Autowired
    private FanoutProducer fanoutProducer;
    @Autowired
    private DirectProducer directProducer;
    @Autowired
    private TopicsProducer topicsProducer;
    @Autowired
    private DelayProducer delayProducer;

    @GetMapping(value = "sampleProducer")
    public Object sampleProducer(){
        sampleProducer.send("sampleProducer" + System.currentTimeMillis());
        return "SUCCESS";
    }

    @GetMapping(value = "fanoutProducer")
    public Object fanoutProducer(String message){
        fanoutProducer.send("fanoutProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

    @GetMapping(value = "directProducer")
    public Object directProducer(String routingKey, String message){
        directProducer.send(routingKey, "directProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

    @GetMapping(value = "topicsProducer")
    public Object topicsProducer(String routingKey, String message){
        topicsProducer.send(routingKey, "topicsProducer:" + message +  System.currentTimeMillis());
        return "SUCCESS";
    }

    @GetMapping(value = "delayProducer")
    public Object delayProducer(String message, long delay){
        log.info("延时消息发送时间:"+ System.currentTimeMillis() + "延迟秒数:"+ delay);
        delayProducer.send("topicsProducer:" + message +  System.currentTimeMillis(), delay);
        return "SUCCESS";
    }

}

测试结果

2024-07-11 15:46:29.140  INFO 21648 --- [nio-8080-exec-1] c.h.rabbitmq.controller.TestController   : 延时消息发送时间:1720683989140延迟秒数:5
2024-07-11 15:46:34.373  INFO 21648 --- [ntContainer#0-1] c.h.rabbitmq.demo.delay.DelayConsumer    : 取消订单消息 接收时间:1720683994373消息体:topicsProducer:这是订单11720683989141
2024-07-11 15:47:35.034  INFO 21648 --- [nio-8080-exec-5] c.h.rabbitmq.controller.TestController   : 延时消息发送时间:1720684055034延迟秒数:0
2024-07-11 15:47:35.036  INFO 21648 --- [ntContainer#0-1] c.h.rabbitmq.demo.delay.DelayConsumer    : 取消订单消息 接收时间:1720684055036消息体:topicsProducer:这是订单11720684055034

基于SpringBoot的高阶用法

该用法主要用于基础工具中,配置更加灵活精简。

@Configuration
public class ComplexConfig {

    @Autowired
    private AmqpAdmin amqpAdmin;

    @PostConstruct
    protected void init(){
        /**
         * 定义了一个goods-service的队列,通过direct交换机,路由键rk-goods-service
         */
        Queue queue = new Queue("goods-service");
        amqpAdmin.declareQueue(queue);

        DirectExchange directExchange = new DirectExchange("center-direct-exchange");
        amqpAdmin.declareExchange(directExchange);
        
        amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(directExchange).with("rk-goods-service"));
    }

}

发送带header的消息

public void send(String headerValue, String message){
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            MessageProperties properties = message.getMessageProperties();
            if (properties == null) {
                properties = new MessageProperties();
            }
            properties.setHeader("class", headerValue);
            return message;
        }
    };
    rabbitTemplate.convertAndSend(SampleConfig.QUEUE, (Object) message, messagePostProcessor);
}

接收带header的消息

@RabbitListener(queues = SampleConfig.QUEUE)
public void fire2(Message message){
    log.info("收到消息2 header:" + message.getMessageProperties().getHeaders().get("class"));
    log.info("收到消息2 content:" + new String(message.getBody()));
}

附录

RabbitMqDocker部署方式

version: '3'
services:
  rabbitmq:
    image: huzhihui/rabbitmq:3.9.11-management-delayed
    container_name: rabbitmq
    networks:
      - default
    environment:
      - "TZ=Asia/Shanghai"
      - "RABBITMQ_DEFAULT_USER=admin"
      - "RABBITMQ_DEFAULT_PASS=admin"
    volumes:
      - "./data:/var/lib/rabbitmq"
    ports:
      - 5672:5672
      - 15672:15672
      - 1883:1883
      - 15675:15675
networks:
  default:
    external:
      name: huzhihui