RabbitMQ的使用

1.MQ的简介

MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。

MQ的优势:

  • 应用解耦
  • 任务异步处理
  • 消锋填谷

常见的 MQ 产品有很多,在这里我们主要简绍RabbitMQ这个产品


2.RabbitMQ的简绍

RabbitMQ的基础架构如下:

RabbitMQ的使用_第1张图片

RabbitMQ提供了6种模式:

  • 简单模式
  • work模式
  • Publish/Subscribe发布与订阅模式
  • Routing路由模式
  • Topics主题模式
  • RPC模式(暂不简绍)

3.RabbitMQ结合SpringBoot的使用

在这里主要简绍Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式

3.1 RabbitMQ的安装

参考文档:https://blog.csdn.net/shz_123/article/details/122803739?spm=1001.2014.3001.5502

3.2 创建SpringBoot项目

  • 创建springboot项目,添加pom文件依赖(生产者Producer模块,消费者Consumer模块一样)
        
            org.springframework.boot
            spring-boot-starter
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
  • application.yml文件配置RabbitMQ相关信息(要与RabbitMQ一致)(生产者Producer模块,消费者Consumer模块一样)
spring:
 rabbitmq:
 #rabbitmq安装宿主机IP
 host: 192.168.126.140
 #登录管理控制台端口
 port: 5672
 #登录管理控制台的用户名
 username: admin
 password: 123456
 virtual-host: /song

RabbitMQ的使用_第2张图片

3.3 在Producer模块创建config类, 注入RabbitTemplate对象,通过RabbitTemplate对象发送消息到交换机,在Consumer模块中创建Listener类来接收信息**

  • Publish/Subscribe发布与订阅模式的config(生产者发送消息,所有消费者都可以接收到消息,没有路由key值)
package com.song.springbootrabbitmqproducer.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 *Publish/Subscribe发布与订阅模式的config
 * @author song
 */
@Configuration
public class PublishModelConfig {
    /**队列_1名称*/
    public static final String PUBLISH_QUEUE_1 = "publish_queue_1";
    /**队列_2名称*/
    public static final String PUBLISH_QUEUE_2 = "publish_queue_2";
    /**交换器名称*/
    public static final String PUBLISH_EXCHANGE = "publish_exchange";

    /**
     * 申明交换机 durable=true表示持久化
     * @return 申明好的交换机
     */
    @Bean("publishEx")
    public Exchange getExchange() {
        return ExchangeBuilder.fanoutExchange(PUBLISH_EXCHANGE).durable(true).build();
    }

    /**
     * 申明队列  持久化
     * @return 申明好的队列
     */
    @Bean("publishQueue1")
    public Queue getQueue1(){
        return QueueBuilder.durable(PUBLISH_QUEUE_1).build();
    }

    /**
     * 申明队列  持久化
     * @return 申明好的队列
     */
    @Bean("publishQueue2")
    public Queue getQueue2(){
        return QueueBuilder.durable(PUBLISH_QUEUE_2).build();
    }

