RabbitMQ入门

RabbitMQ入门 - 高性能的异步通讯组件@


在这里插入图片描述

  • 同步通讯:同一时间只能和一个妹子进行视频通话,其他妹子无法再和你进行视频通话
  • 异步通讯:同一时间许多妹子可以和你发消息,你都可以接收得到,但是不一定能立马回复,也就是不是实时的。

微服务一旦拆分,必然涉及到服务之间的相互调用,目前我们服务之间调用采用的都是基于OpenFeign的调用。

在这里插入图片描述

这种调用中,调用者发起请求后需要等待服务提供者执行业务返回结果后,才能继续执行后面的业务。也就是说调用者在调用过程中处于阻塞状态,因此我们称这种调用方式为同步调用,也可以叫同步通讯。但在很多场景下,我们可能需要采用异步通讯的方式。

在这里插入图片描述

而使用MQ异步,就可以让MQ监听后两个服务,让所有服务并行执行,提高性能。

课程背景:
在这里插入图片描述

1 初识MQ

1.1 同步调用

以黑马商城的余额支付为例:

在这里插入图片描述

  • 拓展性差:每加一个需求都需要在后面级联添加。
  • 性能下降:每个级联的需求都需要一定的延时,导致所需的时间越来越长
  • 级联失败:之间一个不重要的需求失败,导致后续需求都无法执行

在这里插入图片描述

1.2 异步调用

异步调用通常是基于消息通知的方式,包含三个角色:

  • 消息发送者:投递消息的人,就是原来的 调用者
  • 消息接收者:接收和处理消息的人,就是原来的 服务提供者
  • 消息代理:管理、暂存、转发消息,可以理解成微信服务器
    在这里插入图片描述

支付服务不再同步调用业务联度低的服务,而是发送消息通知到Broker。比如交易服务是与支付相关度不大的业务

在这里插入图片描述

使用消息代理,支付服务只需要在支付成功后发送个消息通知即可。其它监听了这个消息代理的服务只需要判断是否有支付消息发送,就自动完成对应的业务。

也就是说支付服务不需要添加新的代码了,只需要将新添加需求的服务监听消息代理,在新添加需求的服务出写相应的业务代码即可。

优势:

  • 解除耦合,拓展性强
  • 无需等待,性能好
  • 故障隔离
  • 缓存消息,流量削峰填谷

在这里插入图片描述

1.3 MQ技术选型

MQ(MessageQueue),中文是消息队列,字面来看就是存放消息的队列。也就是异步调用中的Broker。

在这里插入图片描述

2 RabbitMQ

基本介绍: RabbitMQ的整体架构及核心概念

  • virtual-host:虚拟主机,起到数据隔离的作用

  • publisher:消息发送者

  • consumer:消息的消费者

  • queue:队列,存储消息

  • exchange:交换机,负责路由消息
    在这里插入图片描述

2.1 快速入门

需求: 在RabbitMQ的控制台完成下列操作

  • 新建队列hello.queue1和hello.queue2
  • 向默认的amp.fanout交换机发送一条消息
  • 查看消息是否到达hello.queue1和hello.queue2
  • 总结规律
    在这里插入图片描述

2.2 数据隔离

需求:在RabbitMQ的控制台完成下列操作

  • 新建一个用户hamll
  • 为hmall用户创建一个virtual host
  • 测试不同virtual host之间的数据隔离现象

3 Java客户端—SpringAMQP

3.1 快速入门

在这里插入图片描述

案例: 快速入门

需求如下:

  • 利用控制台创建队列simple.queue
  • 在publisher服务中,利用SpringAMQP直接向simple.queue发送消息
  • 在consumer服务中,利用SpringAMQP编写消费者,监听simple.queue队列

在这里插入图片描述

步骤一: 引入spring-amqp依赖

在父工程中引入spring-amqp依赖,这样publisher和consumer服务都可以使用

在这里插入图片描述

步骤二: 配置RabbitMQ服务端消息

在每一个微服务中引入MQ服务端信息,这样微服务才能连接到RabbitMQ

在这里插入图片描述

步骤三: 发送消息

SpringAMQP提供了RabbitTemplate工具类,方便发送消息,发送消息代码如下:

在这里插入图片描述

步骤四: 接收消息

