RabbitMQ

RabbitMQ

MQ

常见MQ实现

RabbitMQ快速入门

  • 结构介绍

请添加图片描述

  • 消息模型

请添加图片描述

  • 案例

    • 实现思路

        1. 创建连接
        1. 创建Channel
        1. 声明队列
        1. 发送消息
        1. 关闭连接和Channel
    • 代码实现

      • publisher端实现
      
      public class PublisherTest {
          @Test
          public void testSendMessage() throws IOException, TimeoutException {
              // 1.建立连接
              ConnectionFactory factory = new ConnectionFactory();
              // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
              factory.setHost("127.0.0.1");
              factory.setPort(5672);
              factory.setVirtualHost("/");
              factory.setUsername("guest");
              factory.setPassword("123456");
              // 1.2.建立连接
              Connection connection = factory.newConnection();
      
              // 2.创建通道Channel
              Channel channel = connection.createChannel();
      
              // 3.创建队列
              String queueName = "simple.queue";
              channel.queueDeclare(queueName, false, false, false, null);
      
              // 4.发送消息
              String message = "hello, rabbitmq!";
              channel.basicPublish("", queueName, null, message.getBytes());
              System.out.println("发送消息成功:【" + message + "】");
      
              // 5.关闭通道和连接
              channel.close();
              connection.close();
      
          }
      }
      
      • consumer端实现
      
      public class ConsumerTest {
      
          public static void main(String[] args) throws IOException, TimeoutException {
              // 1.建立连接
              ConnectionFactory factory = new ConnectionFactory();
              // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
              factory.setHost("127.0.0.1");
              factory.setPort(5672);
              factory.setVirtualHost("/");
              factory.setUsername("guest");
              factory.setPassword("123456");
              // 1.2.建立连接
              Connection connection = factory.newConnection();
      
              // 2.创建通道Channel
              Channel channel = connection.createChannel();
      
              // 3.创建队列
              String queueName = "simple.queue";
              channel.queueDeclare(queueName, false, false, false, null);
      
              // 4.订阅消息
              channel.basicConsume(queueName, true, new DefaultConsumer(channel){
                  @Override
                  public void handleDelivery(String consumerTag, Envelope envelope,
                                             AMQP.BasicProperties properties, byte[] body) throws IOException {
                      // 5.处理消息
                      String message = new String(body);
                      System.out.println("接收到消息:【" + message + "】");
                  }
              });
              System.out.println("等待接收消息。。。。");
          }
      }
      

SpringAMQP

Basic Queue简单队列模型

  • 实现步骤

      1. 引入依赖,配置mq连接信息
            <!--AMQP依赖,包含RabbitMQ-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>
    
    spring:
      rabbitmq:
        host: 127.0.0.1 # 主机名
        port: 5672 # 端口
        virtual-host: / # 虚拟主机
        username: guest # 用户名
        password: 123456 # 密码
    
      1. 编写publisher端,发送消息
    @SpringBootTest
    /*SpringRunner是JUnit 4中的一个类,用于集成Spring TestContext Framework,以便在测试中使用Spring特性(如依赖注入,自动装配等)。
    * 此处用于解决RabbitTemplate无法实现自动注入的问题
    * 也可使用import org.junit.jupiter.api.Test;而非import org.junit.Test;解决此问题*/
    //@RunWith(SpringRunner.class)
    public class SpringAmqpTest {  
    	@Autowired
        private RabbitTemplate rabbitTemplate;
    
        /**
         * Basic Queue 简单队列测试
         */
        @Test
        public void testSimpleQueue() {
            //队列名
            String queueName = "simple.queue";
            //消息
            String message = "hello springamqp";
            //发送消息
            rabbitTemplate.convertAndSend(queueName, message);
        }
    }
    
      1. 编写consumer端,接收消息
    @Component
    public class SpringRabbitListener {
        //简单队列模型消费者
        @RabbitListener(queues = "simple.queue")
        public void listenSimpleQueueMessage(String message){
            System.out.println("消费者接收到的消息 = " + message);
        }
    }
    

