Rabbit MQ

Rabbit MQ知识点总结

前置知识

微服务间通讯有两种方式::
同步通讯:就像打电话,需要实时响应。
异步通讯:就像发邮件,不需要马上回复。

同步通信

概念

1、Feign调用就属于同步方式,,虽然调用可以实时得到结果。
在这里插入图片描述

远程调用存在问题:

  1. 耦合度高:每次加入新请求都需要修改代码
  2. 性能吞吐能力下降:调用者需要等待服务提供者响应,如果调用链过长则响应时间等于每次调用的时间之和。
  3. 资源浪费有额外的资源消耗:调用链中的每个服务在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源
  4. 级联失败:如果服务提供者出现问题所有调用方都会跟着出问题,从而导致整个微服务群故障。

优缺点

2、优点:
时效性较强,可以立即得到结果
3、缺点:
A—>B, A—>C,如果B和C中有一个宕机,A业务绝对执行失败
A—>B ,A—>C,A接口业务执行时长=A接口本身业务时长+A—>B时间+A—>C花费时间
A—>B ,A—>C,如果需要加入A—>D,需要修改A接口的代码。

异步通信

1、概念:

使用MQ实现服务之间通信就是异步通信。

在这里插入图片描述

2、异步调用则可以避免feign远程调用的问题

我们以购买商品为例,用户支付后需要调用订单服务完成订单状态修改,调用物流服务,从仓库分配响应的库存并准备发货。
在事件模式中,支付服务是事件发布者(publisher),在支付完成后只需要发布一个支付成功的事件(event),事件中带上订单id。
订单服务和物流服务是事件订阅者(Consumer),订阅支付成功的事件,监听到事件后完成自己业务即可。
为了解除事件发布者与订阅者之间的耦合,两者并不是直接通信,而是有一个中间人(Broker)。发布者发布事件到Broker,不关心谁来订阅事件。订阅者从Broker订阅事件,不关心谁发来的消息。

3、优势:

(1)、故障隔离
服务没有直接调用,不存在级联失败问题
A---->MQ <----B
A---->MQ<----C
如果B和C中有一个宕机,不影响A。
(2)、提升效率
无需等待订阅者处理完成,响应更快速
A---->MQ <----B
A---->MQ <----C
A接口花费的时间=A接口本身业务时长 + 发送消息到MQ时间
(3)、服务解耦
耦合度极低,每个服务都可以灵活插拔,可替换
A---->MQ <----B
A---->MQ <----C
在以上业务基础上,添加积分。
只需要让积分服务从MQ获取消息即可。
(4)、调用间没有阻塞,不会造成无效的资源占用
(5)、流量削峰
不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
在这里插入图片描述

4、缺点:

  1. 依赖于MQ 需要依赖于Broker的可靠、安全、性能目前开源软件或云平台上 Broker 的软件是非常成熟的,比较常见的一种就是MQ技术。
  2. 架构复杂了,业务没有明显的流程线,不好管理
  3. 使用Feign进行远程调用,服务消费者可以获取提供方返回值,使用MQ不可以。

常见的MQ产品

1、什么是mq?

