MQ

同步调用的优点:

  • 时效性较强,可以即得到结果

同步调用的问题:

  • 耦合度高

  • 性能和吞吐能力下降

  • 有额外的资源消耗

  • 有级联失败问题

异步通信的优点

  • 耦合度低

  • 吞吐量提升

  • 故障隔离

  • 流量小削峰

异步通信的缺点

  • 依赖于Broker的可靠性,安全性,吞吐能力

  • 架构复杂了,业务没有明显的流程线,不好追踪管理

 

MQ(MessageQueue)

  • 消息队列,字面来看就是存放消息的队列,也就是事件驱动架构中的Broker

  • 屏幕截图 2022-07-14 105045

RabbitMQ

RabbitMQ的几个概念
  • channel :操作MQ的工具

  • exchange : 路由消息到队列中

  • queue : 缓存消息

  • virtual host : 虚拟主机,是对queue,exchange等资源的逻辑分组

简单队列模型
  • 基本消息队列的消息发送流程

    1. 建立connection

    2. 创建channel

    3. 利用channel声明队列

    4. 利用channel向队列发送消息

  • 基本消息队列的消息接收流程

    1. 建立connection

    2. 创建channel

    3. 利用channel声明队列

    4. 定义consumer的消费行为handleDelivery()回调函数

    5. 利用channel将消费者与队列绑定

Spring AMQP

  • AMQP : 应用间消息通信的一种协议,与语言和平台无关

  • Spring AMQP 是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现

  • SpringAMQP发送消息的步骤

    1. 引入amqp的依赖

              <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-amqp</artifactId>
            </dependency>

       

    2. 配置RabbitMQ地址

      spring:
      rabbitmq:
        host: 192.168.142.129   #虚拟主机ip
        port: 5672  #端口
        username: root
        password: root
        virtual-host: /   #虚拟主机

       

    3. 利用RabbitTemplate的convertAndSend方法

      package cn.itcast.mq.springMq;

      import org.junit.Test;
      import org.junit.runner.RunWith;
      import org.springframework.amqp.rabbit.core.RabbitTemplate;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.context.SpringBootTest;
      import org.springframework.test.context.junit4.SpringRunner;

      @RunWith(SpringRunner.class)
      @SpringBootTest
      public class SpringMqTest {

         @Autowired
         private RabbitTemplate rabbitTemplate;

         @Test
         public void TestSendMessage2SimpleQueue(){
             String queueName = "simple queue";
             String message = " hello ,spring amqp";
             rabbitTemplate.convertAndSend(queueName,message);

        }
      }

       

  • SpringAMQP接收消息的步骤

    1. 引入amqp的依赖

    2. 配置RabbitMQ地址

    3. 编写接收类

      package cn.itcast.mq.listen;

      import org.springframework.amqp.rabbit.annotation.RabbitListener;
      import org.springframework.stereotype.Component;

      @Component
      public class SpringRabbitListener {
         @RabbitListener(queues = "simple.queue")
         public void listenMessage(String msg) throws InterruptedException{
             System.out.println(msg);
        }
      }

       

Work Queue

  • Work Queue 工作队列 ,可以提高消息处理速度,避免队列消息堆积

  • 多个消费者绑定到一个队列,同一个消息只会被一个消费者处理

  • 通过设置prefetch来控制消费者预取的消息数据量

发布(Publish),订阅(Subscribe)

  • 允许将同一消息发送给多个消费者,实现方式是加入exchange(交换机)

  • exchange:

    • 广播 Fanout

    • 路由 Direct

    • 话题 Topic

FanoutExchange