Work Queue 工作队列模型

    1. 修改publisher端,模拟大量消息堆积情况
    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * workQueue 工作队列测试
     * 向队列中不停发送消息,模拟消息堆积。
     */
    @Test
    public void testWorkQueue() throws InterruptedException {
        // 队列名称
        String queueName = "simple.queue";
        // 消息
        String message = "hello, message_";
        for (int i = 0; i < 50; i++) {
            // 发送消息
            rabbitTemplate.convertAndSend(queueName, message + i);
            Thread.sleep(20);
        }
    }
    1. 修改consumer端,模拟多个消费者绑定同一个队列进行任务处理
   @Component
public class SpringRabbitListener {
	//工作队列模型消费者
    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(20);
    }

    @RabbitListener(queues = "simple.queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(200);
    }
}
    1. 优化,能者多劳

此时存在问题:两个消费者接收到的消息一样多,导致处理速度下降,添加配置每次只能读取一条消息,处理完才能获取下一条消息,在消费端applicaton.yml文件中添加配置

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完才能获取下一条

发布/订阅模型

Fanout类型交换机
  • 模型图
    请添加图片描述

  • 消息发送流程

  • 1) 可以有多个队列

  • 2) 每个队列都要绑定到Exchange(交换机)

  • 3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定

  • 4) 交换机把消息发送给绑定过的所有队列

  • 5) 订阅队列的消费者都能拿到消息

    • 代码实现

        1. 声明交换机,队列
  @Configuration
	  public class FanoutConfig {
      /**
	       * 声明交换机
       * @return fanout类型交换机
	       */
	      @Bean
	      public FanoutExchange fanoutExchange(){
	          return new FanoutExchange("shifan.fanout");
	      }
	  
	 /**
	       * 第一个队列
	       * @return
	       */
	      @Bean
	      public Queue fanoutQueue1() {
	          return new Queue("fanout.queue1");
	      }
	  
	      /**
	       * 绑定队列1到交换机
	       * @param fanoutQueue1
	       * @param fanoutExchange
	       * @return
	       */
	      @Bean
	      public Binding bindingQueue1(Queue fanoutQueue1,FanoutExchange fanoutExchange){
	          return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
	      }
	      /**
	       * 第二个队列
	       * @return
	       */
	      @Bean
	      public Queue fanoutQueue2() {
	          return new Queue("fanout.queue2");
	      }
	  
	      /**
	       * 绑定队列2到交换机
	       * @param fanoutQueue2
	       * @param fanoutExchange
	       * @return
	       */
	      @Bean
	      public Binding bindingQueue2(Queue fanoutQueue2,FanoutExchange fanoutExchange){
	          return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
	      }
	  }
	  
    1. 发送消息
    	@Autowired   
    	private RabbitTemplate rabbitTemplate;
    	/**
         * 测试发送消息到fanout类型交换机
         */
        @Test
        public void testFanoutExchange(){
            //交换机名称
            String exchangeName = "shifan.fanout";
            //消息
            String message = "hello everyone";
            //发送消息到交换机
            rabbitTemplate.convertAndSend(exchangeName,"", message);
        }
    
    1. 接收消息
           @Component
    public class SpringRabbitListener {
    	//发布订阅模型消费者,fanout类型交换机
        @RabbitListener(queues = "fanout.queue1")
        public void listerFanoutQueue1(String msg){
            System.out.println("消费者1接收到Fanout消息:【" + msg + "】");
        }
        @RabbitListener(queues = "fanout.queue2")
        public void listerFanoutQueue2(String msg){
            System.out.println("消费者2接收到Fanout消息:【" + msg + "】");
        }
    }
    
    • 总结

      • 交换机的作用

        接收publisher发送的消息

        将消息按照规则路由到与之绑定的队列

        不能缓存消息,路由失败,消息丢失

        FanoutExchange的会将消息路由到每个绑定的队列

    Direct类型交换机
    • 模型图