    /**
     * 将队列_1与交换器进行绑定
     * @param queue 要绑定的队列
     * @param exchange 要绑定的交换器
     * @return 绑定好的信息
     */
    @Bean
    public Binding getBinding1(@Qualifier("publishQueue1") Queue queue,
                              @Qualifier("publishEx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
    /**
     * 将队列_2与交换器进行绑定
     * @param queue 要绑定的队列
     * @param exchange 要绑定的交换器
     * @return 绑定好的信息
     */
    @Bean
    public Binding getBinding2(@Qualifier("publishQueue2") Queue queue,
                              @Qualifier("publishEx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
}

测试 发布与订阅模式

@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void test1() {
      //发送消息===》发布与订阅模式
        rabbitTemplate.convertAndSend(PublishModelConfig.PUBLISH_EXCHANGE,"","发布与订阅模式");
    }
}

在RabbitMQ管理控制台可以看到,两个队列分别都接收到了一条信息

在这里插入图片描述
在这里插入图片描述
RabbitMQ的使用_第3张图片

在Consumer模块接收信息

@Component
public class MyListener{
    @RabbitListener(queues = "publish_queue_2")
    public void pubListener2(String message) {
        System.out.println("消费者2======接收到的消息为:" + message);
    }

    @RabbitListener(queues = "publish_queue_1")
    public void pubListener1(String message) {
        System.out.println("消费者1=====接收到的消息为:" + message);
    }
}

测试

@SpringBootTest
class SpringbootRabbitmqCustomerApplicationTests {
    @Autowired
    private MyListener myListener;
    @Test
    //测试设置为死循环,只要生产者发送消息,消费者就可以接收
    void test() {
        while(true){
        }
    }
}

在RabbitMQ管理控制台可以看到
在这里插入图片描述
在这里插入图片描述

Routing路由模式的config(生产者发送消息,交换机根据routingKey将消息分发到队列,在由消费者接收)

  /**
   * Routing路由模式的config
   * @author song
   */
  @Configuration
  public class RoutingModelConfig {
      /**队列名称 routingkey=insert*/
      public static final String ROUTING_QUEUE_KEY_INSERT = "routing_queue_key_insert";
      /**队列_1名称 routingkey=delete*/
      public static final String ROUTING_QUEUE_KEY_DELETE = "routing_queue_key_delete";
      /**交换器名称*/
      public static final String ROUTING_EXCHANGE = "routing_exchange";
      
  /**
  * 申明交换机 durable=true表示持久化
  * @return 申明好的交换机
  */
  @Bean("routingEx")
  public Exchange getExchange() {
      return ExchangeBuilder.directExchange(ROUTING_EXCHANGE).durable(true).build();
  }
  /**
   * 申明队列  持久化
   * @return 申明好的队列
   */
  @Bean("routingQueue_insert")
  public Queue getQueue1(){
      return QueueBuilder.durable(ROUTING_QUEUE_KEY_INSERT).build();
  }

  /**
   * 申明队列  持久化
   * @return 申明好的队列
   */
  @Bean("routingQueue_delete")
  public Queue getQueue2(){
      return QueueBuilder.durable(ROUTING_QUEUE_KEY_DELETE).build();
  }

  /**
   * 队列名称 路由key=insert与交换器进行绑定
   * @param queue 要绑定的队列
   * @param exchange 要绑定的交换器
   * @return 绑定好的信息
   */
  @Bean
  public Binding getRoutBinding1(@Qualifier("routingQueue_insert") Queue queue,
                             @Qualifier("routingEx") Exchange exchange){
      return BindingBuilder.bind(queue).to(exchange).with("insert").noargs();
  }
  /**
   * 队列_1名称 路由key=delete与交换器进行绑定
   * @param queue 要绑定的队列
   * @param exchange 要绑定的交换器
   * @return 绑定好的信息
   */
  @Bean
  public Binding getRoutBinding2(@Qualifier("routingQueue_delete") Queue queue,
                             @Qualifier("routingEx") Exchange exchange){
      return BindingBuilder.bind(queue).to(exchange).with("delete").noargs();
  }

}

测试 routing路由模式

@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
  @Autowired
  private RabbitTemplate rabbitTemplate;
  @Test
  void test2() {
      rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE,"insert","routing模式====routingKey:" +
              "insert");

      rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE,"delete","routing模式====routingKey:" +
              "delete");
  }
}

在RabbitMQ管理控制台可以看到
在这里插入图片描述
在这里插入图片描述

RabbitMQ的使用_第4张图片

在Consumer模块接收信息

@Component
public class MyListener{

    @RabbitListener(queues = "routing_queue_key_delete")
    public void routListener2(String message) {
        System.out.println("消费者2======接收到的消息为:" + message);
    }

    @RabbitListener(queues = "routing_queue_key_insert")
    public void routListener1(String message) {
        System.out.println("消费者1=====接收到的消息为:" + message);
    }
}

测试 与 上述发布与订阅模式一样,不再叙述,直接看结果

在这里插入图片描述在这里插入图片描述

  • Topics通配符模式的config(生产者发送消息,交换机根据routingKey将消息分发到队列,在由消费者接收)

    Topic 类型与 Routining相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型

Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词

/**Topics主题模式的config
 * @author song
 */
@Configuration
public class TopicModelConfig {
    /**队列名称 路由key=item.#    */
    public static final String TOPIC_QUEUE_KEY_MONEY = "topic_queue_key_#";
    /**队列_1名称 路由key=item.*  */
    public static final String TOPIC_QUEUE_KEY_ONE = "topic_queue_key_*";
    /**交换器名称*/
    public static final String TOPIC_EXCHANGE = "topic_exchange";

