Spring Boot 整合——RabbitMQ基础使用

消息队列的基本介绍

关于消息队列

消息队列是一种传输服务。他的角色就是维护一条从Producer到Consumer的路线,
保证数据能够按照指定的方式进行传输。

消息队列中的角色

消息队列中包含下面的角色,各个角色之间相互配合实现了整个消息传递的功能。

  • Producer(消息生产者)
  • Consumer(消息消费者)
  • Exchange(交换器)
  • Queue(队列)
  • RoutingKey(路由key)
  • Connection(连接)
  • Channels(信道)
  • Message(消息对象)

Message(消息对象)
一个Message有两个部分:payload(有效载荷)和label(标签)

名称 详细
payload 主要内容为传输的数据
label 主要是exchange的名字和对数据的描述,此部分的内容在传递给消费者之前会被删除。RabbitMQ也是通过这个label来决定把这个Message发给哪个Consumer

Producer-消息生产者

作为消息的生产者主要的任务为下面内容

  1. 连接RabbitMQ服务器然后将消息投递到Exchange

Consumer-消息消费者

作为消费者的主要任务

  1. 订阅队列,RabbitMQ将Queue中的消息发送到消息消费者。
  2. 一般来说Consumer它是不知道谁发送的这个信息的。但是如果Producer发送的消息内包含了Producer的信息就另当别论了。

Exchange-交换器

Exchange的作用是接收生产者发送的消息,根据参数将消息路由到零个(丢弃)或者多个Queue中。
Exchange存在fanout、direct、topic、headers四种类型,每种类型对应不同的路由规则。

路由名称 路由规则
direct 它会把消息路由到那些binding key与routing key完全匹配的Queue中
fanout 它会把生产者发送到该Exchange的所有消息路由到所有与它绑定的Queue中,最终被多个消费者消费
topic direct的匹配规则是精确匹配的,而topic可以进行模糊匹配
headers 不依赖于routing key与binding key的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配

Queue-队列

  • RabbitMQ的内部对象,用于存储消息
  • 生产者生产的消息最终都被投递到队列中
  • 消费者通过订阅队列消息来获得消息
  • 多个消费者可以订阅同一个队列。这个时候队列中的消息会被平均分配给多个消费者处理,而不是每个消费者都收到消息进行处理。

RoutingKey-路由key

RoutingKey是生产者在推送消息的时候,跟随消息一起发送的路由规则标识

  1. routing key需要与Exchange Type及binding key三者联合使用才能最终生效
  2. 消息生产者将消息推送给Exchange的时候,通过指定routing key来确定消息最终被推送至哪个队列
  3. RabbitMQ为routing key设定的长度限制为255 bytes。

Connection-连接

Producer和Consumer都是通过TCP连接到RabbitMQ Server的

Channels-信道

数据流动都是在Channel中进行的。也就是说,第一步建立TCP连接,第二步建立这个Channel

整个流程可以这么描述

推送消息
推送消息
路由
路由
路由
消费者订阅
消费者订阅
消费者订阅
消费者订阅
消费者订阅
Producer1
exchange1
Producer2
exchange2
queue1
queue2
queue3
Consumer1
Consumer2
Consumer2
Consumer2
Consumer2

Spring Boot 整合RabbitMQ

添加依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>
    dependencies>

简单的参数配置

spring:
  application:
    name: rabbit
  rabbitmq:
    # 地址
    host: 127.0.0.1
    # 端口
    port: 5672
    # 账号信息
    username: admin
    password: admin
    # 开启发送确认
    publisher-confirms: true
    # 开启发送失败退回
    publisher-returns: true
    listener:
      # 开启ACK
      direct:
        # 配置 manual  表示启动消费方的手动确认
        acknowledge-mode: auto
      simple:
        acknowledge-mode: auto
    template:
      retry:
        enabled: true

使用

完成了上面的配置,我们可以直接引入RabbitTemplate来进行对RabbitMQ进行操作了

    @Autowired
    private RabbitTemplate rabbitTemplate;

RabbitMQ的简单操作

direct模式的队列使用

配置Exchange和Queue参数

    public static final String DIRECT_QUEUE1 = "direct.queue1";
    public static final String DIRECT_QUEUE2 = "direct.queue2";
    public static final String DIRECT_EXCHANGE = "direct.exchange";

    @Bean
    public Queue getDirectQueue() {
        return new Queue(DIRECT_QUEUE1);
    }

    @Bean
    public Queue getDirectQueue2() {
        return new Queue(DIRECT_QUEUE2);
    }
    
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(DIRECT_EXCHANGE);
    }
    

