review-消息中间件MQ

embedded/2024/11/19 21:16:25/

RabbitMQ

RabbitMQ,作为当今流行的开源消息代理软件,以其卓越的可靠性、灵活性和易用性在微服务架构和分布式系统中扮演着至关重要的角色。它不仅能够确保消息在不同系统组件间的高效传递,还能通过其高级消息队列协议(AMQP)支持复杂的路由功能,从而满足各种消息分发场景。RabbitMQ的高性能和可扩展性使其成为处理大规模消息传递任务的理想选择,同时,其丰富的API和工具集也极大地简化了开发人员在不同编程环境中的集成和使用。无论是应对日常的消息传递需求,还是构建复杂的事件驱动架构,RabbitMQ都能提供强大而稳定的支持。

kafka消息中间件在上一篇文章SpringBoot3全面复习已经写过,下面主要介绍RabbitMQ的内容。
一、初始MQ
同步调用的优势是,时效性强,等待到结果后返回。
在这里插入图片描述
异步调用
在这里插入图片描述
在异步调用中,发送者不再直接同步调用接收者的业务接口,而是发送一条消息投递给消息Broker。然后接收者根据自己的需求从消息Broker那里订阅消息。每当发送方发送消息后,接受者都能获取消息并处理。
综上,异步调用的优势包括:

  • 耦合度更低 ,扩展性强
  • 异步调用,无需等待,性能更好
  • 缓存消息,流量削峰填谷
  • 故障隔离,避免级联失败

当然,异步通信也并非完美无缺,它存在下列缺点:

  • 完全依赖于Broker的可靠性、安全性和性能 架构复杂,
  • 后期维护和调试麻烦
    技术选型
    消息Broker,目前常见的实现方案就是消息队列(MessageQueue),简称为MQ.
    在这里插入图片描述
    追求可用性:Kafka、 RocketMQ 、RabbitMQ
    追求可靠性:RabbitMQ、RocketMQ
    追求吞吐能力:RocketMQ、Kafka
    追求消息低延迟:RabbitMQ、Kafka
    安装RabbitMQ
    同样基于Docker来安装RabbitMQ,使用下面的命令即可:
java">docker run \-e RABBITMQ_DEFAULT_USER=itheima \-e RABBITMQ_DEFAULT_PASS=123321 \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5672:5672 \--network hmall \-d \rabbitmq:3.8-management

同样基于Docker来安装RabbitMQ,使用下面的命令即可:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
15672:RabbitMQ提供的管理控制台的端口
5672:RabbitMQ的消息发送处理接口
安装完成后,我们访问 http://虚拟机ip:15672即可看到管理控制台。首次访问需要登录,默认的用户名和密码在配置文件中已经指定了。
在这里插入图片描述
在这里插入图片描述
publisher:生产者,也就是发送消息的一方
consumer:消费者,也就是消费消息的一方
queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理
exchange:交换机,负责消息路由转发,没有存储消息的能力。生产者发送的消息由交换机决定投递到哪个队列。
virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
数据隔离
在这里插入图片描述
Name:itheima,也就是用户名
Tags:administrator,说明itheima用户是超级管理员,拥有所有权限
Can access virtual host: /,可以访问的virtual host,这里的/是默认的virtual host
此时hmall用户没有任何virtual host的访问权限在这里插入图片描述
在这里插入图片描述
SpringAMQP
在这里插入图片描述
在这里插入图片描述

已有依赖:

java"><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.itcast.demo</groupId><artifactId>mq-demo</artifactId><version>1.0-SNAPSHOT</version><modules><module>publisher</module><module>consumer</module></modules><packaging>pom</packaging><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.12</version><relativePath/></parent><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><!--单元测试--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency></dependencies>
</project>

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

消息发送
首先配置MQ地址,在publisher服务的application.yml中添加配置:

java">spring:rabbitmq:host: 192.168.150.101 # 你的虚拟机IPport: 5672 # 端口virtual-host: /hmall # 虚拟主机username: hmall # 用户名password: 123 # 密码

编写测试类,利用rabbitTemplate实现消息发送:

java">package com.itheima.publisher.amqp;import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue() {// 队列名称String queueName = "simple.queue";// 消息String message = "hello, spring amqp!";// 发送消息rabbitTemplate.convertAndSend(queueName, message);}
}

在这里插入图片描述

消息接受
配置MQ地址,在consumer服务的application.yml中添加配置:

java">spring:rabbitmq:host: 192.168.150.101 # 你的虚拟机IPport: 5672 # 端口virtual-host: /hmall # 虚拟主机username: hmall # 用户名password: 123 # 密码

consumer服务的com.itheima.consumer.listener包中新建一个类SpringRabbitListener,代码如下:

java">package com.itheima.consumer.listener;import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;@Component
public class SpringRabbitListener {// 利用RabbitListener来声明要监听的队列信息// 将来一旦监听的队列中有了消息,就会推送给当前服务,调用当前方法,处理消息。// 可以看到方法体中接收的就是消息体的内容@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) throws InterruptedException {System.out.println("spring 消费者接收到消息:【" + msg + "】");}
}

WorkQueues模型
在这里插入图片描述
在这里插入图片描述当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。
此时就可以使用work 模型,多个消费者共同处理消息处理,消息处理的速度就能大大提高了。
在这里插入图片描述
消息发送

这次我们循环发送,模拟大量消息堆积现象。
在publisher服务中的SpringAmqpTest类中添加一个测试方法:

java">/*** workQueue* 向队列中不停发送消息,模拟消息堆积。*/
@Test
public void testWorkQueue() throws InterruptedException {// 队列名称String queueName = "simple.queue";// 消息String message = "hello, message_";for (int i = 0; i < 50; i++) {// 发送消息,每20毫秒发送一次,相当于每秒发送50条消息rabbitTemplate.convertAndSend(queueName, message + i);Thread.sleep(20);}
}

消息接受
要模拟多个消费者绑定同一个队列,我们在consumer服务的SpringRabbitListener中添加2个新的方法:

java">@RabbitListener(queues = "work.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(20);
}@RabbitListener(queues = "work.queue")
public void listenWorkQueue2(String msg) throws InterruptedException {System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());Thread.sleep(200);
}

消费者1 sleep了20毫秒,相当于每秒钟处理50个消息
消费者2 sleep了200毫秒,相当于每秒处理5个消息
在这里插入图片描述
也就是说消息是平均分配给每个消费者,并没有考虑到消费者的处理能力。导致1个消费者空闲,另一个消费者忙的不可开交。没有充分利用每一个消费者的能力,最终消息处理的耗时远远超过了1秒。这样显然是有问题的。
能者多劳
修改consumer服务的application.yml文件,添加配置:

java">spring:rabbitmq:listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

由于消费者1处理速度较快,所以处理了更多的消息;消费者2处理速度较慢,只处理了6条消息。而最终总的执行耗时也在1秒左右,大大提升。
正所谓能者多劳,这样充分利用了每一个消费者的处理能力,可以有效避免消息积压问题。
在这里插入图片描述
交换机
在这里插入图片描述
Fanout:广播,将消息交给所有绑定到交换机的队列。我们最早在控制台使用的正是Fanout交换机
Direct:订阅,基于RoutingKey(路由key)发送给订阅了消息的队列
Topic:通配符订阅,与Direct类似,只不过RoutingKey可以使用通配符
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失
在这里插入图片描述
1) 可以有多个队列
2) 每个队列都要绑定到Exchange(交换机)
3) 生产者发送的消息,只能发送到交换机
4) 交换机把消息发送给绑定过的所有队列
5) 订阅队列的消费者都能拿到消息

在这里插入图片描述
消息发送

