服务异步通讯实用篇-RabbitMQ学习笔记

RabbitMQ

  • 一、初识MQ
    • 1.同步通讯
    • 2.异步通讯
    • 3.MQ常见框架
  • 二、RabbitMQ快速入门
    • 1.RabbitMQ概述和安装
      • 1.1 下载镜像
      • 1.2 安装MQ
    • 2.常见消息模型
      • 2.1 HelloWorld案例
  • 三、SpringAMQP
    • 1.Basic Queue简单队列模型
    • 2.Work Queue工作队列模型
    • 3.发布(Publish)、订阅(Subscribe)
      • 3.1 发布、订阅模型-Fanout Exchange
      • 3.2 发布、订阅模型-DirectExchange
      • 3.3 发布、订阅模型-TopicExchange
    • 4.消息转换器

一、初识MQ

同步通讯和异步通讯

服务异步通讯实用篇-RabbitMQ学习笔记_第1张图片

1.同步通讯

同步调用存在的问题
微服务间基于Feign的调用就是属于同步方式,存在一些问题。
服务异步通讯实用篇-RabbitMQ学习笔记_第2张图片
服务异步通讯实用篇-RabbitMQ学习笔记_第3张图片
总结:

  1. 同步调用的优点:
    时效性较强,可以立即得到结果
  2. 同步调用的问题:
    耦合度高
    性能和吞吐能力下降
    有额外的资源消耗
    有级联失败问题

2.异步通讯

异步调用方案
异步调用常见实现就是事件驱动模式

事件驱动优势
优势一:服务解耦

服务异步通讯实用篇-RabbitMQ学习笔记_第4张图片
优势二:性能提升,吞吐量提高
服务异步通讯实用篇-RabbitMQ学习笔记_第5张图片
优势三:服务没有强依赖,不担心级联失败问题
优势四:流量削峰
服务异步通讯实用篇-RabbitMQ学习笔记_第6张图片
总结:

  1. 异步通信的优点:
  • 耦合度低
  • 吞吐量提升
  • 故障隔离
  • 流量削峰
  1. 异步通信的缺点:
  • 依赖于Broker的可靠性、安全性、吞吐能力
  • 架构复杂了,业务没有明显的流程线,不好追踪管理

3.MQ常见框架

什么是MQ

MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列,也就是事件驱动架构中的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概述和安装

RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址:地址

RabbitMQ的结构和概念

服务异步通讯实用篇-RabbitMQ学习笔记_第7张图片

单机部署:我们在CentOS7虚拟机中使用Docker来安装。

1.1 下载镜像

方式一:在线拉取

docker pull rabbitmq:3-management

方式二:从本地加载
压缩文件上传到虚拟机中后,使用命令加载镜像即可:

docker load -i mq.tar

1.2 安装MQ

执行下面的命令来运行MQ容器:

docker run \
 -e RABBITMQ_DEFAULT_USER=root \
 -e RABBITMQ_DEFAULT_PASS=root \
 --name mq \
 --hostname mq1 \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
 rabbitmq:3-management

总结:RabbitMQ中的几个概念:

  • channel:操作MQ的工具
  • exchange:路由消息到队列中
  • queue:缓存消息
  • virtualhost:虚拟主机,是对queue、exchange等资源的逻辑分组

2.常见消息模型

MQ的官方文档给出了5个MQ的Demo示例,对应了几种不同的用法:

  • 基本消息队列(BasicQueue)
  • 工作消息队列(WorkQueue)
    服务异步通讯实用篇-RabbitMQ学习笔记_第8张图片
  • 发布订阅(Publish、Subscribe),又根据交换机类型不同分为三种:
    • Fanout Exchange:广播
      服务异步通讯实用篇-RabbitMQ学习笔记_第9张图片

    • Direct Exchange:路由
      服务异步通讯实用篇-RabbitMQ学习笔记_第10张图片

    • Topic Exchange:主题

服务异步通讯实用篇-RabbitMQ学习笔记_第11张图片

2.1 HelloWorld案例

官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:

  • publisher:消息发布者,将消息发送到队列queue
  • queue:消息队列,负责接收并缓存消息
  • consumer:订阅队列,处理队列中的消息
    服务异步通讯实用篇-RabbitMQ学习笔记_第12张图片
    总结:

基本消息队列的消息发送流程:

1. 建立connection
2. 创建channel
3. 利用channel声明队列
4. 利用channel向队列发送消息

基本消息队列的消息接受流程:

1. 建立connection
2. 创建channel
3. 利用channel声明队列
4. 定义consumer的消费行为handleDelivery()
5. 利用channel将消费者与队列绑定

三、SpringAMQP

什么是SpringAMQP

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

1.Basic Queue简单队列模型

案例:利用SpringAMQP实现HelloWorld中的基础消息队列功能