后续我们将队列和Exchange进行绑定
这个时候消息中的路由键(routing key)和 Binding 中的 binding key 一致消息才能被推送至队列中

    @Bean
    public Binding directBinding1() {
        return BindingBuilder
                // 设置queue
                .bind(getDirectQueue())
                // 绑定交换机
                .to(directExchange())
                // 设置routingKey
                .with("direct.V1");
    }


    @Bean
    public Binding directBinding2() {
        return BindingBuilder
                // 设置queue
                .bind(getDirectQueue2())
                // 绑定交换机
                .to(directExchange())
                // 设置routingKey
                .with("direct.V2");
    }

配置消息发送者

在消息发送者代码中,创建了两个方法,一个使用direct.V1的路由一个使用direct.V2的路由

@Component
public class DirectSender {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send1(User user) {
        this.rabbitTemplate.convertAndSend(
                DirectConfig.DIRECT_EXCHANGE, 
                // routingKey
                "direct.V1", 
                user);
    }

    public void send2(User user) {
        this.rabbitTemplate.convertAndSend(
                DirectConfig.DIRECT_EXCHANGE,
                // routingKey
                "direct.V2",
                user);
    }
    
}

配置消息接收者

同样消息接受者我们也监听了两个队列

@Component
public class DirectReceiver {

    /**
     * queues是指要监听的队列的名字
     * @param user
     */
    @RabbitListener(queues = DirectConfig.DIRECT_QUEUE1)
    @RabbitHandler
    public void receiveDirect1(User user) {

        System.out.println("【receiveDirect1监听到消息】" + user);
    }

    /**
     * queues是指要监听的队列的名字
     * @param user
     */
    @RabbitListener(queues = DirectConfig.DIRECT_QUEUE2)
    @RabbitHandler
    public void receiveDirect2(User user) {

        System.out.println("【receiveDirect2监听到消息】" + user);
    }
}

消息发送的流程

推送消息key1
推送消息key2
消息key1
消息key2
消费者订阅queue1
消费者订阅queue2
Producer
exchange
queue1-路由key1
queue2-路由key2
Consumer1
Consumer2

测试内容

调用接口发送请求

现在我们调用接口:localhost:8000/direct/send1。使用direct.V1的路由key发送消息

控制台可以看到输出内容:

【receiveDirect1监听到消息】User(id=1, name=Direct, age=100)

现在我们调用接口:localhost:8000/direct/send2。使用direct.V2的路由key发送消息

控制台可以看到输出内容:

【receiveDirect2监听到消息】User(id=12, name=DirectV2, age=200)

这个时候可以看到queue消息的监听,严格遵守路由key的一一对应


fanout模式的队列使用

配置Exchange和Queue参数

    /**
     * fanout
     */
    public static final String FANOUT_QUEUE1 = "fanout.queue1";
    public static final String FANOUT_QUEUE2 = "fanout.queue2";
    public static final String FANOUT_EXCHANGE = "fanout.exchange";

    @Bean
    public FanoutExchange topicExchange() {
        return new FanoutExchange(FANOUT_EXCHANGE);
    }

    @Bean
    public Queue getFanoutQueue1() {
        return new Queue(FANOUT_QUEUE1);
    }


    @Bean
    public Queue getFanoutQueue2() {
        return new Queue(FANOUT_QUEUE2);
    }

Exchange和Queue绑定

    /**
     * 监听fanout.exchange 的队列fanout.queue1
     * @return
     */
    @Bean
    public Binding topicBinding1() {
        return BindingBuilder
                // 设置queue
                .bind(getFanoutQueue1())
                // 绑定交换机
                .to(topicExchange());
    }

    /**
     * 监听fanout.exchange 的队列fanout.queue2
     * @return
     */
    @Bean
    public Binding topicBinding2() {
        return BindingBuilder
                // 设置queue
                .bind(getFanoutQueue2())
                // 绑定交换机
                .to(topicExchange());
    }

配置消息发送者

发送消息的时候并没有指定routingKey而是之间发送到exchange中

@Component
public class FanoutSender {
    @Autowired
    private AmqpTemplate rabbitTemplate;

