RabbitMQ01

RabbitMQ01

一、MQ简介

1、同步和异步通讯
微服务间的通讯有同步和异步两种方式:
1、同步通讯:需要实时响应。
2、异步通讯:不需要实时响应。
2、同步调用的问题
微服务间基于Feign的调用就属于与同步方式,存在一些问题。
1、耦合度高: 每次加入新的需求,都要修改原来的代码。
2、性能下降: 调用者需要等待服务提供响应者,如果调用链过长,则响应的时间等于每次调用的时间之和。
3、资源浪费: 调用链中的每个服务都在等待响应过程中,不能释放请求占用的资源,高并发场景下会极度
   浪费系统资源。
4、级联失败: 如果服务提供者出现问题,所有调用方都会跟着出现问题,会迅速导致整个微服务集群故障。
3、异步调用
1、简介
1、异步调用常见实现就是事件驱动模式。
2、事件是指系统硬件或软件的状态出现任何重大改变。
2、优点
1、耦合度低 : 每个服务都可以灵活插拔,可替换
2、吞吐量提升 : 无需等待订阅者处理完成,响应更快速
3、故障隔离 : 服务没有直接调用,不存在级联失败问题
4、流量削峰 : 不管发布事件的流量波动多大,都由Broker接收,订阅者可以按照自己的速度去处理事件
5、调用间没有阻塞,不会造成无效的资源占用
3、缺点
1、依赖于Broker的可靠性、安全性、吞吐能力
2、架构复杂,业务没有明显的流程线,不好追踪管理
4、MQ简介
MQ(MessageQueue),消息队列,存放的消息的队列。也就是事件驱动架构中的Broker

在这里插入图片描述

1、追求可用性:KafkaRocketMQRabbitMQ
2、追求可靠性:RabbitMQRocketMQ
3、追求吞吐能力:RocketMQKafka
4、追求消息低延迟:RabbitMQKafka

二、RabbitMQ的概述

一、MQ的基本结构

在这里插入图片描述

RabbitMQ中的一些角色:
	1、publisher:生产者
	2、consumer:消费者
	3、exchange:交换机,负责消息路由
	4、queue:队列,存储消息
	5、virtualHost:虚拟主机,隔离不同租户的exchange、queue、消息的隔离

三、RabbitMA安装

1、启动容器
systemctl start docker
2、获取mq.tar的方式
1、在线拉取
docker pull rabbitmq:3.8-management
2、从本地加载
上传到虚拟机中后,使用命令加载镜像即可:
docker load -i mq.tar
3、安装
执行下面的命令来运行MQ容器:
docker run \
 -e RABBITMQ_DEFAULT_USER=cwl \
 -e RABBITMQ_DEFAULT_PASS=root \
 --name mq \
 --hostname mq1 \
 -p 15672:15672 \
 -p 5672:5672 \
 -d \
rabbitmq:3.8-management
15672 浏览器访问端口
5672 java代码连接的时候使用的端口
docker run 跑一个容器
-e 环境变量
-d 后台启动
rabbitmq 镜像名字
3.8-management 版本

五、RabbitMQ消息模型

1、常见的RabbitMQ消息模型

1、基本消息队列(BasicQueue)
2、工作消息队列(WorkQueue)
3、发布订阅(Publish、Subscribe),又根据交换机类型不同分为三种:
1Fanout Exchange:广播
2Direct Exchange:路由
3Topic Exchange:主题

2、基于最基础的消息队列模型的实现

基于最基础的消息队列模型来实现的,只包括三个角色:
	1、publisher:消息发布者,将消息发送到队列queue
	2、queue:消息队列,负责接受并缓存消息
	3、consumer:订阅队列,处理队列中的消息

在这里插入图片描述

1、publisher实现

public class PublisherTest {
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory factory = new ConnectionFactory();
        //1.1 设置连接参数 主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.211.128");
        factory.setPort(5672);
        // 虚拟主机对资源做逻辑的分组 代表 用 / 表示
        factory.setVirtualHost("/");
        factory.setUsername("cwl");
        factory.setPassword("root");
        //1.2 建立连接
        Connection connection = factory.newConnection();
        //2. 创建通道Channel
        Channel channel = connection.createChannel();
        //3. 创建队列
        String queueName ="simple.queue1";
        // 声明  参数一 队列名称, 参数二 是否持久化  参数三 是否独占 
        // 参数四 是否自动删除 参数五 额外的参数
        channel.queueDeclare(queueName,false,false,false,null);
        //4. 发送消息
        String message="hello rabbitmq!";
        // 执行发送消息的动作 参数一 交换机 参数二 队列名 参数三 是否带参数 
        // 参数四  发送消息的内容
        channel.basicPublish("",queueName,null,message.getBytes());
        System.out.println("发送消息成功:【"+message+"】");
        //5. 关闭通道
        channel.close();
        connection.close();
    }
}

