RabbitMQ 介绍与 SpringBootAMQP使用

一、MQ概述

异步通信的优点:

  • 耦合度低
  • 吞吐量提升
  • 故障隔离
  • 流量削峰

异步通信的缺点:

  • 依赖于Broker的可靠性、安全性、吞吐能力
  • 架构复杂,业务么有明显的流程线,不方便追踪管理

什么是的MQ
MQ(Message Queue),消息队列,就是放消息的队列。也是事件驱动架构中的Broker。

RabbitMQ ActiveMQ RocketMQ Kafka
公司/社区 Rabbit Apache 阿里 Apache
开发语言 Erlang Java Java Scala&Java
协议支持 AMQP、XMPP、
SMTP、STOMP
OpenWire、STOMP、
REST、XMPP、AMQP
自定义协议 自定义协议
可用性 一般
单机吞吐量 一般 非常高
消息延迟 微秒级 毫秒级 毫秒级 毫秒级
消息可靠性 一般 一般

二、RabbitMQ概述

1. RabbitMQ的结构和概念

  • Channel:操作MQ的工具
  • Exchange:路由消息到队列中
  • Queue:缓存消息
  • Virtual Host:虚拟主机,是对Queue、Exchange等资源的逻辑分组

RabbitMQ 介绍与 SpringBootAMQP使用_第1张图片
2. 常见消息模型

  • 基本消息队列(BasicQueue)
    RabbitMQ 介绍与 SpringBootAMQP使用_第2张图片

    • Publisher:消息发布者,将消息发送到队列Queue
    • Queue:消息队列,负责接受并缓存消息
    • Consumer:订阅队列,处理队列中的消息

    RabbitMQ 介绍与 SpringBootAMQP使用_第3张图片

  • 工作消息队列(WorkQueue)
    RabbitMQ 介绍与 SpringBootAMQP使用_第4张图片
    Work queues,也被称为(Task queues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息

    当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型,多个消费者共同进行消息处理,提高消费速度。

    RabbitMQ 介绍与 SpringBootAMQP使用_第5张图片

  • 发布订阅(Publish、Subscribe),根据交换机类型不同分为三种:

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

  • Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:

    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  • Consumer:消费者,与以前一样,订阅队列,没有变化

  • Queue:消息队列也与以前一样,接收消息、缓存消息。

    RabbitMQ 介绍与 SpringBootAMQP使用_第6张图片
    Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!

RabbitMQ 介绍与 SpringBootAMQP使用_第7张图片

  • Fanout Exchange: 广播
    RabbitMQ 介绍与 SpringBootAMQP使用_第8张图片
    在广播模式下,消息发送流程:

    - 1)  可以有多个队列
    - 2)  每个队列都要绑定到Exchange(交换机)
    - 3)  生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
    - 4)  交换机把消息发送给绑定过的所有队列
    - 5)  订阅队列的消费者都能拿到消息
    

RabbitMQ 介绍与 SpringBootAMQP使用_第9张图片

  • Direct Exchange:路由
    RabbitMQ 介绍与 SpringBootAMQP使用_第10张图片

    在Fanout模式中,一条消息会被所有订阅的队列都消费。但是,在某些场景下,希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
    RabbitMQ 介绍与 SpringBootAMQP使用_第11张图片

    在Direct模型下:

    • 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
    • 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey
    • Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
  • Topic Exchange:主题
    RabbitMQ 介绍与 SpringBootAMQP使用_第12张图片

Topic类型的ExchangeDirect相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符。

通配符规则:

  • #:匹配一个或多个词
  • *:匹配不多不少恰好1个词

如下图:

  • Queue1:绑定的是china.# ,因此凡是以 china.开头的routing key 都会被匹配到。包括china.news和china.weather
  • Queue2:绑定的是#.news ,因此凡是以 .news结尾的 routing key 都会被匹配。包括china.news和japan.news
    RabbitMQ 介绍与 SpringBootAMQP使用_第13张图片

3. RabbitMQ的安装

1、安装Erlang:RabbitMQ是用Erlang编写的,因此首先需要安装Erlang运行环境(注意Erlang与RabbitMQ的对应版本)。运行以下命令进行安装:sudo apt install erlang

2、在线拉取镜像:docker pull rabbitmq:3-management

3、运行以下命令来下载并启动RabbitMQ Docker镜像:docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management

4、浏览器访问RabbitMQ管理页面:http://IP:15672/(注意:若网页无法访问,可能是rabbitmq_management插件未启用)

5、进入sbin目录下,查看插件,命令:rabbitmq-plugins list
RabbitMQ 介绍与 SpringBootAMQP使用_第14张图片

6、 若 rabbitmq_management 插件未启用(状态无 * ),通过命令启用该插件:rabbitmq-plugins enable rabbitmq_management
RabbitMQ 介绍与 SpringBootAMQP使用_第15张图片

