SpringCloud(九)【MQ服务异步通信】

SpringCloud(一)【SpringCloud基础】
SpringCloud(二)【单体服务及远程调用】
SpringCloud(三)【Eureka注册中心】
SpringCloud(四)【Ribbon负载均衡】
SpringCloud(五)【Nacos注册中心】
SpringCloud(六)【Feign】
SpringCloud(七)【Gateway网关】
SpringCloud(八)【Docker】



一、同步调用和异步调用概念

  1. 同步调用
  • 优点
    • 时效性强,可以立即得到返回结果
  • 缺点
    • 耦合度高(修改业务都需要修改原来的代码)
    • 性能和吞吐能力下降(响应时间=每次调用的时间之和)
    • 有额外的资源消耗(在等待过程中无法释放请求占用的资源,高并发情况下会浪费系统资源)
    • 有级联失败问题(任何一个服务提供者出现问题则整体出现问题)
  1. 异步通信
    流量削峰
  • 优点
    • 耦合度低
    • 吞吐量提高
    • 故障隔离(一个服务提供者失败,不影响其他)
    • 流量削峰(上图)
  • 缺点
    • 依赖于Broker的可靠性、安全性、吞吐能力
    • 架构复杂了,业务没有明显的流程线,不好追踪管理
  1. MQ
    MQ (MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。
RabbitMQActiveMQRocketMQKafka
公司/社区RabbitApache阿里Apache
开发语言ErlangJavaJavaScala&Java
协议支持AMQP,XMPP,SMTP,STOMPOpenWire,STOMP,REST,XMPP,AMQP自定义协议自定义协议
可用性一般
单机吞吐率一般非常高
消息延迟微秒级毫秒级毫秒级毫秒以内
消息可靠性一般一般

二、RabbitMQ

RabbitMQ官网

(一)安装

  1. 拉取RabbitMQ镜像
docker pull rabbitmq:3-management
  1. 运行MQ容器
docker run \
 -e RABBITMQ_DEFAULT_USER=min \
 -e RABBITMQ_DEFAULT_PASS=123456 \
 --name mq \
 --hostname mq1 \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3-management
  1. 进入管理端口查看
    http://192.168.21.158:15672/
    在这里插入图片描述
    在这里插入图片描述

(二)RabbitMQ基础知识

  1. RabbitMQ的结构和概念
    在这里插入图片描述
  2. 概念
  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组
  1. 常见消息模型
    RabiitMQ官网查看
  • 基本消息队列(BasicQueue)
  • 工作消息队列(WorkQueue)
    在这里插入图片描述
  • 发布订阅(Publish、Subscribe),又根据交换机类型不同分为三种:
    • Fanout Exchange:广播
      在这里插入图片描述
    • Direct Exchange:路由
      在这里插入图片描述
    • Topic Exchange:主题
      在这里插入图片描述
  1. 简单实现测试
    使用单元测试模拟实现
  • 生产者
@Test
    public void testSendMsg2SimpleQueue() throws IOException, TimeoutException {
        // 1 建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1 设置连接参数:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.21.158");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("min");
        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 msg = "hello, mq!";
        channel.basicPublish("",queueName,null,msg.getBytes(StandardCharsets.UTF_8));

        System.out.println("消息发送成功:"+msg);

        //5 关闭通道和连接
        channel.close();
        connection.close();

    }
  • 消费者
 @Test
    public void testSendMsg2SimpleQueue() throws IOException, TimeoutException {
        // 1 建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1 设置连接参数:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.21.158");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("min");
        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 msg = new String(body);
                System.out.println("收到消息:"+msg);
            }
        });
        System.out.println("等待接收消息...");
    }

在这里插入图片描述

(三)SpringAMQP

  1. 什么是AMQP
    Advanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。
  2. 什么是SpringAMQP
    SpringAMQP官网
    Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。

(1)hellomq入门案例实现

在这里插入图片描述

  1. 引入依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 生产者和消费者配置文件
spring:
  rabbitmq:
    host: 192.168.21.158
    port: 5672
    username: min
    password: 123456
    virtual-host: /
  1. 生产者发送消息
 @Resource
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendMsg2SimpleQueue(){
        String queueName = "simple_queue";
        String msg = "spring使用方式1";
        rabbitTemplate.convertAndSend(queueName,msg);
    }
  1. 消费者消费消息
@Component
public class SpringRabbit {
    @RabbitListener(queues = "simple_queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者接收消息:"+msg);

    }
}

在这里插入图片描述

(2)Work Queue工作队列模型

Work queue,工作队列,可以提高消息处理速度,避免队列消息堆积。
在这里插入图片描述

  1. 前置条件
    假设生产者每秒生产50个消息,消费者1效率较高,消费者2效率较低,但是两个消费者加起来可以消费完生产者生产的消息。
  2. 生产者
    @Test
    public void testSendMsg2WorkQueue() throws InterruptedException {
        String queueName = "work_queue";
        String msg = "springAmqp的work_queue:";
        for (int i = 0; i < 50; i++) {
            rabbitTemplate.convertAndSend(queueName,msg + i);
            Thread.sleep(20);
        }
    }
  1. 消费者
    @RabbitListener(queues = "work_queue")
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收消息:"+msg + LocalDateTime.now());
        Thread.sleep(20);
    }

    @RabbitListener(queues = "work_queue")
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2接收消息:"+msg + LocalDateTime.now());
        Thread.sleep(200);
    }