流程如下:

  1. 在父工程中引入spring-amqp的依赖
  2. 在publisher服务中利用RabbitTemplate发送消息到simple.queue这个队列
  3. 在consumer服务中编写消费逻辑,绑定simple.queue这个队列

步骤一:引入AMQP依赖
因为publish和consumer服务都需要amqp

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-amqpartifactId>
        dependency>

步骤二:在publisher中编写测试方法,向simple.queue发送消息

  1. 在publisher服务中编写application.yml,添加mq连接信息:
在这里插入代码片
  1. 在publisher服务中新建一个测试类,编写测试方法:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;

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

}

总结:

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

AMQP发送消息的流程:

  1. 引入amqp的starter依赖
  2. 配置RabbitMQ地址
  3. 利用RabbitTemplate的convertAndSend方法

步骤三:在consumer中编写消费逻辑,监听simple.queue

  1. 在consumer服务中编写application.yml,添加mq连接信息:
spring:
  rabbitmq:
    host: 192.168.81.128    # 主机名
    port: 5672        # 端口
    virtual-host: / #虚拟主机
    username: root    # 用户名
    password: root    # 密码
  1. 在consumer服务中新建一个类,编写消费逻辑:
@Component
public class SpringRabbitListener {

    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueue(String msg){
        System.out.println("消费者接收到simple.queue的消息:【" + msg + "】");
    }
}

总结:SpringAMQP如何接收消息

  • 引入ampq的starter依赖
  • 配置RabbitMQ地址
  • 定义类,添加@Component注解
  • 类声明方法,添加@RabbitListener注解,方法参数就是消息
    注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能

2.Work Queue工作队列模型

Work queue,工作队列,可以提高消息处理速度,避免队列消息堆积
服务异步通讯实用篇-RabbitMQ学习笔记_第13张图片
案例:模拟WorkQueue,实现一个队列绑定多个消费者

步骤一:生者循环发送消息到simple.queue

  1. 在publisher服务中定义测试方法,每秒产生50条信息,发送到simple.queue
    @Test
    public void testSendMessageWorkQueue() throws InterruptedException {
    	//队列名称
        String queueName = "simple.queue";
        //消息
        String message = "hello,message__!";
        for (int i = 1; i <= 50; i++) {
			//发送消息
            rabbitTemplate.convertAndSend(queueName, message + i);			
            //避免发送太快
            Thread.sleep(20);
        }

    }
  1. 在consumer服务中定义两个消息监听者,都监听simple.queue队列
    @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. 消费者1每秒处理50条消息,消费者2每秒处理10条消息

消费预取限制
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:

spring:
  rabbitmq:
    host: 180.76.54.3    # 主机名
    port: 5672        # 端口
    virtual-host: / #虚拟主机
    username: root    # 用户名
    password: root    # 密码
    listener:
      simple:
        prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

总结:Work模型的使用:

  • 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
  • 通过设置prefetch来控制消费者预取的消息数量

3.发布(Publish)、订阅(Subscribe)

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

常见exchange类型包括:

  • Fanout:广播
  • Direct:路由
  • Topic:话题
    服务异步通讯实用篇-RabbitMQ学习笔记_第14张图片

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

3.1 发布、订阅模型-Fanout Exchange

Fanout Exchange 会将接收到的消息路由到每一个跟踪其绑定的queue
服务异步通讯实用篇-RabbitMQ学习笔记_第15张图片
案例:利用SpringAMQP演示FanoutExchange的使用
步骤一:在consumer服务中声明Exchange、Queue、Binding
SpringAMQP提供了交换机、队列、绑定关系的API,例如
服务异步通讯实用篇-RabbitMQ学习笔记_第16张图片
在consumer服务常见一个类,添加@Configuration注解,并声明FanoutExchange、Queue和绑定关系对象Binding,代码如下:

@Configuration
public class FanoutConfig {
    // itcast.fanout
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("itcast.fanout");
    }

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


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

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

    //绑定队列2到交换机
    @Bean
    public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {
        return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
    }
}