7、启用后,重新访问地址,用户名/密码默认:guest/guest
RabbitMQ 介绍与 SpringBootAMQP使用_第16张图片

三、SpringAMQP

AMQP,Adanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的开发标准,与语言和平台无关。

SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。

SpringAmqp的官方地址:https://spring.io/projects/spring-amqp

SpringAMQP提供了三个功能:

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听器模式,异步接收消息
  • 封装了RabbitTemplate工具,用于发送消息

1、使用SpringBootAMQP- SimpleQueue的步骤

  • 引入AMQP的Starter依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  • 配置RabbitMQ地址
logging:
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
spring:
  rabbitmq:
    host: 127.0.0.1
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        prefetch: 3 # 每次只能获取一条消息,处理完成才能获取下一个消息
  • 利用RabbitTemplate的convertAndSend方法
package com.example.rabbitmq_demo;

import org.junit.jupiter.api.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
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleQueue() {
        //queueName
        String queueName = "ty.simple.queue";

        //message
        String message = "hello world ";

        //send Message
        rabbitTemplate.convertAndSend(queueName, message);
    }

}

2、使用SpringBootAMQP- FanoutExchange的步骤

  • 创建Spring配置类,绑定交换机 - 队列
package com.example.rabbitmq_demo.consumer.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 FanoutConfig {

    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("ty.fanout");
    }

    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");
    }

    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     * 绑定交换机与队列
     */
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){
        return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
    }

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

  • 利用RabbitTemplate的convertAndSend方法
package com.example.rabbitmq_demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
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
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendFanoutExchange() {
        //exchangeName
        String exchangeName = "ty.fanout";

        //message
        String message = "hello world fanout";

        //send Message
        rabbitTemplate.convertAndSend(exchangeName, "", message);

    }

}

2、使用SpringBootAMQP- Direct的步骤

  • 基于注解来声明队列和交换机
package com.example.rabbitmq_demo.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ConsumerDemo {

    /**
     * 基于@Bean的方式声明队列和交换机比较麻烦,Spring还提供了基于注解方式来声明
     * 在consumer的SpringRabbitListener中添加两个消费者,同时基于注解来声明队列和交换机
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.direct.queue1"),
            exchange = @Exchange(name = "ty.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "green"}
    ))
    public void listenDirectQueue1(String msg){
        System.out.println("listener ty.direct.queue1 Get message : " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.direct.queue2"),
            exchange = @Exchange(name = "ty.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("listener ty.direct.queue2 Get message : " + msg);
    }
}
  • 通过convertAndSend发送消息,会根据的RoutingKey,将消息发送至指定队列。
package com.example.rabbitmq_demo;

import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
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
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Test
    public void testSendDirectExchange(){
        String exchangeName = "ty.direct";
        String message = "hello ty";
        rabbitTemplate.convertAndSend(exchangeName, "red", message);
    }
}

3、使用SpringBootAMQP- Tpic的步骤

  • 基于注解来声明队列和交换机
package com.example.rabbitmq_demo.consumer;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class ConsumerDemo {
    /**
     * Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key的时候使用通配符
     * @param msg
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.topic.queue1"),
            exchange = @Exchange(name = "ty.topic", type = ExchangeTypes.TOPIC),
            key = "ty.#"
    ))
    public void listenTopicQueue1(String msg){
        System.out.println("listener ty.topic.queue1 Get message : " + msg);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "ty.topic.queue2"),
            exchange = @Exchange(name = "ty.topic", type = ExchangeTypes.TOPIC),
            key = "#.tyty"
    ))
    public void listenTopicQueue2(String msg){
        System.out.println("listener ty.topic.queue2 Get message : " + msg);
    }
}
  • 根据RoutingKey通配符,发送到对应Queue
package com.example.rabbitmq_demo;

import org.junit.jupiter.api.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
class RabbitmqDemoApplicationTests {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSendTopicExchange(){
        String exchangeName = "ty.topic";
        String message = "hello ty";
        rabbitTemplate.convertAndSend(exchangeName, "ty.tyty", message);
    }

}

4、SpringBootAMQP对象序列化

SpringBootAMQP默认使用的是 x-java-serialized-object,JDK序列化数据体积过大、有安全漏洞,且可读性差。
RabbitMQ 介绍与 SpringBootAMQP使用_第17张图片
可通过配置JSON转换器,使用Json的方式做序列化和反序列化。

  • 引入jar
<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
    <version>2.9.10</version>
</dependency>
  • 配置类中增加Bean
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter();
}

RabbitMQ 介绍与 SpringBootAMQP使用_第18张图片

你可能感兴趣的:(rabbitmq,学习,笔记)