MQ (MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker

2、类型

在这里插入图片描述

3、如何选取以及优缺点?

追求可用性:Kafka、 RocketMQ 、RabbitMQ
追求可靠性:RabbitMQ、RocketMQ
追求吞吐能力:RocketMQ、Kafka
追求消息低延迟:RabbitMQ、Kafka
ActiveMQ
优点:单机吞吐量万级,时效性 ms 级,可用性高,基于主从架构实现高可用性,不容易丢失数据。
缺点:官方社区维护越来越少,高吞吐量场景较少使用。

Kafka:
优点:为大数据而生,吞吐量高,多运用于大数据领域的实时计算和日志采集场景。
缺点:社区更新较慢。
用途:适用于产生大量数据的互联网数据收集业务,建议大型公司使用,如果有日志采集功能,首选 kafka。
RokectMQ:
优点:Java 语言实现,单机吞吐量十万级,支持 10 亿级别的消息堆积。
缺点:支持的客户端语言不多。
用于: 金融互联网领域。

RabbitMQ:
优点:一个在 AMQP (高级消息队列协议)基础上完成的,主流的消息中间件之一,erlang 语言实现的高并发特性,吞吐量到万级,支持多种语言,社区活跃度高。
缺点:商业版收费。
用于:中小型公司优先选择。

RabbitMQ

1、概念:

基于Erlang语言开发的开源消息通信中间件。官网

2、安装

安装dockerhub中搜索镜像

1.拉取镜像
  • docker pull rabbitmq:3.9-management
  • 带management的这种版本是带管理控制台的
2.创建容器
docker run --name mq  -p 15672:15672  -p 5672:5672 -v mq-plugins:/plugins   -d -e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=123321   rabbitmq:3.9-management

		--name:容器名称
		-p端口映射,15672管理控制台端口,5672是RabbitMQ通信端口
		-v:数据卷挂载 /plugins是容器内部安装插件的目录
		-d 后台
		-e 设置环境变量,RABBITMQ_DEFAULT_USER和RABBITMQ_DEFAULT_PASS用来设置RabbitMq的用户名和密码
		注意:如果没有指定rabbitmq的用户名和密码,自带guest,guest
		
		docker run --name mq  -p 15672:15672  -p 5672:5672 -v mq-plugins:/plugins -d    rabbitmq:3.9-management
3.进入管理控制台
访问:http://192.168.200.128:15672/

Rabbit MQ的结构和概念

1、流程图示例

在这里插入图片描述

  • channel:操作MQ的工具
  • exchange:路由消息到队列中,接收来自生产者的消息,并将消息推送到队列中。决定消息是要推送给特定队列,还是推送给多个队列,还是丢弃。
  • queue:缓存消息、 存储消息
  • virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组

2、RabbitMQ工作原理

在这里插入图片描述
Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker。
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等
Connection:publisher/consumer 和 broker 之间的 TCP 连接Channel,如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCPConnection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的Connection 极大减少了操作系统建立 TCP connection 的开销
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout(multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

3、RabbitMQ四大概念

在这里插入图片描述

  • Exchange:交换机,负责消息路由 路由消息到队列中,接收来自生产者的消息,并将消息推送到队列中。决定消息是要推送给特定队列,还是推送给多个队列,还是丢弃。
  • Queue:队列,存储消息
  • publisher:生产者:负责产生数据
  • consumer:消费者 :负责产生数据

Spring AMQP

1、AMQP介绍概述

AMQP:(高级消息队列协议)是一种协议,类似http协议,amqp协议规定了,消息生产者和mq及消息消费者和mq通信的标准.

2、 Spring AMQP

	基于AMQP协议,定义的一套规范,spring基于amqp协议定义的一套api规范(spring-amqp);
	并且由rabbitmq官方对这一套规范进行了实现(spring-rabbit)

3、Spring-Rabbit

对spring-amqp规范实现

SpringAMQP如何发送消息?(利用SpringAMQP实现HelloWorld中的基础消息队列功能 测试

)
流程如下:
在父工程中引入spring-amqp的依赖
在publisher生产者服务中利用RabbitTemplate发送消息到simple.queue这个队列
在consumer消费者服务中编写消费逻辑,绑定simple.queue这个队列

  1. 引入amqp的starter依赖
<!--AMQP依赖,包含RabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 配置RabbitMQ地址 在publisher服务中编写application.yml,添加mq连接信息:
spring:
  rabbitmq:
    host: 192.168.150.101 # 主机名
        port: 5672 # 端口
        virtual-host: / # 虚拟主机 
        username: itcast # 用户名
        password: 123321 # 密码
  1. 利用RabbitTemplate的convertAndSend方法
@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);
                                       }
}

Rabbit MQ 常见消息模型

1、Spring AMQP:基于AMQP协议,定义的一套规范,spring基于amqp协议定义的一套api规范(spring-amqp);并且由rabbitmq官方对这一套规范进行了实现(spring-rabbit)

2、Spring-Rabbit:对spring-amqp规范实现
3、消费预取限制
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:


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

一、基本、简单消息队列 BasicQueue

在这里插入图片描述

1、实现步骤:利用SpringAMQP实现简单工作模式

  1. 创建并运行容器