SpringAMQP提供声明式的消息监听,只需要通过注解在方法上声明要监听的队列名称,将来SPringAMQP就会把消息传递给当前方法:

在这里插入图片描述

3.2 WorkQueue

当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。

此时就可以使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。

Work queues,任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。

在这里插入图片描述

基本思路如下:

  1. 在RabbitMQ的控制台创建一个队列,名为work.queue
  2. 在publisher服务中定义测试方法,发送50条消息到work.queue
  3. 在consumer服务中定义两个消息监听者,都监听work.queue队列
  4. 消费者1每秒处理40条消息,消费者2每秒处理5条消息

以上实验可以发现

  • 每个消费者是平均分配队列中的消息,队列中的每个消息只能被一个消费者处理
  • 多个消费者接受一个队列中的消息,就算一个处理快一个处理慢,队列中的消息也是一个一个平均投递。导致处理快消费者很快完成了自己的25条消息,而处理慢的消费者很慢的完成了自己的25条消息。这样的处理其实不合理。

消费者消息推送限制

默认情况下:RabbitMQ的会将消息依次轮训投递给绑定在队列上的每一个消费者。但这并没有考虑到消费者是否已经处理完消息,可能出现消息堆积。

因此需要修改application.yml,设置preFetch值为1,确保同一时刻最多投递给消费者1条消息:这样就能保证速度快的人多处理,速度慢的人少处理

在这里插入图片描述

image-20240730112901791

3.3 Fanout交换机

交换机的作用主要是 接收 发送者发送的消息,并将消息 路由 到与其绑定的队列。

常见的交换机的类型有以下三种:

  • Fanout:广播
  • Direct:定向
  • Topic:话题

Fanout Exchange会将接收到的消息路由到每一个跟其绑定的queue,所以也叫广播模式。在广播模式下,消息发送流程是这样的:

在这里插入图片描述

  • 可以有多个队列
  • 每个队列都要绑定到Exchange(交换机)
  • 生产者发送的消息,只能发送到交换机
  • 交换机把消息发送给绑定过的所有队列
  • 订阅队列的消费者都能拿到消息

Fanout交换机优势:本来在没有交换机,只有队列的情况下,一条消息只能一个消费者处理。现在有了交换机,一个消息可以被传递到所有连接的队列中,每个只有一个消费者连接的队列都可以得到相同的消息,并让消费者处理,也就是最终每个消费者都可以接收到相同的消息。

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

实现思路如下:

  1. 在RabbitMQ控制台中,声明队列fanout.queue1和fanout.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall.fanout,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2
  4. 在publisher中编写测试方法,向hmall.fanout发送消息

交换机创建:

在这里插入图片描述

队列创建:

在这里插入图片描述

交换机与队列绑定:

在这里插入图片描述

发送者:

    @Test
    public void testFanoutQueue() {
        //1. 交换机名
        String exchangeName = "hmall.fanout";
        //2. 消息
        String message = "hello,Spring amqp";
        //3. 发送消息
        rabbitTemplate.convertAndSend(exchangeName, null, message);
    }

监听者:

    @RabbitListener(queues = "fanout.queue1")
    public void listenFanoutQueue1(String message) {
        log.info("消费者1监听到fanout.queue1的消息:{}", message);

    }

    @RabbitListener(queues = "fanout.queue2")
    public void listenFanoutQueue2(String message) {
        log.info("消费者2监听到fanout.queue2的消息:{}", message);

    }

在这里插入图片描述

3.4 Direct交换机

Direct Exchange会将接收到的消息根据规则路由到指定的Queue,因此称为 定向 路由

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

在这里插入图片描述

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

需求如下:

  1. 在RabbitMQ控制台中,声明队列direct.queue1和direct.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall.direct,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2
  4. 在publish中编写测试方法,利用不同的RoutingKey向hmall.direct发送消息。

交换机:

在这里插入图片描述

队列:
在这里插入图片描述

队列绑定交换机并制定RouteKey:
在这里插入图片描述

发送者代码

    @Test
    public void testDirectQueue() {
        //1. 交换机名
        String exchangeName = "hmall.direct";
        //2. 消息
        String message = "hello,Spring amqp";
        //3. 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "red", message + "red");
        rabbitTemplate.convertAndSend(exchangeName, "yellow", message + "yellow");
        rabbitTemplate.convertAndSend(exchangeName, "blue", message + "blue");
    }