package cn.itcast.mq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FanoutExchangeConfig {
   //交换机
   @Bean
   public FanoutExchange fanoutExchange(){
       return new FanoutExchange("yang.l");
  }
   //队列1
   @Bean
   public Queue fanoutQueue1(){
       return new Queue("fanoutQueue1");
  }
   //两者绑定
   @Bean
   public Binding fanoutBinging1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
       return BindingBuilder
              .bind(fanoutQueue1)
              .to(fanoutExchange);
  }
   //队列1
   @Bean
   public Queue fanoutQueue2(){
       return new Queue("fanoutQueue2");
  }
   //两者绑定
   @Bean
   public Binding fanoutBinging2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
       return BindingBuilder
              .bind(fanoutQueue2)
              .to(fanoutExchange);
  }

}
    @RabbitListener(queues = "fanoutQueue1")
   public void listenMessageFanout1(String msg){
       System.out.println("消费者fanoutQueue1"+ msg + LocalDate.now());

  }
   @RabbitListener(queues = "fanoutQueue2")
   public void listenMessageFanout2(String msg){
       System.out.println("消费者fanoutQueue2"+ msg + LocalDate.now());

  }
@Test
   public void TestFanoutExchange() {
       String exchange = "yang.l";
       String message = " hello every one!";
       rabbitTemplate.convertAndSend(exchange,null,message);

  }

总结:

  • 交换机的作用:

    • 接收publisher发送的消息

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

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

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

  • 声明队列,交换机,绑定关系的Bean是什么?

    • Queue

    • FanoutExchange

    • Binding

DirectExchange

//direct
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name ="direct.queue1"),
           exchange = @Exchange(name = "direct.y",type = ExchangeTypes.DIRECT),
           key = {"red","blue"}
  ))
   public void listenMessageDirect1(String msg){
       System.out.println("消费者DirectQueue1"+ msg );

  }

   @RabbitListener(bindings = @QueueBinding(
           value = @Queue(name ="direct.queue2"),
           exchange = @Exchange(name = "direct.y",type = ExchangeTypes.DIRECT),
           key = {"red","yellow"}
  ))
   public void listenMessageDirect2(String msg){
       System.out.println("消费者DirectQueue2"+ msg));

  }

 

//Direct
   @Test
   public void TestDirectExchange() {
       String exchange = "direct.y";
       String message = " hello every one!";
       rabbitTemplate.convertAndSend(exchange,"red",message);

  }

 

TopicExchange

 

//Topic
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue("topic.queue1"),
           exchange = @Exchange(name="topic.y",type=ExchangeTypes.TOPIC),
           key = {"china.#"}
  ))
   public void listenMessageTopic1(String msg){
       System.out.println("消费者TopicQueue1"+ msg );

  }
   @RabbitListener(bindings = @QueueBinding(
           value = @Queue("topic.queue2"),
           exchange = @Exchange(name="topic.y",type=ExchangeTypes.TOPIC),
           key = {"#.news"}
  ))
   public void listenMessageTopic2(String msg){
       System.out.println("消费者TopicQueue2"+ msg );

  }

 

//Topic
   @Test
   public void TestTopicExchange() {
       String exchange = "topic.y";
       String message = " china.news dream everybody!";
       rabbitTemplate.convertAndSend(exchange,"china.news",message);

  }

 

消息转换器

  • SpringAMQP中的消息的序列化和反序列化是怎么实现的?

    • 利用MessageConverter实现的,默认是JDK的序列化方式

    • 注意发送方和接收方必须使用相同的MessageConverter

    • 消息是object,可以接受任意消息,,例对象

pom.xml

        <dependency>
          <groupId>com.fasterxml.jackson.core</groupId>
          <artifactId>jackson-databind</artifactId>
      </dependency>

 

Jackson 转换: MessageConverter

 @Bean
   public MessageConverter messageConverter(){
       return new Jackson2JsonMessageConverter();
  }

 

//object
   @Bean
   public Queue objectQueue(){
       return new Queue("object.queue");
  }

 

//object
   @RabbitListener(queues = "object.queue")
   public void listenMessageObject(Map<String,Object> msg){
       System.out.println("消费者objectQueue"+ msg + LocalDate.now());
  }

 

//object
   @Test
   public void TestObjectExchange() {
       String queueName = "object.queue";
       Map<String,Object> msg = new HashMap<>();
       msg.put("name","柳岩");
       msg.put("age",20);
       rabbitTemplate.convertAndSend(queueName,msg);

  }