2、consumer实现

public class ConsumerTest {

    @Test
    public void testAcceptMessage() throws IOException, TimeoutException {
        //1. 建立连接
        ConnectionFactory factory = new ConnectionFactory();
        //1.1  设置连接参数  主机名、端口、vhost、用户名、密码
        factory.setHost("192.168.211.128");
        factory.setPort(5672);
        // 虚拟主机对资源做逻辑的分组 代表 用 / 表示
        factory.setVirtualHost("/");
        factory.setUsername("cwl");
        factory.setPassword("root");
        //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,
                                       // body 消息体(生产者发送的消息)
                                       byte[] body) throws IOException {
                //5. 处理消息
                String message=new String(body);
                System.out.println("接收到的消息【" + message + "】");
            }
        });
        System.out.println("等待接收消息...");
    }
}

3、总结

基本消息队列的消息发送流程:
	1、建立connection
	2、创建channel
	3、利用channel声明队列
	4、利用channel向队列发送消息

基本消息队列的消息接收流程:
	1、建立connection
	2、创建channel
	4、利用channel声明队列
	5、定义consumer的消费行为handleDelivery()
	6、利用channel将消费者与队列绑定

3、SpringAMQP

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

SpringAmqp的官方地址https://spring.io/projects/spring-amqp
    AMQPAdvanced Message Queuing Protocol,是用于在应用程序之间传递业务消息的
开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。
    Spring AMQP是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息。包含两部分,
其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
2、实现基础消息队列功能的流程
1、在父工程中引入spring-amqp的依赖
		<!--AMQP依赖,包含RabbitMQ-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
2、在publisher服务中利用RabbitTemplate发送消息到simple.queue队列
@SpringBootTest(classes = PublisherApplication.class)
public class SpringAmqpTest {

    @Autowired
    // 模板类 里面封装了发送消息的方法
    private RabbitTemplate rabbitTemplate;

    //发送消息
    @Test
    public void sendHellWorld(){
        // 参数一 指定要发送消息到的队列的名称  参数二 指定要发送的消息
        rabbitTemplate.convertAndSend("helloworld","hello world spring ampq");
    }
}
3、在cnsumer服务中编写消费逻辑,绑定simple.queue队列
1、消息接收,配置MO地址,在consumer服务的application.yml添加配置
logging:
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
spring:
  rabbitmq:
    host: 192.168.211.128
    port: 5672
    virtual-host: /
    username: cwl
    password: root
2、在consumer服务的com.cwl.mq.listener包中新建一个类SpringRabbitListener
@Component
public class SpringRabbitListener {

   @RabbitListener(queues = "helloworld") // queues 制定的是要监听的消息队列名称
    public void acceptMessage(String msg){
        System.out.println("msg = " + msg);
    }
}
3、SpringAMQP如何接收消息?
1、引入amqp的starter依赖
2、配置RabbitMQ地址
3、定义类,添加@Component注解
4、类中声明方法,添加@RabbitListener注解,方法参数就时消息
注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能

4、Work Queue工作队列

1、简介
     Work queue 工作队列,可以提高消息处理速度,避免队列消息堆积,也被称为(Task queue),
任务模型,让多个消费者绑定一个队列,共同消费队列中的消息。

在这里插入图片描述

2、work queue实现一个队列绑定多个消费者的流程
1、在publisher服务中定义测试方法,每秒产生50条消息,送到simple.queue
2、在consumer服务中定义两个消费者监听者,都监听simple.queue队列
3、消费者1每秒处理50条消息,消费者2每秒处理10条消息
3、work queue一个队列绑定多个消费者的具体实现
1、在publisher服务中添加一个测试方法,循环发送50条消息到simple.queue队列
@SpringBootTest(classes = PublisherApplication.class)
public class SpringAmqpWorkQueueTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void testWorkQueue() throws InterruptedException {
        String queueName = "helloworld";
        String message="hello message";
        for (int i = 0; i < 50; i++) {
            rabbitTemplate.convertAndSend(queueName,message+i);
            Thread.sleep(20);
        }
    }
}
2、在consumer服务中添加两个消费者,也监听simple.queue
@Component
public class SpringRabbitListener {
    // 消费者1
    @RabbitListener(queues = "helloworld") // queues 制定的是要监听的消息队列名称
    public void workQueueMessage1(String msg) throws InterruptedException {
        System.out.println("msg = " + msg);
        Thread.sleep(20);
    }
    // 消费者2
    @RabbitListener(queues = "helloworld") // queues 制定的是要监听的消息队列名称
    public void workQueueMessage2(String msg) throws InterruptedException {
        System.err.println("msg = " + msg);
        Thread.sleep(100);
    }
}
3、消费预取限制
修改consumer中的application.yml文件,设置preFetch这个值,可以控制预取消息的上限。
logging:
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
spring:
  rabbitmq:
    host: 192.168.211.128
    port: 5672
    virtual-host: /
    username: cwl
    password: root
    listener:
      simple:
        prefetch: 1 # 设置预取机制的数量 每一次只能获取一个消息 消费完了之后才能下次再取