消费者代码

    @RabbitListener(queues = "direct.queue1")
    public void listenDirectQueue1(String message) {
        log.info("消费者1监听到direct.queue1的消息:{}", message);

    }

    @RabbitListener(queues = "direct.queue2")
    public void listenDirectQueue2(String message) {
        log.info("消费者2监听到direct.queue2的消息:{}", message);

    }

结果:

在这里插入图片描述

在这里插入图片描述

3.5 Topic交换机

TopicExchange也是基于RouTingKey做消息路由,但是routingKey通常是多个单词的组合,并且以 . 分割

Queue与Exchange指定BindingKey时可以使用通配符:

  • #:代指0个或多个单词
  • *:代指一个单词

在这里插入图片描述

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

需求如下:

  1. 在RabbitMQ控制台中,声明队列topic.queue1和topic.queue2
  2. 在RabbitMQ控制台中,声明交换机hmall.topic,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2
  4. 在publisher中编写测试方法,利用不同的RoutingKey向hmall.topic发送消息

在这里插入图片描述

创建交换机:

在这里插入图片描述

创建队列:

在这里插入图片描述

队列绑定交换机:

在这里插入图片描述

消费者代码

    @RabbitListener(queues = "topic.queue1")
    public void listenTopicQueue1(String message) {
        log.info("消费者1监听到topic.queue1的消息:{}", message);

    }

    @RabbitListener(queues = "topic.queue2")
    public void listenTopicQueue2(String message) {
        log.info("消费者2监听到topic.queue2的消息:{}", message);

    }

发送者代码:

    @Test
    public void testTopicQueue() {
        //1. 交换机名
        String exchangeName = "hmall.topic";
        //2. 消息
        String message1 = "中国乒乓球队乱杀";
        String message2 = "中国西安天气不错";
        String message3 = "日本岛快沉了";
        //3. 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "china.news", message1);
        rabbitTemplate.convertAndSend(exchangeName, "china.weather", message2);
        rabbitTemplate.convertAndSend(exchangeName, "japan.news", message3);
    }

结果:

在这里插入图片描述

在这里插入图片描述

3.6 声明队列和交换机

在之前我们都是基于RabbitMQ控制台来创建队列、交换机。但是在实际开发时,队列和交换机是程序员定义的,将来项目上线,又要交给运维去创建。那么程序员就需要把程序中运行的所有队列和交换机都写下来,交给运维。在这个过程中是很容易出现错误的。

因此推荐的做法是由程序启动时检查队列和交换机是否存在,如果不存在自动创建。

SpringAMQP提供了几个类,用来声明队列、交换机及其绑定关系:

  • Queue:用于声明队列,可以用工厂类QueueBuilder构建
  • Exchange:用于声明交换机,可以用工厂类ExchangeBuilder构建
  • Binding:用于声明队列和交换机的绑定关系,可以用工厂类BindingBulider构建
    在这里插入图片描述

例如,声明一个Fanout类型的交换机,并且创建队列与其绑定:

在这里插入图片描述

如果是Direct或者Topic交换机,则BindingBuilder函数后面还要加.BingdingKey();

@Configuration
public class FanoutConfiguration {

    @Bean
    public FanoutExchange fanoutExchange(){
//        return new FanoutExchange("hmall.fanout");
        return ExchangeBuilder.fanoutExchange("hmall.fanout").build();
    }

    @Bean
    public Queue fanoutQueue1(){
//        return new Queue("fanout.queque1");
        return QueueBuilder.durable("fanout.queque1").build();
    }

    @Bean
    public Queue fanoutQueue2(){
//        return new Queue("fanout.queque2");
        return QueueBuilder.durable("fanout.queque2").build();
    }

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

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

案例: 利用SpringAMQP声明DirectExchange并于队列绑定

需求如下:

  1. 在consumer服务中,声明队列direct.queue1和direct.queue2
  2. 在consumer服务中,声明交换机hmall.direct,将两个队列与其绑定
  3. 在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2

在这里插入图片描述

@Configuration
public class DirectConfiguration {

    @Bean
    public DirectExchange directExchange(){
//        return new DirectExchange("hmall.fanout");
        return ExchangeBuilder.directExchange("hmall.direct").build();
    }