docker run --name mq  -p 15672:15672  -p 5672:5672 -v mq-plugins:/plugins \  
 -d -e RABBITMQ_DEFAULT_USER=itcast -e RABBITMQ_DEFAULT_PASS=123321   rabbitmq:3.9-management

账号密码 itcast 123321 http://192.168.247.128:15672/#/exchanges 虚拟机的ip和端口

  1. 创建父工程,引入amqp的starter依赖
<!--AMQP依赖,包含RabbitMQ-->
 <dependencies> 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
 </dependencies> 
  1. 创建并编写生产者producer 创建模块

    (1)、 引入amqp的starter依赖

<!--AMQP依赖,包含RabbitMQ-->
 <dependencies> 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
 </dependencies> 

(2)、 配置RabbitMQ地址 在publisher服务中编写application.yml,添加mq连接信息:

#配置rabbitmq地址
spring:
  rabbitmq:
    host: 192.168.247.128 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码

(3) 编写启动类

@SpringBootApplication
public class ProdcuerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProdcuerApplication.class,args);
    }
}

(4)、注入RabbitTemplate,调用编写测试类进行测试 查看消息队列是否创建

convertAndSend(String exchange,String routingKey,Object message)   交换机(不写就是默认)   消息队列名称  发送的消息
//默认交换机他是根据消息队列名称将消息路由到指定队列
//routingKey:写队列名称
//rabbitTemplate.convertAndSend(",",“simple.queue”,"hello,world")
如果发送消息到默认交换机可以使用两个参数的方法
convertAndSend(String routingKey,Object message)
convertAndSend(“simple.queue”,"hello,world")
@SpringBootTest(classes = ProdcuerApplication.class(启动类的class文件))
public class ProdcuterTest {
    //注入
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Test
    public void testSimpleRabbitTemplate() {
        // 需要手动创建一个消息队列  消息队列名称
        String queueName = "simple.queue";
        //简单消息
        String message = "hello, spring amqp!";
        //调用
        rabbitTemplate.convertAndSend("", queueName, message);
    }

4.编写消费者代码 创建模块

(1)、 引入amqp的starter依赖
<!--AMQP依赖,包含RabbitMQ-->
 <dependencies> 
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
 </dependencies> 

(2)、 配置RabbitMQ地址 在publisher服务中编写application.yml,添加mq连接信息:

#配置rabbitmq地址
spring:
  rabbitmq:
    host: 192.168.247.128 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码

(3) 编写启动类

@SpringBootApplication
public class ProdcuerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProdcuerApplication.class,args);
    }
}

(4)确定建立了消息队列之后,运行消费者服务进行读取 查看消息队列

@Component
@RabbitListener(queues="队列名称")
public void xxx(Object message) {
}
@Component
@Slf4j
public class SpringRabbitListener {
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        System.out.println("spring 消费者接收到消息 :【" + msg + "】");  } }

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

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

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

  • 建立connection
  • 创建channel
  • 利用channel声明队列
  • 利用channel向队列发送消息

4、基本消息队列的消息接收流程:

  • 建立connection
  • 创建channel
  • 利用channel声明队列
  • 定义consumer的消费行为handleDelivery()
  • 利用channel将消费者与队列绑定

二、 工作消息队列 WorkQueue

1、关系(竞争关系)作用:可以提高消息处理速度,避免队列消息堆积。

启动多个消费者实例,让多个消费者监听同一个队列即可。
他们是竞争关系。 一个消息从生产者到消费者是串行的
在这里插入图片描述

2、在简单工作队列的基础上,复制一层消费者,监听消息队列即可,但需要更改一些配置

(1)、在简单工作模式的基础上再复制一次消费者代码
启动类 配置文件修改模块、9088 9089的端口名字、队列名称不变、监听同名的消息队列

@Component
@RabbitListener(queues="队列名称")
public void xxx(Object message) {
}
@Component
@Slf4j
public class SpringRabbitListener {
    @RabbitListener(queues = "simple.queue")
    public void listenSimpleQueueMessage(String msg) throws InterruptedException {
        System.out.println("spring 消费者接收到消息 :【" + msg + "】");  } }

(2)、在进行测试 先测试添加 确认添加完之后在进行测试 可以启动消费者 可以在控制台 发消息 消费者接受消息控制台查看