在这里插入图片描述

运行消费者消费服务,观察消费情况。
预期:消费者1在一秒内处理完多的消息,消费者2在一秒内处理的消息不如消费者2多。
实际结果:消费者1和消费者2的消息处理数量一样多,且消费者2没有在一秒内处理完成。
原因:存在消息预取机制。
在这里插入图片描述

  1. 消息预取
    配置消费者预取数量
spring:
  rabbitmq:
    host: 192.168.21.158
    port: 5672
    username: min
    password: 123456
    virtual-host: /
    listener:
      direct:
        prefetch: 1 # 消费者预取数量

重启消费者服务后,达成预期目标。

(3)发布-订阅模型

发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange(交换机)。常见exchange类型包括:

  • Fanout:广播
  • Direct:路由
  • Topic:话题

注意:exchange负责消息路由,而不是存储,路由失败则消息丢失

在这里插入图片描述

FanoutExchange

在这里插入图片描述

  1. 消息队列绑定
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 RabbitMqConfig {

    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("min.fanout");
    }
    
    @Bean
    public Queue fanoutQueue1() {
        return new Queue("fanout_queue1", false);
    }
    
    @Bean
    public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1){
        return BindingBuilder.bind(fanoutQueue1)
                .to(fanoutExchange);
    }

    @Bean
    public Queue fanoutQueue2() {
        return new Queue("fanout_queue2", false);
    }

    @Bean
    public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2){
        return BindingBuilder.bind(fanoutQueue2)
                .to(fanoutExchange);
    }

}


  1. 生产者
@Test
    public void testSendMsg2FanoutQueue()  {
        String exchangeName = "min.fanout";
        String msg = "fanout的使用";
        rabbitTemplate.convertAndSend(exchangeName,"",msg);
    }
  1. 消费者
    // fanout
    @RabbitListener(queues = "fanout_queue1")
    public void listenFanoutQueue1(String msg){
        System.err.println("fanout_queue1接收消息:"+msg);
    }

    // fanout
    @RabbitListener(queues = "fanout_queue2")
    public void listenFanoutQueue2(String msg) {
        System.err.println("fanout_queue2接收消息:"+msg);
    }

在这里插入图片描述

DirectExchange

在这里插入图片描述

  1. 生产者
    @Test
    public void testSendMsg2DirectQueue()  {
        String exchangeName = "min.direct";
        String key = "red";
        String msg = "direct的使用:"+key;
        rabbitTemplate.convertAndSend(exchangeName,key,msg);
    }
  1. 消费者
    // direct
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "min.direct",type = ExchangeTypes.DIRECT),
            key = {"red","blue"}
    ))
    public void listenDirectQueue1(String msg) {
        System.err.println("direct.queue1接收消息:"+msg);
    }

    // direct
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "min.direct",type = ExchangeTypes.DIRECT),
            key = {"red","yellow"}
    ))
    public void listenDirectQueue2(String msg) {
        System.err.println("direct.queue2接收消息:"+msg);
    }

生产者依次生产key为:blue、yellow、red
在这里插入图片描述

TopicExchange

TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 . 分割。 Queue与Exchange指定BindingKey时可以使用通配符:

  • #:代指0个或多个单词
  • *:代指一个单词
    在这里插入图片描述
  1. 生产者
    // topic
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "min.topic",type = ExchangeTypes.TOPIC),
            key = {"china.#"}
    ))
    public void listenTopicQueue1(String msg) {
        System.err.println("topic.queue1接收消息:"+msg);
    }

    // topic
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"),
            exchange = @Exchange(name = "min.topic",type = ExchangeTypes.TOPIC),
            key = {"#.news"}
    ))
    public void listenTopicQueue2(String msg) {
        System.err.println("topic.queue2接收消息:"+msg);
    }
  1. 消费者
    @Test
    public void testSendMsg2TopicQueue()  {
        String exchangeName = "min.topic";
        String key = "china.news";
        String msg = "topic的使用:"+key;
        rabbitTemplate.convertAndSend(exchangeName,key,msg);
    }

依次发送key为:china.news、china.weather
在这里插入图片描述

(四)消息转换器

Spring的对消息对象的处理是org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。 如果要修改只需要定义一个MessageConverter 类型的Bean即可。推荐用JSON方式序列化,步骤如下:

  1. 引入依赖
<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
</dependency>
  1. 声明一个队列并且配置
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
    @Bean
    public Queue objectQueue() {
        return new Queue("object_queue");
    }
  1. 生产者
   @Test
    public void testSendMsg2ObjectQueue()  {
        String queueName = "object_queue";
        Map<String,String> res = new HashMap<>();
        res.put("name","name1");
        res.put("age","1");
        rabbitTemplate.convertAndSend(queueName,res);
    }
  1. 消费者
    @RabbitListener(queues = "object_queue")
    public void listenObjectQueue2(Map<String,String> msg) {
        System.err.println("object_queue接收消息:"+msg);
    }

在这里插入图片描述

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值