4、Work模型的使用
1、多个消费者绑定到一个队列,同一条消息只会被一个消费者处理
2、通过设置prefetch来控制消费者预取的消息数量 默认是250

5、发布(Publish)、订阅(Subscribe)

1、简介
发布订阅模式就是允许将同一消息发送给多个消费者,实现方式是加入了exchange(交换机)
常见的exchange类型:
	1Fanout 广播
	2Direct 路由
	3Topic 主题

在这里插入图片描述

在订阅模型中,多了一个exchange角色:
	1Publisher:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
	2Exchange:交换机.一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递
      交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange
      的类型。
	3Exchange有以下3种类型:
		1Fanout:广播,将消息交给所有绑定到交换机的队列
		2Direct:定向,把消息交给符合指定routing key 的队列
		3Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
		4Consumer:消费者,与以前一样,订阅队列,没有变化
		5Queue:消息队列也与以前一样,接收消息、缓存消息。
注: Exchange(交换机)只负责转发消息,不具备存储消息的能力。因此如果没有任何队列
    与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失。
2、Fanout的使用

在这里插入图片描述

1、实现思路
在广播模式下,消息发送流程:
	1) 可以有多个队列
	2) 每个队列都要绑定到Exchange(交换机)
	3) 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定
	4) 交换机把消息发送给绑定过的所有队列
	5) 订阅队列的消费者都能拿到消息
在广播模式下,消息发送流程的具体实现:
1、在consumer服务中,利用代码声明队列、交换机,并将两者绑定。
2、在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
3、在publisher编写测试方法,向cwl.fanout发送消息

1、在consumer中创建一个类,声明队列和交换机:

@Configuration
public class FanoutConfig {

    //1. 创建一个交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("cwl.fanout");
    }
    //2. 创建两个队列
    @Bean
    public Queue queue1(){
        //设置队列名称
        return new Queue("fanout.queue1");
    }

    @Bean
    public Queue queue2(){
        //设置队列名称
        return new Queue("fanout.queue2");
    }

    //3. 绑定2个队列到同一个交换机
    @Bean
    public Binding binding1(){
        return BindingBuilder.bind(queue1()).to(fanoutExchange());
    }
    @Bean
    public Binding binding2(){
        return BindingBuilder.bind(queue2()).to(fanoutExchange());
    }
}

2、在publisher服务的SpringAmqpTest类中添加测试方法:

@SpringBootTest(classes = PublisherApplication.class)
public class FanoutTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void send(){
        String msg = "fanout消息内容";
        // 参数一 指定要发送的交换机的名称 
        // 参数二 指定routingke 这里就是空字符串 (Fanout模式就是这样)
        // 参数三 指定发送的消息内容
        rabbitTemplate.convertAndSend("cwl.fanout","",msg);
    }
}

3、在consumer服务的SpringRabbitListener中添加两个方法,作为消费者:

@Component
public class SpringRabbitListener {
	// 消费者1
    @RabbitListener(queues = "fanout.queue1") // queues 制定的是要监听的消息队列名称
    public void fanoutMessage1(String msg) throws InterruptedException {
        System.out.println("消费者1 msg = " + msg);
    }
    // 消费者2
    @RabbitListener(queues = "fanout.queue2") // queues 制定的是要监听的消息队列名称
    public void fanoutMessage2(String msg) throws InterruptedException {
        System.err.println("消费者1 msg= " + msg);
    }
}

4、总结

1、交换机的作用是什么?
	1.接收publisher发送的消息
	2.将消息按照规则路由到与之绑定的队列
	3.不能缓存消息,路由失败,消息丢失
	4. FanoutExchange的会将消息路由到每个绑定的队列

2、声明队列、交换机、绑定关系的Bean是什么?
	1.Queue
	2.FanoutExchange
	3.Binding
3、Direct使用
1、简介
1Direct Exchange 会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes)。
2、每一个Queue都与Exchange设置一个BindingKey
3、发布者发送消息时,指定消息的RoutingKey
4Exchange将消息路由到BindingKey与消息RoutingKey一致的队列

在这里插入图片描述

Direct模型下:
	1、队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
	2、消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey3Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,
	只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息.
2、实现思路
1、利用@RabbitListener声明ExchangeQueueRoutingKey
2、在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
3、在publisher中编写测试方法,向cwl.direct发送消息
3、具体实现

1、在consumer的SpringRabbitListener中添加两个消费者,同时基于注解来声明队列和交换机