步骤二:在consumer服务的SpringRabbitListener类中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2

    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String msg) {
        System.out.println("消费者接收到fanout.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String msg) {
        System.out.println("消费者接收到fanout.queue2的消息:【" + msg + "】");
    }

步骤三:在publisher服务的SpringAmqpTest类中编写测试方法,向itcast.fanout发送消息

    @Test
    public void testSendFanoutExchange() {
        //交换机名称
        String exchangeName = "itcast.fanout";
        //消息
        String message = "hello,ever one!";
        //发送消息,参数分别是:交换机名称、RoutingKey(暂时为空),消息
        rabbitTemplate.convertAndSend(exchangeName, "", message);
    }

服务异步通讯实用篇-RabbitMQ学习笔记_第17张图片

总结:

  • 交换机的作用是什么?
    • 接收publisher发送消息
    • 将消息按照规则路由到与之绑定的队列
    • 不能缓存消息,路由失败,消息丢失
    • FanoutExchange的会将消息路由到每个绑定的队列
  • 声明队列、交换机、绑定关系的Bean是什么?
    • Queue
    • FanoutExchange
    • Binding

3.2 发布、订阅模型-DirectExchange

DirectExchange会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。

  • 每一个Queue都与Exchange设置一个BindingKey
  • 发布者发送消息时,指定消息的RoutingKey
  • Exchange将消息路由到BindingKey与消息RoutingKey一致的队列
    服务异步通讯实用篇-RabbitMQ学习笔记_第18张图片

案例:利用SpringAMQP演示DirectExchange的使用

步骤一:在consumer服务声明Exchange、Queue

  1. 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
  2. 并利用@RabbitListener声明Exchange、Queue、RoutingKey
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String msg) {
        System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"),
            exchange = @Exchange(name = "itcast.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String msg) {
        System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
    }

步骤二:在publisher服务发送消息到DirectExchange
在publisher服务的SpringAmqpTest类中添加测试方法:

    @Test
    public void testSendDirectExchange() {
        //交换机名称
        String exchangeName = "itcast.direct";
        //消息
        String message = "hello,red!";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName, "red", message);
    }

服务异步通讯实用篇-RabbitMQ学习笔记_第19张图片
总结:

  • 描述下Direct交换机与Fanout交换机的差异?
    • Fanout交换机将消息路由给每一个与之绑定的队列
    • Direct交换机根据RoutingKey判断路由给哪个队列
    • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
  • 基于@RabbitListener注解声明队列和交换机有哪些常见注解?
    • @Queue
    • @Exchange

3.3 发布、订阅模型-TopicExchange

TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 .分割。
Queue与Exchange指定BindingKey时可以使用通配符:
#:代表0个或多个单词
*:代指一个单词
服务异步通讯实用篇-RabbitMQ学习笔记_第20张图片

案例:利用SpringAMQP演示TopicExchange的使用

步骤一:在consumer服务声明Exchange、Queue

  1. 在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue
  2. 并利用@RabbitListener声明Exchange、Queue、RoutingKey
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
            key = {"china.#"}
    ))
    public void listenTopicQueue1(String msg) {
        System.out.println("消费者1接收到topic.queue1的消息:【" + msg + "】");
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"),
            exchange = @Exchange(name = "itcast.topic", type = ExchangeTypes.TOPIC),
            key = {"#.news"}
    ))
    public void listenTopicQueue2(String msg) {
        System.out.println("消费者2接收到topic.queue2的消息:【" + msg + "】");
    }

步骤二:在publisher服务发送消息到TopicExchange
在publisher服务的SpringAmqpTest类中添加测试方法:

    @Test
    public void testSendTopicExchange() {
        //交换机名称
        String exchangeName = "itcast.topic";
        //消息
        String message = "天气忽冷忽热!";
        //发送消息
        rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
    }

服务异步通讯实用篇-RabbitMQ学习笔记_第21张图片
总结:描述下Direct交换机和Topic交换机的差异?

  • 两者比较相似,最大的变化就是Topic交换机它是可以支持通配符的, 在bindingKey支持通配符,在RoutingKey时多个单词以 .分割,通配符可以是#代表 0个或多个,*代表恰好一个

4.消息转换器

案例:测试发送Object类型消息
说明:在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。
我们在consumer中利用@Bean声明一个队列:

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

在publisher中发送消息以测试:

    @Test
    public void testSendObjectQueue() {
        Map<String, Object> msg = new HashMap<>();
        msg.put("name", "张三");
        msg.put("age", 21 );
        rabbitTemplate.convertAndSend("object.queue", msg);
    }

消息转换

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

  • 我们在publisher服务引入依赖
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
        dependency>
  • 我们在publisher服务声明MessageConverter:
    @Bean
    public MessageConverter messageConverter() {
        return new Jackson2JsonMessageConverter();
    }
  • 我们在consumer服务引入Jackson依赖:
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
        dependency>
  • 我们在consumer服务定义MessageConverter:
    @Bean
    public MessageConverter jsonMessageConverter(){
        return new Jackson2JsonMessageConverter();
    }
  • 然后定义一个消费者,监听object.queue队列并消费消息:
    @RabbitListener(queues = "object.queue")
    public void listenObjectQueue(Map<String, Object> msg) {
        System.out.println("接收到消息:【" + msg + "】");
    }

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

  • 利用MessageConverter实现的,默认是JDK的序列化
  • 注意发送方与接收方必须使用相同的MessageConverter

你可能感兴趣的:(MQ,rabbitmq,学习,java)