    /**
     * 申明交换机 durable=true表示持久化
     * @return 申明好的交换机
     */
    @Bean("topicEx")
    public Exchange getExchange() {
        return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE).durable(true).build();
    }

    /**
     * 申明队列  持久化
     * @return 申明好的队列
     */
    @Bean("topicQueue_#")
    public Queue getQueue1(){
        return QueueBuilder.durable(TOPIC_QUEUE_KEY_MONEY).build();
    }

    /**
     * 申明队列  持久化
     * @return 申明好的队列
     */
    @Bean("routingQueue_*")
    public Queue getQueue2(){
        return QueueBuilder.durable(TOPIC_QUEUE_KEY_ONE).build();
    }

    /**
     * 队列名称 路由key=item.# 与交换器进行绑定
     * @param queue 要绑定的队列
     * @param exchange 要绑定的交换器
     * @return 绑定好的信息
     */
    @Bean
    public Binding getTopBinding1(@Qualifier("topicQueue_#") Queue queue,
                                   @Qualifier("topicEx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("item.#").noargs();
    }
    /**
     * 队列名称 路由key=item.* 与交换器进行绑定
     * @param queue 要绑定的队列
     * @param exchange 要绑定的交换器
     * @return 绑定好的信息
     */
    @Bean
    public Binding getTopBinding2(@Qualifier("routingQueue_*") Queue queue,
                                   @Qualifier("topicEx") Exchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("item.*").noargs();
    }
}

测试 Topics通配符模式

@SpringBootTest
class SpringbootRabbitmqProducerApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void test3() {
        rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
                "item.insert","商品新增,routing key 为item.insert");

        rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
                "item.update.date", "商品修改,routing key 为item.update.date");
        rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
                "item.delete", "商品删除,routing key 为item.delete");
    }
}

在RabbitMQ管理控制台可以看到

在这里插入图片描述
在这里插入图片描述
RabbitMQ的使用_第5张图片

在Consumer模块接收信息

@Component
public class MyListener{
    @RabbitListener(queues = "topic_queue_key_#")
    public void topListener1(String message) {
        System.out.println("消费者1=====接收到的消息为:" + message);
    }

    @RabbitListener(queues = "topic_queue_key_*")
    public void topListener2(String message) {
        System.out.println("消费者2=====接收到的消息为:" + message);
    }
}

测试 与 上述发布与订阅模式一样,不再叙述,直接看结果

在这里插入图片描述
RabbitMQ的使用_第6张图片

3.4 小结

  • Publish/Subscribe发布与订阅模式 交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
  • Routing路由模式 Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列。
  • Topics通配符模式 Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic在配置routing key 的时候可以使用通配符,显得更加灵活。

4.RabbitMQ的高级特性

1.消息的可靠投递

在使用 RabbitMQ 的时候,作为消息发送方希望杜绝任何消息丢失或者投递失败场景。

RabbitMQ 为我们提供了两种方式用来控制消息的投递可靠性模式。

  • confirm 确认模式

    消息从 producer 到 exchange 发生错误,则会返回一个 confirmCallback

  • return 退回模式

    消息从 exchange–>queue 投递失败则会返回一个 returnCallback 。

消息的可靠投递配置

  spring:
  rabbitmq:
    #rabbitmq安装宿主机IP
    host: 192.168.126.140
    #端口
    port: 5672
    #登录管理控制台的用户名
    username: admin
    password: 123456
    virtual-host: /song

    #消息的可靠投递
    # 旧版本 开启 confirm 确认模式
    # publisher-confirms: true
    # 新版的开启 confirm 确认模式
    publisher-confirm-type: correlated
    # 开启 return 退回模式
    publisher-returns: true

测试

 /**
     * 确认模式测试
     */
    @Test
    void confirmTest() {
        rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {

            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                System.out.println("confirm方法被执行了....");
                if (ack) {
                    //接收成功
                    System.out.println("接收成功消息" + cause);
                } else {
                    //接收失败
                    System.out.println("接收失败消息" + cause);
                    //做一些处理,让消息再次发送。
                }
            }
        });

//        rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE,
//                "item.insert", "商品新增,routing key 为item.insert");
        rabbitTemplate.convertAndSend("error",
                "item.insert", "商品新增,routing key 为item.insert");
    }

模拟当发送消息时,交换机名称不对时,结果

在这里插入图片描述

/**
     * 返回模式测试
     */
    @Test
    public void testReturn() {
        //设置交换机处理失败消息的模式
        rabbitTemplate.setMandatory(true);
        //2.设置ReturnCallBack

        rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
            /**
             *
             * @param message 消息对象
             * @param replyCode 错误码
             * @param replyText 错误信息
             * @param exchange 交换机
             * @param routingKey 路由键
             */
            @Override
            public void returnedMessage(Message message, int replyCode, String
                    replyText, String exchange, String routingKey) {
                System.out.println("return 执行了....");
                System.out.println(message);
                System.out.println(replyCode);
                System.out.println(replyText);
                System.out.println(exchange);
                System.out.println(routingKey);
                //处理
            }
        });
        //3. 发送消息
        //rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE, "item.insert",
        //        "message confirm....");
        rabbitTemplate.convertAndSend(TopicModelConfig.TOPIC_EXCHANGE, "item1.insert",
                "message confirm....");
    }