三、 发布( Publish )、订阅( Subscribe ):Fanout:广播、Direct:路由、Topic:通配符(交换机类型)

发布订阅的特点:
生产者发送消息到某个交换机(不能是默认交换机,默认交换及名字为空)
由交换机将消息转发给绑定它的所有消息队列
消费者可以从每一个消息队列中获取消息

在这里插入图片描述

1、前置了解

可以看到,在订阅模型中,多了一个exchange角色,而且过程略有变化:

  • Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
  • Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有以下3种类型:
    • Fanout:广播,将消息交给所有绑定到交换机的队列
    • Direct:定向,把消息交给符合指定routing key 的队列
    • Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
  • Consumer:消费者,与以前一样,订阅队列,没有变化
  • Queue:消息队列也与以前一样,接收消息、缓存消息。

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

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

四、广播发布订阅 Fanout Exchange:(FanoutExchange的会将消息路由到每个绑定的队列)

1、实现步骤

(1)、在工作模式的基础上单独创建模块rubbitmq-common,

a、引入依赖

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

b、只是创建交换机、消息队列、以及绑定,无需在创建启动类
直接在rabbitmq中创建fanout类型(发布订阅类型)声明交换机,名字为itcast.fanout。
创建多个消息队列,我们以2个为例,名称分表为fanout.queue1和fanout.queue2。
让交换机和2个队列绑定。

@Configuration
public class PublishSubscribeConfig {
    /**
     * 声明交换机
     */
    @Bean
    public Exchange createFanoutExchange() {
        return ExchangeBuilder.fanoutExchange("itcast.fanout").build();
    }
    /**
     * 声明队列
     */
    @Bean
    public Queue createFanoutQueue1() {
        //return new Queue("fanout.queue1",true);
        return QueueBuilder.durable("fanout.queue1").build();
    }

    @Bean
    public Queue createFanoutQueue2() {
        //return new Queue("fanout.queue1",true);
        return QueueBuilder.durable("fanout.queue2").build();
    }
    /**
     * 绑定
     */
    @Bean
    public Binding bindFanoutQueue1ToItcastFanout(@Qualifier("createFanoutQueue1") Queue queue,
                                                  @Qualifier("createFanoutExchange") Exchange exchange) {
        //交换机和队列在绑定的时候指定routingKey
        //发布订阅工作模式,交换机和队列绑定的时候routingkey固定写""
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }

    @Bean
    public Binding bindFanoutQueue2ToItcastFanout(@Qualifier("createFanoutQueue2") Queue queue,
                                                  @Qualifier("createFanoutExchange") Exchange exchange) {
        //交换机和队列在绑定的时候指定routingKey
        //发布订阅工作模式,交换机和队列绑定的时候routingkey固定写""
        return BindingBuilder.bind(queue).to(exchange).with("").noargs();
    }
}

c,在publisher生产者中编写测试方法,向itcast.fanout发送消息。前提是要引入绑定消息,交换机等的模块也就是步骤b

生产者测试"  启动后可以直接查看  先启动生产者  在启动消费者 进行发送消息 和接收消息
"     //交换机  消息队列  消息  测试方法  
  @Test
    public void testSendItcastFanout() {
        rabbitTemplate.convertAndSend("itcast.fanout","","i love gg");
    }

d,搭建2个消费者从不同的队列获取消息,查看是否都可以获取到
前提是要引入绑定消息,交换机等的模块也就是步骤b

@Component
@Slf4j
public class FanoutQueue1Listener {
    /**
     * 监听队列1
     * @param message
     */
    @RabbitListener(queues = "fanout.queue1")
    public void receiveMessage(String message) {
        //处理消息
        log.info("接受到的消息是:{}",message);
    }
}
@Component
@Slf4j
public class FanoutQueue2Listener {
    /**
     * 监听队列2
     * @param message
     */
    @RabbitListener(queues = "fanout.queue2")
    public void receiveMessage(String message) {
        //处理消息
        log.info("接受到的消息是:{}",message);
    }
}

2、交换机的作用是什么?

  • 接收publisher发送的消息
  • 将消息按照规则路由到与之绑定的队列
  • 不能缓存消息,路由失败,消息丢失
  • FanoutExchange的会将消息路由到每个绑定的队列

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

  • Queue
  • FanoutExchange
  • Binding