    /**
     * 消息发送到FanoutConfig.FANOUT_EXCHANGE交换机中
     * @param user
     */
    public void send(User user) {
        this.rabbitTemplate
                .convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", user);
    }
}

配置消息接收者
监听的时候我们监听了不同的队列消息

@Component
public class FanoutReceiver {

    /**
     * queues是指要监听的队列的名字
     * @param user
     */
    @RabbitListener(queues = FanoutConfig.FANOUT_QUEUE1)
    public void receiveTopic1(User user) {
        System.out.println("【receiveFanout1监听到消息】" + user);
    }

    @RabbitListener(queues = FanoutConfig.FANOUT_QUEUE2)
    public void receiveTopic2(User user) {
        System.out.println("【receiveFanout2监听到消息】" + user);
    }
}

消息发送的流程

fanout模式
消息key1推送至队列1
消息key1推送至队列2
消费者订阅队列1
消费者订阅队列2
Producer
exchange
queue1-路由无
queue2-路由无
Consumer1
Consumer2

测试内容

调用接口发送请求

现在我们调用接口:localhost:8000/fanout/send。使用direct.V1的路由key发送消息

控制台可以看到输出内容:

【receiveFanout1监听到消息】User(id=1, name=Fanout, age=100)
【receiveFanout2监听到消息】User(id=1, name=Fanout, age=100)

此时一条消息被路由下所有队列消费掉


topic模式的队列使用

配置Exchange和Queue参数

   /**
     * topic
     */
    public static final String TOPIC_QUEUE1 = "topic.queue1";
    public static final String TOPIC_QUEUE2 = "topic.queue2";
    public static final String TOPIC_EXCHANGE = "topic.exchange";


    @Bean
    public Queue getTopicQueue1() {
        return new Queue(TOPIC_QUEUE1);
    }

    @Bean
    public Queue getTopicQueue2() {
        return new Queue(TOPIC_QUEUE2);
    }
    
    @Bean
    public TopicExchange topicExchange() {
        return new TopicExchange(TOPIC_EXCHANGE);
    }

Exchange和Queue绑定

其中队列2使用的匹配模式。
其中匹配方式:

  • #:匹配一个或者多个词
  • *: 只能匹配一个词
    @Bean
    public Binding topicBinding1() {
        return BindingBuilder
                // 设置queue
                .bind(getTopicQueue1())
                // 绑定交换机
                .to(topicExchange())
                // 设置routingKey
                .with("dai.message");
    }

    @Bean
    public Binding topicBinding2() {
        return BindingBuilder
                .bind(getTopicQueue2())
                .to(topicExchange())
                .with("dai.#");
    }

配置消息发送者

消息发送者里面设置了两个请求,一个用来验证精确匹配,一个用来验证模糊匹配

@Component
public class TopicSender {
    @Autowired
    private AmqpTemplate rabbitTemplate;

    /**
     * 第一个参数:TopicExchange名字
     * 第二个参数:Route-Key
     * 第三个参数:要发送的内容
     * @param user
     */
    public void send(User user) {
        this.rabbitTemplate.convertAndSend(
                TopicConfig.TOPIC_EXCHANGE,
                "dai.message", 
                user);
        this.rabbitTemplate.convertAndSend(
                TopicConfig.TOPIC_EXCHANGE, 
                "dai.dai", 
                user);
    }
}

配置消息接收者

监听操作我们还是监听两个队列

@Component
public class TopicReceiver {
    /**
     * queues是指要监听的队列的名字
     * @param user
     */
    @RabbitListener(queues = TopicConfig.TOPIC_QUEUE1)
    public void receiveTopic1(User user) {
        System.out.println("【receiveTopic1监听到消息】" + user.toString());
    }
    @RabbitListener(queues = TopicConfig.TOPIC_QUEUE2)
    public void receiveTopic2(User user) {
        System.out.println("【receiveTopic2监听到消息】" + user.toString());
    }
}

消息发送的流程

topic模式-key:queue.a
topic模式-key:queue.b
消息queue.a推送至队列1
消息queue.a推送至队列2
消息queue.b推送至队列2
消费者订阅队列1
消费者订阅队列2
Producer
exchange
Producer2
queue1-queue.a
queue2-queue.#
Consumer1
Consumer2

测试内容

调用接口发送请求

现在我们调用接口:localhost:8000/topic/send。发送消息

控制台可以看到输出内容:

【receiveTopic2监听到消息】User(id=5, name=Topic, age=500)
【receiveTopic1监听到消息】User(id=5, name=Topic, age=500)
【receiveTopic2监听到消息】User(id=5, name=Topic, age=500)

可以看到消息被模糊匹配然后被两个队列都消费掉了


附录:API文档

文档地址

https://docs.spring.io/spring-amqp/docs/2.0.13.BUILD-SNAPSHOT/reference/html/

支持的参数

参数 描述 参数末班
spring.rabbitmq.address 客户端连接的地址,有多个的时候使用逗号分隔,该地址可以是IP与Port的结合
spring.rabbitmq.cache.channel.checkout-timeout 当缓存已满时,获取Channel的等待时间,单位为毫秒
spring.rabbitmq.cache.channel.size 缓存中保持的Channel数量
spring.rabbitmq.cache.connection.mode 连接缓存的模式 CHANNEL
spring.rabbitmq.cache.connection.size 缓存的连接数
spring.rabbitmq.connnection-timeout 连接超时参数单位为毫秒:设置为“0”代表无穷大
spring.rabbitmq.dynamic 默认创建一个AmqpAdmin的Bean true
spring.rabbitmq.host RabbitMQ的主机地址 localhost
spring.rabbitmq.listener.acknowledge-mode 容器的acknowledge模式
spring.rabbitmq.listener.auto-startup 启动时自动启动容器 true
spring.rabbitmq.listener.concurrency 消费者的最小数量
spring.rabbitmq.listener.default-requeue-rejected 投递失败时是否重新排队 true
spring.rabbitmq.listener.max-concurrency 消费者的最大数量
spring.rabbitmq.listener.prefetch 在单个请求中处理的消息个数,他应该大于等于事务数量
spring.rabbitmq.listener.retry.enabled 不论是不是重试的发布 false
spring.rabbitmq.listener.retry.initial-interval 第一次与第二次投递尝试的时间间隔 1000
spring.rabbitmq.listener.retry.max-attempts 尝试投递消息的最大数量 3
spring.rabbitmq.listener.retry.max-interval 两次尝试的最大时间间隔 10000
spring.rabbitmq.listener.retry.multiplier 上一次尝试时间间隔的乘数 1.0
spring.rabbitmq.listener.retry.stateless 不论重试是有状态的还是无状态的 true
spring.rabbitmq.listener.transaction-size 在一个事务中处理的消息数量。为了获得最佳效果,该值应设置为小于等于每个请求中处理的消息个数,即spring.rabbitmq.listener.prefetch的值
spring.rabbitmq.password 登录到RabbitMQ的密码
spring.rabbitmq.port RabbitMQ的端口号 5672
spring.rabbitmq.publisher-confirms 开启Publisher Confirm机制 false
spring.rabbitmq.publisher-returns 开启publisher Return机制 false
spring.rabbitmq.requested-heartbeat 请求心跳超时时间,单位为秒
spring.rabbitmq.ssl.enabled 启用SSL支持 false
spring.rabbitmq.ssl.key-store 保存SSL证书的地址
spring.rabbitmq.ssl.key-store-password 访问SSL证书的地址使用的密码
spring.rabbitmq.ssl.trust-store SSL的可信地址
spring.rabbitmq.ssl.trust-store-password 访问SSL的可信地址的密码
spring.rabbitmq.ssl.algorithm SSL算法,默认使用Rabbit的客户端算法库
spring.rabbitmq.template.mandatory 启用强制信息 false
spring.rabbitmq.template.receive-timeout receive()方法的超时时间 0
spring.rabbitmq.template.reply-timeout sendAndReceive()方法的超时时间 5000
spring.rabbitmq.template.retry.enabled 设置为true的时候RabbitTemplate能够实现重试 false
spring.rabbitmq.template.retry.initial-interval 第一次与第二次发布消息的时间间隔 1000
spring.rabbitmq.template.retry.max-attempts 尝试发布消息的最大数量 3
spring.rabbitmq.template.retry.max-interval 尝试发布消息的最大时间间隔 10000
spring.rabbitmq.template.retry.multiplier 上一次尝试时间间隔的乘数 1.0
spring.rabbitmq.username 登录到RabbitMQ的用户名
spring.rabbitmq.virtual-host 连接到RabbitMQ的虚拟主机

你可能感兴趣的:(RabbitMQ,#,Spring,Boot常用组件,JAVA)