请添加图片描述

  • 流程

      > 队列与交换机的绑定,不能是任意绑定了,而是要指定一个`RoutingKey`(路由key)
      >
      > 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 `RoutingKey`。
      >
      > Exchange不再把消息交给每一个绑定的队列,而是根据消息的`Routing Key`进行判断,只有队列的`Routingkey`与消息的 `Routing key`完全一致,才会接收到消息
    
    • 代码实现

        1. 基于注解声明交换机,队列
        @Component
      public class SpringRabbitListener {
      	//发布订阅模型消费者,direct类型交换机
          //基于注解的方式声明队列和交换机
          @RabbitListener(bindings = @QueueBinding(
                  value = @Queue(name = "direct.queue1")
                  ,exchange = @Exchange(name = "shifan.direct",type = ExchangeTypes.DIRECT)
                  ,key = {"red","yellow"}))
          public void listenDirectQueue1(String msg){
              System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
          }
      
          @RabbitListener(bindings = @QueueBinding(
                  value = @Queue(name = "direct.queue2")
                  ,exchange = @Exchange(name = "shifan.direct",type = ExchangeTypes.DIRECT)
                  ,key = {"red","blue"}))
          public void listenDirectQueue2(String msg){
              System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
          }
      }
      
        1. 发送消息
      	@Autowired
      	private RabbitTemplate rabbitTemplate;
          /**
           * 测试发送消息到direct类型交换机
           */
          @Test
          public void testDirectExchange(){
              //交换机名
              String exchangeName = "shifan.direct";
              //消息
              String message = "道阻且长,行则将至";
              //发送消息到direct交换机
              rabbitTemplate.convertAndSend(exchangeName, "yellow",message);
          }
      
    • 总结

      Direct交换机与Fanout交换机的差异

      Fanout交换机将消息路由给每一个与之绑定的队列

      Direct交换机根据RoutingKey判断路由给哪个队列

      如果多个队列具有相同的RoutingKey,则与Fanout功能类似

    Topic类型交换机
    • 模型图

请添加图片描述

- 与Direct的区别

  - Topic`类型的`Exchange`与`Direct`相比,都是可以根据`RoutingKey`把消息路由到不同的队列。只不过`Topic`类型`Exchange`可以让队列在绑定`Routing key` 的时候使用通配符!
  - `Routingkey` 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: `item.insert`

通配符规则:

#:匹配一个或多个词

*:匹配不多不少恰好1个词

举例:

item.#:能够匹配item.spu.insert 或者 item.spu

item.*:只能匹配item.spu

  • 代码实现

      1. 基于注解声明交换机,队列
    @Component
    public class SpringRabbitListener {
    	//发布订阅模型消费者,topic类型交换机
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "topic.queue1")
                ,exchange = @Exchange(name = "shifan.topic",type = ExchangeTypes.TOPIC)
                ,key = "china.#"))
        public void listenTopicQueue1(String msg){
            System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
        }
        @RabbitListener(bindings = @QueueBinding(
                value = @Queue(name = "topic.queue2")
                ,exchange = @Exchange(name = "shifan.topic",type = ExchangeTypes.TOPIC)
                ,key = "#.news"))
        public void listenTopicQueue2(String msg){
            System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
        }
    }
    
      1. 发送消息
        @Autowired
    	private RabbitTemplate rabbitTemplate;
    	/**
         * 测试发送消息到topic类型交换机
         */
        @Test
        public void testTopicExchange(){
            //交换机名
            String exchangeName = "shifan.topic";
            //消息
            String message = "道阻且长,行则将至";
            //发送消息到direct交换机
            rabbitTemplate.convertAndSend(exchangeName, "china.weather",message);
        }
    
消息转换器
  • SpringAMQP默认使用的消息序列化与反序列化方式

    • JDK序列化

    • 缺点

      数据体积过大

      有安全漏洞

      可读性差

  • 改用JSON方式实现序列化与反序列化

    • 代码实现

        1. 引入依赖
              <dependency>
                  <groupId>com.fasterxml.jackson.dataformat</groupId>
                  <artifactId>jackson-dataformat-xml</artifactId>
                  <version>2.9.10</version>
              </dependency>
      
        1. 将消息转换器加入到容器中
    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值