五、路由 Direct Exchange:(指定的队列)

1、概念

生产者发送消息到交换机,交换机会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。

  • 每一个Queue都与Exchange设置一个BindingKey ,也可以多个
  • 发布者发送消息时,指定消息的RoutingKey
  • Exchange将消息路由到BindingKey与消息RoutingKey一致的队列

2、图解

在这里插入图片描述

3、实现步骤

将交换机 消息这些配置类模块引入到生产者以及消费者模块中
配置自动装配:
如果交换机 消息 绑定在另一个模块中,启动的时候没有扫描到这个模块

  • 因此需要在启动类上+import注解 将配置类的class文件写到启动类上
  • 在resources下创建META-INF:包
    spring.factories:
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    cn.itcast.config.PublishSubscribeConfig,cn.itcast.config.DirectConfig

org.springframework.boot.autoconfigure.EnableAutoConfiguration=需要自动装配类的全类名
微服务启动之后会自动扫描

a 、在单独创建的模块中创建配置类(与广播类似)DirectConfig 并且声明交换机.消息队列,绑定, 启动会报错 因为没有装配

@Configuration
public class DirectConfig {
    /**
     * 声明交换机创建direct类型(路由类型)交换机,名字为itcast.direct。
     *
     * @return
     */
    @Bean
    public Exchange createDirectExchange() {
        return ExchangeBuilder.directExchange("itcast.queue").build();
    }

    /**
     * 创建多个消息队列,我们以2个为例,
     * 名称分表为direct.queue1和direct.queue2。
     *
     * @return
     */
    @Bean
    public Queue createDirectQueue1() {
        return QueueBuilder.durable("direct.queue1").build();
    }

    @Bean
    public Queue createDirectQueue2() {
        //durable  持久化  如果不持久 关闭后就没有了消息队列
        return QueueBuilder.durable("direct.queue2").build();
    }