模拟当发送消息时,routingKey不对时,结果

RabbitMQ的使用_第7张图片

2.Consumer Ack 与 消费端限流

ack指Acknowledge,确认。 表示消费端收到消息后的确认方式

有三种确认方式:

  • 自动确认:acknowledge=“none”

    自动确认是指,当消息一旦被Consumer接收到,则自动确认收到,并将相应 message从RabbitMQ 的消息缓存中移除。但是在实际业务处理中,很可能消息接收到,业务处理出现异常,那么该消息就会丢失

  • 手动确认:acknowledge="manual

    手动确认方式,则需要在业务处理成功后,调用channel.basicAck(),手动签收,如果出现异常,则调用channel.basicNack()方法,让其自动重新发送消息

  • 根据异常情况确认:acknowledge=“auto”,(这种方式使用麻烦,不作讲解)

    消费端限流:是消费端每次从mq拉取限定个数的消息来消费


Consumer Ack 与 消费端限流 的配置文件(消费端配置)

spring:
  rabbitmq:
    host: 192.168.126.140
    port: 5672
    username: admin
    password: 123456
    virtual-host: /song
    listener:
      # RabbitMQ模式使用simple  simple支持事务的
      simple:
        # Consumer ACK机制:设置为手动签收
        acknowledge-mode: manual
        # 限流,配置3 表示消费端每次向MQ拉取最大3条消息
        prefetch: 3

消费者接收消息的代码

当消费者接收消息出错时

在这里插入图片描述

3.TTL

Time To Live,消息过期时间设置

  • 当消息到达存活时间后,还没有被消费,会被自动清除。

  • RabbitMQ可以对消息设置过期时间,也可以对整个队列(Queue)设置过期时间。

  • 设置队列过期时间使用参数:x-message-ttl,单位:ms(毫秒),会对整个队列消息统一过期

  • 设置消息过期时间使用参数:expiration。单位:ms(毫秒),当该消息在队列头部时(消费时),会单独判断这一消息是否过期。
    如果两者都进行了设置,以时间短的为准。

    管控台中设置队列TTL
    RabbitMQ的使用_第8张图片

在列队代码中设置队列TTL

/**
     * 申明队列  持久化
     * @return 申明好的队列
     */
    @Bean("routingQueue_*")
    public Queue getQueue2(){
        return QueueBuilder.durable(TOPIC_QUEUE_KEY_ONE).withArgument("x-message-ttl",10000).build();
    }

在消息代码中设置队列TTL

@Test
    void test2() {
        rabbitTemplate.convertAndSend(RoutingModelConfig.ROUTING_EXCHANGE, "insert", "routing模式====routingKey:" +
                "insert", new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");//设置消息过期时间为5秒
                return message;
            }
        });
    }

4.死信队列

死信队列,英文缩写:DLX 。Dead Letter Exchange(死信交换机),当消息成为Deadmessage后,可以被重新发送到另一个交换机,这个交换机就是DLX。

RabbitMQ的使用_第9张图片

消息成为死信的三种情况:

  1. 队列消息长度到达限制;
  2. 消费者拒接消费消息,basicNack/basicReject,并且不把消息重新放入原目标队列,requeue=false;
  3. 原队列存在消息过期设置,消息到达超时时间未被消费;

管控台中设置死信队列

RabbitMQ的使用_第10张图片


在代码中设置死信队列

 @Bean
    public Exchange exchangeDlx(){
        return ExchangeBuilder.topicExchange("dead_exchange").build();
    }

    @Bean
    public Queue queueNormalDlx(){
        return QueueBuilder.durable("queue_Normal_DLX")//正常队列的名称
                .withArgument("x-dead-letter-exchange","dead_exchange")//设置改队列的死信交换机
                .withArgument("x-dead-letter-routing-key","dlx.xf")//设置该队列的发送消息时指定的routingkey
                .withArgument("x-message-ttl",10000)//设置队列中消息的过期时间
                .withArgument("x-max-length",10).build();//设置队列的最大容量
    }

5.延迟队列

延迟队列,即消息进入队列后不会立即被消费,只有到达指定时间后,才会被消费。

但是可以使用:TTL+死信队列 组合实现延迟队列的效果。

你可能感兴趣的:(docker,RabbitMQ,spring,boot,java,docker)