@Component
public class SpringRabbitListener {
// 消费者1
    @RabbitListener(
            bindings = @QueueBinding(
            value = @Queue("direct.queue1"),
            exchange = @Exchange(value = "cwl.direct", type = ExchangeTypes.DIRECT),
            key = {"blue","red"}
            )
    )
    public void directMessage1(String msg) throws InterruptedException {
        System.out.println("消费者1 msg = " + msg);

    }

    // 消费者2
    @RabbitListener(
            bindings = @QueueBinding(
            value = @Queue("direct.queue2"),
            exchange = @Exchange(value = "cwl.direct", type = ExchangeTypes.DIRECT),
            key = {"yellow","red"}
            )
    )
    public void directMessage2(String msg) throws InterruptedException {
        System.err.println("消费者2 msg= " + msg);
    }
}

2、在publisher服务的SpringAmqpTest类中添加测试方法

@SpringBootTest(classes = PublisherApplication.class)
public class directTest {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void send(){
        String msg = "direct消息内容";
        //参数一 指定要发送的交换机的名称 参数二 指定的routingkey 需要指定将这个消息发给谁 blue
        rabbitTemplate.convertAndSend("cwl.direct","blue",msg);
    }
}

3、总结

1、描述下Direct交换机与Fanout交换机的差异?
	1.Fanout交换机将消息路由给每一个与之绑定的队列
	2.Direct交换机根据RoutingKey判断路由给哪个队列
	3.如果多个队列具有相同的RoutingKey,则与Fanout功能类似

2、基于@RabbitListener注解声明队列和交换机有哪些常见注解?
	1.@Queue
	2.@Exchange
4、Topic使用
1、简介
1TopicExchangeDirectExchange类似,都是可以根据RoutingKey把消息路由到不同的队列,
   区别在于routingKey必须是多个单词的列表,并且以 . 分割。
2QueueExchange指定BindingKey时可以使用通配符:
	1. #:代指0个或多个单词
	2. *:代指一个单词

在这里插入图片描述

2、实现思路
1、并利用@RabbitListener声明ExchangeQueueRoutingKey
2、在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2
3、在publisher中编写测试方法,向cwl.topic发送消息
3、具体实现

1、在publisher服务的SpringAmqpTest类中添加测试方法:

@SpringBootTest(classes = PublisherApplication.class)
public class topicTest {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    public void send(){
        String msg = "topic消息内容  gaotian";
        String msg2 = "topic消息内容  gaotian";
        String msg3 = "topic消息内容  新闻";
        // 参数一 指定要发送的交换机的名称 
        // 参数二 指定的routingkey 需要指定将这个消息发给谁 blue
        rabbitTemplate.convertAndSend("cwl.topic","china.gaotian",msg);
        rabbitTemplate.convertAndSend("cwl.topic","china.gaotian",msg2);
        rabbitTemplate.convertAndSend("cwl.topic","hanggou.news",msg3);
    }
}

2、在consumer服务的SpringRabbitListener中添加方法

@Component
public class SpringRabbitListener {
	// 消费者1
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue("topic.queue1"),
                    exchange = @Exchange(value = "cwl.topic", type = ExchangeTypes.TOPIC),
                    key = {"china.#"}
            )
    )
    public void topicMessage1(String msg) throws InterruptedException {
        System.out.println("消费者1 msg = " + msg);

    }

    // 消费者2
    @RabbitListener(
            bindings = @QueueBinding(
            value = @Queue("topic.queue2"),
            exchange = @Exchange(value = "cwl.topic", type = ExchangeTypes.TOPIC),
            key = {"#.news"}
            )
    )
    public void topicMessage2(String msg) throws InterruptedException {
        System.err.println("消费者2 msg= " + msg);
    }
}

3、总结

描述下Direct交换机与Topic交换机的差异?
	1Topic交换机接收的消息RoutingKey必须是多个单词,以 **.** 分割
	2Topic交换机与队列绑定时的bindingKey可以指定通配符
	3、#:代表0个或多个词
	4*:代表1个词
5、消息转换器
1、简介
1Spring的对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现
   是SimpleMessageConverter,基于JDKObjectOutputStream完成序列化。

2、默认情况下Spring采用的序列化方式是JDK序列化。众所周知,JDK序列化存在下列问题:
	1、数据体积过大
	2、有安全漏洞
	3、可读性差
3、如果要修改只需要定义一个MessageConverter 类型的Bean即可。推荐用JSON方式序列化。
   JSON的体积更小、可读性更高.
2、配置JSON转换器的步骤

1、在publisher和consumer两个服务中都引入依赖

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.9.10</version>
</dependency>

2、在publisher和consumer两个服务中的启动类上添加一个Bean即可

	@Bean
    public MessageConverter messageConverter(){
        // 将对象转换为JSON 和将 JSON转换对象
        return new Jackson2JsonMessageConverter();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值