    /**
     * 绑定  第一个绑定了
     */
    @Bean
    public Binding bingingDirectQueue1ToItcastDirectBlue(@Qualifier("createDirectQueue1") Queue queue,
                                                         @Qualifier("createDirectExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("blue").noargs();
    }
    @Bean
    public Binding bingingDirectQueue1ToItcastDirectRed(@Qualifier("createDirectQueue1") Queue queue,
                                                        @Qualifier("createDirectExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("red").noargs();
    }
    @Bean
    public Binding bingingDirectQueue2ToItcastDirectBlue(@Qualifier("createDirectQueue2") Queue queue,
                                                         @Qualifier("createDirectExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("yellow").noargs();
    }
    @Bean
    public Binding bingingDirectQueue2ToItcastDirectRed(@Qualifier("createDirectQueue2") Queue queue,
                                                        @Qualifier("createDirectExchange") Exchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("red").noargs();
    }

}


b 、生产者发消息

    /**
     * 发送路由模式  给itcast.direct交换机 blue消息队列  后边跟发送的消息
     */
    @Test
    public void testSendDirect() {
        rabbitTemplate.convertAndSend("itcast.direct", "blue", "i love  dd");
    }

c 、消费者获取消息

@Component
@Slf4j
public class Direct2Listener {
    /**
     * 监听队列2
     * @param message
     */
    @RabbitListener(queues = "direct.queue2")
    public void receiveMessage(String message) {
        //处理消息
        log.info("接受到的消息是:{}",message);
    }
}
@Component
@Slf4j
public class Direct1Listener {
    /**
     * 监听队列1
     * @param message
     */
    @RabbitListener(queues = "direct.queue1")
    public void receiveMessage(String message) {
        //处理消息
        log.info("接受到的消息是:{}",message);
    }
}

d 、注意模块引入 以及自动配置

路由模式和发布订阅模式的区别:

交换机必须是路由类型的交换机(DirectExchange)
交换机和队列绑定的时候要指定routingKey(路由规则)
生产者发送消息的时候要指定routingKey.

发布订阅模式的特点

生产者发送消息到某个交换机(不能是默认交换机)
由交换机将消息转发给绑定它的所有消息队列
消费者可以从每一个消息队列中获取消息

描述下Direct交换机与Fanout交换机的差异?

  • Fanout交换机将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列
  • 如果多个队列具有相同的RoutingKey,则与Fanout功能类似

基于@RabbitListener注解声明队列和交换机有哪些常见注解?@Queue@Exchange

五、通配符 Topic Exchange:(可以根据#:代指0个或多个单词,*:代指一个单词,进行通配路由,)

1、 概念;

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

2、特点 # (代指0个或多个单词) *(代指一个单词)

Queue与Exchange指定BindingKey时可以使用通配符:
#:代指0个或多个单词
*:代指一个单词

3、流程图

在这里插入图片描述
生产者发送消息时候的 rountingkey 是以下:
china.news 代表有中国的新闻消息;
china.weather 代表中国的天气消息;
japan.news 则代表日本新闻
japan.weather 代表日本的天气消息;
绑定队列 rountingkey 的时候:
bindingKey: china.#
bindingKey: japan.#
bindingKey: #.news
bindingKey: #.weather
此时就可以完成
china.news 代表有中国的新闻消息; 通配 bindingKey: china.# 、 #.news
china.weather 代表中国的天气消息; 通配 bindingKey: china.# 、 #.weather
japan.news 则代表日本新闻 ; 通配 bindingKey: japan.#、#.news
japan.weather 代表日本的天气消息; 通配 bindingKey: japan.#、#.weathe
从而实现发送一个消息,到数个统配的队列 注意:除了两边的,还有中间的通配符

4、具体实现

在这里插入图片描述

通配符模式和与路由模式比较类似,交换机和队列绑定的时候routingKey可以使用通配符。
可以使用2种通配符。
#:代指0个或多个单词
*:代指一个单词

  • 生产者发送消息,如果routingKey是hello.email.world这个消息被交换机路由到那个队列。
  • 生产者发送消息,如果routingKey是phone.hello.world.mm这个消息被交换机路由到那个队列。
  • 生产者发送消息,如果routingKey是phone.email.good这个消息被交换机路由到那个队列。

a、实现思路如下:
在rabbitmq中创建topic类型(通配符类型)交换机,名字为itcast.topic。
创建多个消息队列,我们以2个为例,名称分别为topic.queue1和topic.queue2。
让交换机和2个队列绑定,并且指定routingkey。
在publisher中编写测试方法,向itcast.topic发送消息。
搭建2个消费者从不同的队列获取消息,查看。
@RabbitListener在消费端需要先启动消费端 同时会消费掉信息

b、可以使用 路由模式的,只需要修改,rountingkey 其他步骤一样
(1)、创建父模块,只需要 pom文件,其他子模块已经引入了amqp

    <modules>
        <module>producer</module>
        <module>comsumer1</module>
        <module>rubbitmq-common</module>
        <module>comsumer2</module>
    </modules>

(2)、自定义一个配置模块。定义一个配置类,引入amqp依赖,并且手写绑定的通配符的交换机、消息、队列)(无需启动类)配置自动配置文件 META-INF:包 包下创建 spring.factories, org.springframework.boot.autoconfigure.EnableAutoConfiguration=需要自动装配类的全类名

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

配置自动配置,微服务启动时后,直接扫描配置类:有两个方法,
1:在生产者启动类使用@Import 注解,(括号中写配置类类名.class)
2: - 在resources下创建META-INF:包 包下创建 spring.factories,文件 配置配置类的全路径名
spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=需要自动装配类的全类名
微服务启动之后会自动扫描

(3)、创建生产者、消费者模块,引入依赖,配置,还有启动类,编写启动类以及发送消息+监听器,

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
server:
  port: 9088

  #配置rabbitmq地址
spring:
  rabbitmq:
    host: 192.168.247.128 # 主机名
    port: 5672 # 端口
    virtual-host: / # 虚拟主机
    username: itcast # 用户名
    password: 123321 # 密码

生产者

@SpringBootTest(classes = ProdcuerApplication.class)
    /**
     * 发送通配符模式  给itcast.topic交换机 blue消息队列  后边跟发送的消息
     */
    @Test
    public void testSendTopic1() {
        rabbitTemplate.convertAndSend("itcast.direct", "hello.email.word", "i love  red");
    }
    @Test
    public void testSendTopic2() {
        rabbitTemplate.convertAndSend("itcast.direct", "phone.sd", "i love  blue");
    }
    }

(4)、消费者取消息与路由交换的一致,监听的时候绑定队列即可
c、第二种实现思路:不另外创建交换机 消息队列,以及绑定的rountingkey的
,直接在消费者端进行,生产者发送消息即可

//生产者测试类中发送消息
@SpringBootTest(classes = 启动类.class)
public class ProdcuterTest {
  /**
     * 发送通配符模式  给itcast.topic交换机 blue消息队列  后边跟发送的消息
     */
    @Test
    public void testSendTopic1() {
        rabbitTemplate.convertAndSend("itcast.direct", "hello.email.word", "i love  red");
    }
    @Test
    public void testSendTopic2() {
        rabbitTemplate.convertAndSend("itcast.direct", "phone.sd", "i love  blue");
    }
}

消费者模块1

@Slf4j
@Component
public class TopicQueue1Listener {
    @RabbitListener(bindings = {
           @QueueBinding(value=@Queue(name ="topic.queue1",durable = "true"),
                   exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
                   key = {"*.email.*"})
    })
    public void TopicQueueListener(String message){
        log.info("接受到的消息是:{}",message);
    }
}

消费者模块2

@Slf4j
@Component
public class TopicQueue2Listener {
    @RabbitListener(bindings = {
           @QueueBinding(value=@Queue(name ="topic.queue2",durable = "true"),
                   exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
                   key = {"phone.#"})
    })
    public void TopicQueueListener(String message){
        log.info("接受到的消息是:{}",message);
    }
}

d、

描述下Direct交换机与Topic交换机的差异?

Topic交换机接收的消息RoutingKey必须是多个单词,以 . 分割
Topic交换机与队列绑定时的bindingKey可以指定通配符
#:代表0个或多个词
*:代表1个词
Direct根据RoutingKey路由到(指定的队列)

配置自动装配:

如果交换机 消息 绑定在另一个模块中,启动的时候没有扫描到这个模块,

  • 因此需要在启动类上+import注解 将配置类的class文件写到启动类上
  • 在resources下创建META-INF:包 包下创建 spring.factories,文件 配置配置类的全路径名
    spring.factories:
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    cn.itcast.config.PublishSubscribeConfig,cn.itcast.config.DirectConfig

org.springframework.boot.autoconfigure.EnableAutoConfiguration=需要自动装配类的全类名
微服务启动之后会自动扫描

引用模块与自动配置不牵扯,自动配置是,启动类启动的时候就加载自动配置的类

消息转换器

1、前置知识 SpringAMQP底层默认使用JDK的ObjectOutputStream 我们看不懂

发送对象类型的消息,SpringAMQP底层默认使用JDK的ObjectOutputStream对对象类型消息进行序列化。将消息转化为字节数组。
底层支持的是序列化接口( Serializable)、String、 byte[]、
而JDK的ObjectOutputStream对对象实现序列化,要求类实现序列化接口。
使用JDK的序列器对对象进行序列化,是将对象序列化成字节数组,我们是看不懂的。

Spring AMQP也支持JSON的序列化,可以将对象类型消息转化为json字符串,发送到rabbitmq.
消费者获取到json字符串消息后再将消息反序列化为java对象。
1、使用简单工作模式做演示 在生产者中
在这里插入图片描述
如果user类没有实现Serializable 序列化接口发送消息的时候会报错,
解决办法:
1、引入依赖 + 声明MessageConverter:

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

2 在消费者微服务声明bean 或者在公共类中

@Configuration
public class MessageConverterConfig
@Bean
public MessageConverter jsonMessageConverter(){
    return new Jackson2JsonMessageConverter(); 
    }

如果实在公共类还需要 自动装配

  • 因此需要在启动类上+import注解 将配置类的class文件写到启动类上
  • 在resources下创建META-INF:包 包下创建 spring.factories,文件 配置配置类的全路径名
    spring.factories:
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    cn.itcast.config.PublishSubscribeConfig,cn.itcast.config.DirectConfig

org.springframework.boot.autoconfigure.EnableAutoConfiguration=需要自动装配类的全类名

在这里插入图片描述

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值