java">@Test
public void testFanoutExchange() {// 交换机名称String exchangeName = "hmall.fanout";// 消息String message = "hello, everyone!";rabbitTemplate.convertAndSend(exchangeName, "", message);
}

消息接收

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

在这里插入图片描述
一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。
Direct交换机
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
消息接收
在consumer服务的SpringRabbitListener中添加方法:

java">@RabbitListener(queues = "direct.queue1")
public void listenDirectQueue1(String msg) {System.out.println("消费者1接收到direct.queue1的消息:【" + msg + "】");
}@RabbitListener(queues = "direct.queue2")
public void listenDirectQueue2(String msg) {System.out.println("消费者2接收到direct.queue2的消息:【" + msg + "】");
}

消息发送

java">@Test
public void testSendDirectExchange() {// 交换机名称String exchangeName = "hmall.direct";// 消息String message = "红色警报!日本乱排核废水,导致海洋生物变异,惊现哥斯拉!";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

在这里插入图片描述
Topic
在这里插入图片描述
Queue1:绑定的是china.# ,因此凡是以 china.开头的routing key 都会被匹配到。包括china.news和china.weather
Queue2:绑定的是#.news ,因此凡是以 .news结尾的 routing key 都会被匹配。包括china.news和japan.news
在这里插入图片描述
.消息发送

java">/*** topicExchange*/
@Test
public void testSendTopicExchange() {// 交换机名称String exchangeName = "itcast.topic";// 消息String message = "喜报!孙悟空大战哥斯拉,胜!";// 发送消息rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}

.消息接收

java">@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("消费者接收到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("消费者接收到topic.queue2的消息:【" + msg + "】");
}

在这里插入图片描述

声明队列交换机
在这里插入图片描述

Exchange 只是一个接口,其具体的实现类分别是对应的几个不同类型的交换机,如 FanoutExchange 、DirectExchange 、TopicExchange 等等。

在这里插入图片描述
其中绑定关系的构建是 BindingBuilder.bind(队列).to(交换机).with(RoutingKey) 这样的。

@RabbitListener 注解声明
可以使用当时用于定义消费者的注解 @RabbitListener 来定义队列、交换机、及绑定关系,只需其中的 bindings 属性,在其中使用 @QueueBinding 注解进行定义。
在这里插入图片描述
1.value = @Queue(…) 定义了队列的具体属性。
2.exchange = @Exchange(…) 指定关联的交换机详情。
3.key = {“hi”} 设置了绑定的路由键。
.消息转换器
在 Spring AMQP 在内部进行消息转化的时候会使用 JDK 自带的序列化方式,这种方法存在着问题,首先 JDK 的序列化存在安全风险,反序列化时容易被代码注入,其次,序列化后的消息占用空间太多,可读性差。

在这里插入图片描述
在这里插入图片描述
建议使用 JSON 序列化代替默认的 JDK 序列化。
在这里插入图片描述

1.在消息的接收者和消费者中都引入 jackson 的依赖

java">        <!--jackson--><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.13.4</version></dependency>

两者中都要配置 MessageConverter 成 Bean(可在启动类中配置)
在这里插入图片描述
发送者的可靠性
发送者重连
在这里插入图片描述
发送者确认机制
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
MQ的可靠性
在这里插入图片描述
数据持久化
在这里插入图片描述
在这里插入图片描述
在默认情况下是非持久的,可以选择 2 发送持久化的消息,而 Spring AMQP 发送的消息默认是持久化的,我们也可以通过自定义构建消息来发送非持久化的消息。
在这里插入图片描述

java">Message message = MessageBuilder.withBody("holle, SpringAMQP".getBytes(StandardCharsets.UTF_8)).setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT).build();

其中 setDeliveryMode 用于设置投递模式为持久化或非持久化。 持久化的优点在于重启后,持久化的交换机、队列、消息仍然会存在,提高了效率。
Lazy Queue(惰性队列)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
接收者的可靠性
消费者确认机制
在这里插入图片描述

java">spring:rabbitmq:listener:simple:ackonwledge-mode: auto # 配置为 auto 模式

失败重试机制
Spring AMQP 提供了消费者重试机制,在消费者出现异常时利用本地重试,而不是无限的发送消息到 MQ 中,我们可以通过在 yml 配置文件中添加相关配置来开启重试机制。

java">spring:rabbitmq:listener:simple:retry:enabled: true      # 开启重试机制initial-interval: 1000ms # 第一次重试间隔时间multiplier: 1      # 失败后重试间隔倍数max-attempts: 3    # 最大重试次数stateless: true    # true无状态;false有状态。如果业务中包含事务,则设置为false

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
业务幂等性
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
延迟消息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
延迟消息插件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


http://www.ppmy.cn/embedded/138867.html

相关文章

Dubbo 3.x源码(25)—Dubbo服务引用源码(8)notify订阅服务通知更新

基于Dubbo 3.1&#xff0c;详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了接口级的服务引入订阅的refreshInterfaceInvoker方法&#xff0c;当时还有最为关键的notify服务通知更新的部分源码没有学习&#xff0c;本次我们来学习notify通知本地服务更新的源码。 Dubb…

使用OpenFeign实现HTTP调用的最简单案例

首先编写服务提供者代码&#xff0c;也就是创建一个springboot项目&#xff0c;端口默认8080即可&#xff0c;然后新建一个接口&#xff0c;启动项目之后可以通过http://localhost:8080/api/data直接访问 RestController RequestMapping("/api") public class DataC…

容器安装gitlab

说明: 1、容器快速搭建gitlab,并将数据、配置文件、日志挂载到物理机磁盘 2、禁用不必要的gitlab插件,只部署必须的gitlab插件,减少cpu、内存等资源 3、gitlab页面禁用注册按钮,同时注意gitlab备份和恢复必须要相同版本 4、请备份gitlab两个重要配置文件:gitlab.rb、gitl…

【Python】Tkinter模块(巨详细)

专栏文章索引&#xff1a;Python 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 本文内容系本人根据阅读的《Python GUI设计tkinter从入门到实践》所得&#xff0c;以自己的方式进行总结和表达。未经授权&#xff0c;禁止在任何平台上以任何形式复制或发布原始书籍的内容。…

【css+JavaScript 】关于链接选中且通过 js 设置持久的选中状态

1、点击后选中状态保持&#xff1a;当你点击任意链接时&#xff0c;它的颜色变为红色&#xff0c;并且页面刷新后仍然保留选中状态。 2、页面刷新后保持选中&#xff1a;即使刷新页面&#xff0c;选中的链接也能通过 localStorage 恢复&#xff0c;确保用户的选中状态持续存在…

怎样选择合适的服务器租用呢?

在数字化时代当中&#xff0c;企业选择合适的服务器租用是至关重要的&#xff0c;服务器是承载着网站和数据存储的主要核心&#xff0c;服务器的稳定性会影响到网站的访问速度和用户的体验感&#xff0c;本文就从几个方面来探讨怎样选择合适的服务器租用吧&#xff01; 首先企业…

【MYSQL】什么是关系型数据库与非关系型数据库?

真正的让你快速理解什么是关系型数据库与非关系型数据库~ 主要是以查询语句&#xff0c;存储结构&#xff0c;拓展 性上的区别。 关系型数据库&#xff08;最经典就是mysql&#xff0c;oracle&#xff09;&#xff1a;它是支持SQL语言&#xff0c;并且关系型数据库大部分都支持…

C# 常用三方库

C# 第三方库 C# 第三方库日志工具库REST 客户端JSON 处理App.config 文件自定义ConfigSection 的 auto 配置ORM 工具嵌入数据库条码/二维码通讯类组件串口通讯 https://www.nuget.org/packages/GodSharp.SerialPort/Modbus 通讯组件西门子通讯组件Fins协议通讯组件, 报表组件包…