    @Bean
    public Queue directQueue1(){
//        return new Queue("direct.queque1");
        return QueueBuilder.durable("direct.queque1").build();
    }

    @Bean
    public Queue directQueue2(){
//        return new Queue("direct.queque2");
        return QueueBuilder.durable("direct.queque2").build();
    }

    @Bean
    public Binding directQueue1BindingRed(Queue directQueue1,DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("red");
    }

    @Bean
    public Binding directQueue1BindingBlue(Queue directQueue1,DirectExchange directExchange){
        return BindingBuilder.bind(directQueue1).to(directExchange).with("blue");
    }

    @Bean
    public Binding directQueue2BindingRed(Queue directQueue2,DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("red");
    }

    @Bean
    public Binding directQueue2BindingYellow(Queue directQueue2,DirectExchange directExchange){
        return BindingBuilder.bind(directQueue2).to(directExchange).with("yellow");
    }
}

SpringAMQP还提供了基于@RabbitListener注解来声明队列和交换机的方式:

在这里插入图片描述

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "blue"}
    ))
    public void listenDirectQueue1(String message) {
        log.info("消费者1监听到direct.queue1的消息:{}", message);

    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2", durable = "true"),
            exchange = @Exchange(name = "hmall.direct", type = ExchangeTypes.DIRECT),
            key = {"red", "yellow"}
    ))
    public void listenDirectQueue2(String message) {
        log.info("消费者2监听到direct.queue2的消息:{}", message);

    }

直接生成交换机、队列以及对应的绑定关系

3.7 消息交换机

案例: 消息转换器

需求: 测试利用SpringAMQP发送对象类型的消息

  • 声明一个队列,名为object.queue
  • 编写单元测试,向队列中直接发送一条消息,消息类型为Map
  • 在控制台查看消息,总结你能发现的问题

在这里插入图片描述

@Test
public void testSendMap() throws InterruptedException {
    // 准备消息
    Map<String,Object> msg = new HashMap<>();
    msg.put("name", "柳岩");
    msg.put("age", 21);
    // 发送消息
    rabbitTemplate.convertAndSend("object.queue", msg);
}

发送消息后看控制台

在这里插入图片描述

显然,JDK序列化方式并不合适。我们希望消息体的体积更小、可读性更高,因此可以使用JSON方式来做序列化和反序列化。

消息转换器

Spring的对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。

存在下列问题:

  • JDK的序列化有安全问题
  • JDK序列化的消息太大
  • JDK序列化的消息可读性太差

建议采用JSON序列化代替默认的JDK序列化,要做两件事:

在publisher和consumer中都要引入jackson依赖:

在这里插入图片描述

在publisher和consumer中都要配置MessageConverter:

在这里插入图片描述

    @RabbitListener(queues = "object.queue")
    public void listenObjectQueue(Map<String,Object> msg){
        log.info("消费者2监听到 object.queue的消息:{}",msg);
    }
  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RabbitMQ是一款使用Erlang语言开发的开源消息中间件,实现了AMQP(高级消息队列协议)。它具有以下特点: 1. 可靠性:支持持久化、传输确认、发布确认等机制,保证了消息的可靠性。 2. 灵活的消息分发策略:消息在进入RabbitMQ之前由交换机进行路由,可以根据不同的分发策略进行消息的分发,包括简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式等。 3. 支持集群:多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker,提高了可用性和扩展性。 4. 支持多种协议和语言客户端:RabbitMQ支持多种消息队列协议,如STOMP、MQTT等,同时也支持多种编程语言的客户端。 5. 可视化管理界面:RabbitMQ提供了一个易用的用户界面,方便用户监控和管理消息Broker。 6. 插件机制:RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。 要开始使用RabbitMQ,首先需要安装RabbitMQ并配置相关信息。在Windows系统中,可以按照官方文档的指引进行安装。在配置文件中,需要指定RabbitMQ的主机、端口、用户名和密码等信息。 参考资料: \[1\] RabbitMQ官网 \[2\] application.yml配置文件 \[3\] RabbitMQ入门指南文章 希望这些信息对你有帮助! #### 引用[.reference_title] - *1* *2* *3* [超详细的RabbitMQ入门](https://blog.csdn.net/Rok728/article/details/123106242)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值