RabbitMQ 从入门到精通
1 MQ架构设计原理
1.1 什么是消息中间件
消息中间件基于队列模型实现异步/同步传输数据
作用:可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。
1.2 传统的http请求存在那些缺点
1.Http请求基于请求与响应的模型,在高并发的情况下,客户端发送大量的请求到达服务器端有可能会导致我们服务器端处理请求堆积。
2.Tomcat服务器处理每个请求都有自己独立的线程,如果超过最大线程数会将该请求缓存到队列中,如果请求堆积过多的情况下,有可能会导致tomcat服务器崩溃的问题。
所以一般都会在nginx入口实现限流熔断 网关整合Sentinal,整合服务保护框架。
(nginx可以从博主的Nginx从入门到入土(一):DNS域名解析_nginx 域名解析-CSDN博客等系列文章了解)
"Sentinel" 指的是阿里巴巴开源的 流量控制和服务保护框架,全称为 Sentinel。它是一个面向分布式系统的轻量级流量控制组件,主要用于实现限流、熔断、系统负载保护等功能,确保服务在高并发或异常情况下的稳定性。
3.http请求处理业务逻辑如果比较耗时的情况下,容易造成客户端一直等待,阻塞等待过程中会导致客户端超时发生重试策略,有可能会引发幂等性问题。
重试策略:当客户端发起 HTTP 请求后,如果服务器处理时间过长,客户端可能会因为超时而认为请求失败,从而触发 重试机制。重试策略是指客户端在请求失败时,自动重新发送请求的行为。
幂等性问题:由于重试导致请求被重复处理,可能引发数据不一致或重复操作的问题
注意事项:接口是http协议的情况下,最好不要处理比较耗时的业务逻辑,耗时的业务逻辑应该单独交给多线程或者是mq处理。
1.3 Mq应用场景有那些
- 异步发送短信
- 异步发送新人优惠券
- 处理一些比较耗时的操作
1.4 为什么需要使用mq
可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。
1.5 同步发送http请求

场景描述
在会员注册的业务逻辑中,客户端发送请求到服务器端后,服务器需要依次执行以下操作:
-
插入会员数据(
insertMember()):耗时约 1 秒。 -
发送登录短信提醒(
sendSms()):耗时约 3 秒。 -
发送新人优惠券(
sendCoupons()):耗时约 3 秒。
如果采用同步处理方式,客户端需要阻塞等待 7 秒 才能收到响应,这会导致用户体验较差,尤其是在高并发场景下,还可能引发请求超时、重试等问题。
优化方案
为了提升系统性能和用户体验,可以采用 异步处理 的方式,将耗时操作从主流程中解耦。以下是两种常见的实现方式:
1. 多线程异步
通过多线程技术,将耗时操作(如发送短信、发放优惠券)放到独立的线程中执行,主线程只需处理核心逻辑(如插入会员数据),从而快速响应客户端。
优点:
-
实现简单,适合小规模系统。
-
无需引入额外中间件。
缺点:
-
线程管理复杂,容易引发资源耗尽问题。
-
无法保证消息的可靠性和顺序性。
2. 消息队列(MQ)异步
通过消息队列(如 RabbitMQ)将耗时操作异步化,具体流程如下:
-
客户端发送注册请求。
-
服务器处理核心逻辑(如插入会员数据),并将后续耗时操作(发送短信、发放优惠券)封装为消息,发送到消息队列。
-
服务器立即响应客户端,无需等待耗时操作完成。
-
消费者从消息队列中获取消息,异步执行发送短信、发放优惠券等操作。
优点:
-
解耦业务逻辑,提升系统可扩展性。
-
支持高并发,避免请求阻塞。
-
保证消息的可靠性和顺序性。
缺点:
-
需要引入消息队列中间件,增加系统复杂度。
推荐方案:基于 RabbitMQ 的异步处理
对于互联网项目(客户端为 Android/iOS,服务器端为 PHP/Java),推荐使用 RabbitMQ 实现异步处理,具体流程如下:
-
客户端:发送注册请求。
-
服务器:
-
处理核心逻辑(如插入会员数据)。
-
将发送短信、发放优惠券等操作封装为消息,发送到 RabbitMQ。
-
立即响应客户端,减少等待时间。
-
-
RabbitMQ:
-
存储消息,确保消息不丢失。
-
将消息分发给消费者。
-
-
消费者:
-
从 RabbitMQ 获取消息,异步执行发送短信、发放优惠券等操作。
-
通过这种方式,客户端只需等待核心逻辑的处理时间(如 1 秒),而无需阻塞等待所有操作完成,从而显著提升用户体验和系统性能。
1.6 多线程处理业务逻辑

用户向数据库中插入一条数据之后,在单独开启一个线程异步发送短信和优惠操作。
客户端只需要等待1s时间
优点:适合于小项目 实现异步
缺点:有可能会消耗服务器 cpu等资源
1.7 Mq处理业务逻辑

先向数据库中插入一条会员数据,让后再向MQ中投递一个消息,MQ服务器端在将消息推送给消费者异步解耦处理发送短信和优惠券。
1.8 Mq与多线程之间区别
MQ可以实现异步/解耦/流量削峰问题;
多线程也可以实现异步,但是消耗到cpu资源,没有实现解耦。
2 Mq设计基础知识
多线程版本mq;
基于网络通讯版本mq netty实现
2.1 基于多线程队列简单实现mq
import com.alibaba.fastjson.JSONObject;
import java.util.concurrent.LinkedBlockingDeque;public class BoyatopThreadMQ {// 消息队列(Broker),用于存储生产者生成的消息private static LinkedBlockingDeque<JSONObject> broker = new LinkedBlockingDeque<>();public static void main(String[] args) {// 创建并启动生产者线程Thread producer = new Thread(() -> {while (true) {try {// 模拟生产者每隔1秒生成一条消息Thread.sleep(1000);// 创建消息数据JSONObject data = new JSONObject();data.put("phone", "18611111111");// 将消息放入队列broker.offer(data);System.out.println("生产者生成数据: " + data.toJSONString());} catch (InterruptedException e) {System.err.println("生产者线程被中断: " + e.getMessage());Thread.currentThread().interrupt(); // 恢复中断状态break;}}}, "生产者");producer.start();// 创建并启动消费者线程Thread consumer = new Thread(() -> {while (true) {try {// 从队列中获取消息JSONObject data = broker.poll();if (data != null) {// 处理消息System.out.println(Thread.currentThread().getName() + " 消费数据: " + data.toJSONString());}} catch (Exception e) {System.err.println("消费者处理数据异常: " + e.getMessage());}}}, "消费者");consumer.start();}
}
输出示例:
生产者生成数据: {"phone":"18611111111"}
消费者 消费数据: {"phone":"18611111111"}
生产者生成数据: {"phone":"18611111111"}
消费者 消费数据: {"phone":"18611111111"}
...
2.2 基于netty实现mq
消费者netty客户端与nettyServer端MQ服务器端保持长连接,MQ服务器端保存消费者连接。
生产者netty客户端发送请求给nettyServer端MQ服务器端,MQ服务器端再将该消息内容发送给消费者。

body:{"msg":{"userId":"123456","age":"23"},"type":"producer",”topic”:””}
生产者投递消息与消息可靠性
1. 消息持久化机制
生产者通过 Netty 客户端将消息投递到 RabbitMQ 服务器后,RabbitMQ 服务器需要缓存该消息。为了防止 RabbitMQ 服务器宕机导致消息丢失,RabbitMQ 采用了 持久化机制:
-
消息持久化:将消息存储到磁盘中,确保即使服务器重启,消息也不会丢失。
-
队列持久化:将队列的元数据(如队列名称、绑定关系等)持久化到磁盘。
-
交换机持久化:将交换机的元数据持久化到磁盘。
通过持久化机制,RabbitMQ 能够在服务器宕机后恢复消息和元数据,确保消息不丢失。
2. 消息确认机制
当 RabbitMQ 接收到生产者投递的消息后,即使消费者暂时不在线,消息也不会丢失。这是因为 RabbitMQ 采用了 消息确认机制:
-
生产者确认(Publisher Confirm):生产者发送消息后,RabbitMQ 会返回一个确认(ACK),表示消息已成功接收并持久化。
-
消费者确认(Consumer Ack):消费者成功消费消息后,会通过 Netty 长连接向 RabbitMQ 发送确认(ACK),RabbitMQ 才会将该消息从队列中删除。
-
消息重试:如果消费者未发送确认,RabbitMQ 会认为消息未被成功消费,并将其重新投递给其他消费者。
消息投递模式
1. RabbitMQ 服务器主动推送消息
-
RabbitMQ 服务器与消费者通过 Netty 保持长连接。
-
当有新消息时,RabbitMQ 会通过 Netty 将消息推送给消费者。
-
这种模式适合实时性要求高的场景,但需要消费者具备较高的处理能力。
2. 消费者主动拉取消息
-
消费者可以通过 Netty 客户端主动从 RabbitMQ 拉取消息。
-
消费者首次启动时,会从 RabbitMQ 拉取未消费的消息。
-
这种模式适合消费者处理能力有限的场景,但可能存在一定的延迟。
RabbitMQ 的高并发设计思想
1. 基于 Netty 的高性能网络通信
-
RabbitMQ 使用 Netty 作为网络通信框架,支持高并发的消息传输。
-
Netty 的异步、事件驱动模型能够高效处理大量并发连接,适合 RabbitMQ 的高并发场景。
2. 消费者负载均衡
-
RabbitMQ 支持多个消费者同时消费一个队列中的消息,实现负载均衡。
-
通过 Netty 长连接,RabbitMQ 可以将消息均匀分发给多个消费者,提升整体消费能力。
3. 消息预取(Prefetch)
-
RabbitMQ 允许消费者根据自身能力设置预取数量(Prefetch Count),即一次性从队列中拉取多条消息。
-
通过调整预取数量,可以平衡消费者的处理能力和消息消费速率。
消费者消费速率的优化
1. 消费者集群
-
通过部署多个消费者实例(消费者集群),实现消息的并行处理,提升整体消费能力。
-
RabbitMQ 会自动将消息分发给不同的消费者,避免单点瓶颈。
2. 批量拉取消息
-
消费者可以通过 Netty 客户端一次性拉取多条消息进行批量处理,减少网络通信开销,提高消费效率。
3. 异步处理
-
消费者可以使用 Netty 的异步处理机制,将消息处理任务交给线程池或异步框架执行,避免阻塞主线程。
潜在问题与解决方案
1. 消息消费延迟
-
问题:在高并发场景下,消费者可能无法及时处理消息,导致消息积压和延迟。
-
解决方案:
-
增加消费者实例,提升并行处理能力。
-
调整预取数量,优化消费者的消息拉取策略。
-
使用批量处理和异步处理机制,提高消费速率。
-
2. 消息重复消费
-
问题:在网络抖动或消费者异常的情况下,可能导致消息重复消费。
-
解决方案:
-
在消费者端实现幂等性处理,确保多次消费同一条消息不会产生副作用。
-
使用分布式锁或唯一标识(如消息ID)避免重复处理。
-
Maven依赖
<dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.0.23.Final</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version></dependency></dependencies>
2.3 Mq消息中间件名词
- Producer 生产者:投递消息到MQ服务器端;
- Consumer 消费者:从MQ服务器端获取消息处理业务逻辑;
- Broker MQ服务器端
- Topic 主题:分类业务逻辑发送短信主题、发送优惠券主题
- Queue 存放消息模型 队列 先进先出 后进后出原则 数组/链表
- Message 生产者投递消息报文:json
2.4 主流mq区别对比
| 特性 | ActiveMQ | RabbitMQ | RocketMQ | kafka |
| 开发语言 | java | erlang | java | scala |
| 单机吞吐量 | 万级 | 万级 | 10万级 | 10万级 |
| 时效性 | ms级 | us级 | ms级 | ms级以内 |
| 可用性 | 高(主从架构) | 高(主从架构) | 非常高(分布式架构) | 非常高(分布式架构) |
| 功能特性 | 成熟的产品,在很多公司得到应用;有较多的文档;各种协议支持较好 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低管理界面较丰富 | MQ功能比较完备,扩展性佳 | 只支持主要的MQ功能,像一些消息查询,消息回溯等功能没有提供,毕竟是为大数据准备的,在大数据领域应用广。 |
3 RabbitMQ
3.1 RabbitMQ基本介绍
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件),RabbitMQ服务器是用Erlang语言编写的。
RabitMQ官方网站:
RabbitMQ: One broker to queue them all | RabbitMQ

1.点对点(简单)的队列
2.工作(公平性)队列模式
3.发布订阅模式
4.路由模式Routing
5.通配符模式Topics
6.RPC
https://www.rabbitmq.com/getstarted.html
3.2 RabbitMQ基本安装
由于都是外网的文件,开个梯子会有意想不到的效果
1.给出Erlang官网下载地址:Downloads - Erlang/OTP

等待下载,需要空间大概400MB少一点
2.配置erlang环境变量
找到控制面板里的环境变量,并新增一个系统变量。

我把该文件放在了F盘下,根据自己的需要编写变量值。

双击path,新增路径%ERLANG_HOME%\bin

3.打开命令窗口,输入erl或者erl -version()验证环境是否配置成功
一定得记住,打开的这些个窗口一定都得点确定保存!如果出现了命令行告诉你erl不是内部或者外部命令可能就是由于这个,亦或者是你的环境变量设置的不对。

4 安装rabbitmq
给出RabbitMQ官网下载址:Installing on Windows — RabbitMQ。找到exe文件并点击下载安装

5 配置Rabbitmq环境变量


注意是sbin,是根据你目录来的

6 安装管理工具RabbitMQ-Plugins,进入sbin文件下,打开命令窗口输入
rabbitmq-plugins enable rabbitmq_management
直接进入目录,输入cmd跳进命令行
7 安装好管理工具后,进入sbin目录 ,点击rabbit_server.bat,启动rabbitMQ

8 最后输入http://localhost:15672/(默认账号:guest,密码:guest)就能进入RabbitMQ管理界面

3.3 RabbitMQ 启动常见问题:无法访问管理平台
问题描述
RabbitMQ 启动成功后,无法通过浏览器访问管理平台页面(通常为 http://localhost:15672)。
原因分析
默认情况下,RabbitMQ 的管理插件(rabbitmq_management)并未自动启用,需要手动启用该插件。
解决方案
1. 启用 RabbitMQ 管理插件
-
打开命令行工具,并导航到 RabbitMQ 的
sbin目录。例如:cd F:\path\rabbitmq\rabbitmq\rabbitmq_server-3.6.9\sbin
-
执行以下命令,启用管理插件:
rabbitmq-plugins enable rabbitmq_management
2. 启动 RabbitMQ 应用
如果 RabbitMQ 未完全启动,可以执行以下命令启动应用
rabbitmqctl start_app
3. 验证管理平台
-
打开浏览器,访问 RabbitMQ 管理平台
http://localhost:15672
-
使用默认用户名和密码登录:
-
用户名:
guest -
密码:
guest
-
注意事项
-
确保 RabbitMQ 服务已启动。可以通过以下命令检查服务状态:
rabbitmqctl status
-
如果仍然无法访问,请检查防火墙设置,确保端口
15672未被阻止。 -
如果使用的是云服务器或远程主机,请确保安全组或防火墙规则允许外部访问
15672端口。

3.4 Rabbitmq管理平台中心
RabbitMQ 管理平台地址 http://127.0.0.1:15672
默认账号:guest/guest 用户可以自己创建新的账号
Virtual Hosts:
像mysql有数据库的概念并且可以指定用户对库和表等操作的权限。那RabbitMQ呢?
RabbitMQ也有类似的权限管理。在RabbitMQ中可以虚拟消息服务器VirtualHost,每个VirtualHost相当月一个相对独立的RabbitMQ服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。
- 默认的端口15672:rabbitmq管理平台端口号
- 默认的端口5672: rabbitmq消息中间内部通讯的端口
- 默认的端口号25672 rabbitmq集群的端口号
3.5 RabbitMQ 核心概念与关键配置
1. Virtual Hosts(虚拟主机)
-
定义:虚拟主机是 RabbitMQ 中资源的逻辑隔离单元,类似于数据库中的“命名空间”。
-
作用:
-
实现不同业务或环境(如开发、测试、生产)的消息队列隔离。
-
每个 Virtual Host 拥有独立的队列、交换机、绑定关系等资源。
-
-
示例:
-
/booyaVirtualHosts:用于订单和支付业务。 -
/test:用于测试环境。
-
2. Queue(队列)
-
定义:队列是消息的临时存储容器,生产者将消息发送到队列,消费者从队列中获取消息。
-
特性:
-
持久化:队列可配置为持久化(Durable),防止 RabbitMQ 重启后丢失。
-
自动删除:队列在无消费者时自动删除(Auto-Delete)。
-
-
示例:
-
订单队列:存储订单相关消息。 -
支付队列:存储支付相关消息。
-
3. Exchange(交换机)
-
定义:交换机是消息路由的核心组件,负责将生产者的消息分发到指定队列。
-
路由模式:
-
Direct Exchange:基于路由键(Routing Key)精确匹配队列。
-
Topic Exchange:支持通配符的路由键匹配。
-
Fanout Exchange:广播消息到所有绑定队列。
-
Headers Exchange:基于消息头属性路由。
-
-
类比:
-
类似于邮局的分拣系统,根据地址(路由规则)将信件(消息)投递到正确的邮箱(队列)。
-
4. 关键端口与协议
| 端口号 | 协议/用途 | 说明 |
|---|---|---|
| 15672 | HTTP | RabbitMQ 管理平台端口,用于 Web 界面操作(默认地址:http://localhost:15672)。 |
| 5672 | AMQP(Advanced Message Queuing Protocol) | RabbitMQ 的核心通信端口,客户端(生产者/消费者)通过此端口与服务器交互。 |
| 25672 | Erlang 分布式通信 | RabbitMQ 节点间集群通信和数据同步的端口。 |
5. AMQP 协议
-
角色:AMQP 是 RabbitMQ 的核心协议,定义了消息的格式、传输规则及确认机制。
-
特点:
-
支持异步、可靠的消息传递。
-
提供事务、消息确认等机制保障可靠性。
-
4 快速入门RabbitMQ简单队列
4.1. 创建 Virtual Host 和队列
在 RabbitMQ 管理平台中,首先需要创建一个 Virtual Host 和队列。
-
登录 RabbitMQ 管理平台:
-
打开浏览器,访问
http://localhost:15672。 -
使用默认用户名和密码登录:
-
用户名:
guest -
密码:
guest
-
-
-
创建 Virtual Host:
-
在管理平台中,导航到
Admin选项卡。 -
点击
Virtual Hosts,然后点击Add a new virtual host。 -
输入 Virtual Host 名称(例如
/booyaVirtualHosts),点击Add virtual host。
-
-
创建队列:
-
导航到
Queues选项卡。 -
点击
Add a new queue。 -
输入队列名称(例如
订单队列),选择刚刚创建的 Virtual Host(/booyaVirtualHosts),点击Add queue。 -
重复上述步骤,创建另一个队列(例如
支付队列)。
-
4.2 编写生产者代码
生产者负责向 RabbitMQ 队列发送消息。
public class RabbitMQConnection {/*** 获取连接** @return*/public static Connection getConnection() throws IOException, TimeoutException {// 1.创建连接ConnectionFactory connectionFactory = new ConnectionFactory();// 2.设置连接地址connectionFactory.setHost("127.0.0.1");// 3.设置端口号:connectionFactory.setPort(5672);// 4.设置账号和密码connectionFactory.setUsername("zhangsan");connectionFactory.setPassword("123456");// 5.设置VirtualHostconnectionFactory.setVirtualHost("/Boyatop");return connectionFactory.newConnection();}
}
public class Producer {private static final String QUEUE_NAME = "Boyatop";public static void main(String[] args) throws IOException, TimeoutException {// 1.创建连接Connection connection = RabitMQConnection.getConnection();// 2.设置通道Channel channel = connection.createChannel();// 3.设置消息String msg = "hello world";System.out.println("msg:" + msg);channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());channel.close();connection.close();}
}
Maven依赖
<dependencies><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>3.6.5 </version></dependency>
</dependencies>
4.3 编写消费者代码
消费者负责从 RabbitMQ 队列中接收并处理消息。
public class Consumer {private static final String QUEUE_NAME = "Boyatop";public static void main(String[] args) throws IOException, TimeoutException {// 1.创建连接Connection connection = RabitMQConnection.getConnection();// 2.设置通道Channel channel = connection.createChannel();DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("消费者获取消息:" + msg);}};// 3.监听队列channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}
4.4 RabbitMQ如何保证消息不丢失
Mq如何保证消息不丢失:
4.4.1 生产者角色
确保生产者投递消息到MQ服务器端成功。
Ack 消息确认机制
同步或者异步的形式
- 方式1:Confirms
- 方式2:事务消息
4.4.2 消费者角色
在rabbitmq情况下:
必须要将消息消费成功之后,才会将该消息从mq服务器端中移除。
在kafka中的情况下:
不管是消费成功还是消费失败,该消息都不会立即从mq服务器端移除。
4.4.3 Mq服务器端 在默认的情况下 都会对队列中的消息实现持久化
1 使用消息确认机制+持久技术
A.消费者确认收到消息机制
channel.basicConsume(QUEUE_NAME, false, defaultConsumer);
注:第二个参数值为false代表关闭RabbitMQ的自动应答机制,改为手动应答。
在处理完消息时,返回应答状态,true表示为自动应答模式。
channel.basicAck(envelope.getDeliveryTag(), false);
B.生产者确认投递消息成功 使用Confirm机制 或者事务消息 
Confirm机制 同步或者是异步的形式
RabbitMQ默认创建是持久化的

代码中设置 durable为true
参数名称详解:
durable是否持久化 durable为持久化、 Transient 不持久化
autoDelete 是否自动删除,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除
2 使用rabbitmq事务消息;
channel.txSelect();channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());channel.txCommit();
相关核心代码
生产者
public class Producer {private static final String QUEUE_NAME = "Boyatop-queue";public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {//1.创建一个新连接Connection connection = RabbitMQConnection.getConnection();//2.设置channelChannel channel = connection.createChannel();//3.发送消息String msg = "Hello Wrold6666";
channel.confirmSelect();channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());boolean result = channel.waitForConfirms();if (result) {System.out.println("消息投递成功");} else {System.out.println("消息投递失败");}channel.close();connection.close();}
}
消费者
public class Consumer {private static final String QUEUE_NAME = "Boyatop-queue";public static void main(String[] args) throws IOException, TimeoutException, IOException, TimeoutException {// 1.创建连接Connection connection = RabbitMQConnection.getConnection();// 2.设置通道Channel channel = connection.createChannel();DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("消费者获取消息:" + msg);// 消费者完成 消费该消息channel.basicAck(envelope.getDeliveryTag(), false);}};// 3.监听队列channel.basicConsume(QUEUE_NAME, false, defaultConsumer);}
}
5 RabbitMQ五种消息模式
5.1RabitMQ工作队列
默认的传统队列是为均摊消费,存在不公平性;如果每个消费者速度不一样的情况下,均摊消费是不公平的,应该是能者多劳。

采用工作队列
在通道中只需要设置basicQos为1即可,表示MQ服务器每次只会给消费者推送1条消息必须手动ack确认之后才会继续发送。
channel.basicQos(1);
5.2 RabbitMQ交换机类型
- Direct exchange(直连交换机)
- Fanout exchange(扇型交换机)
- Topic exchange(主题交换机)
- Headers exchange(头交换机)
- /Virtual Hosts---区分不同的团队
- ----队列 存放消息
- ----交换机 路由消息存放在那个队列中 类似于nginx
- ---路由key 分发规则
5.3 RabbitMQ Fanout 发布订阅
生产者发送一条消息,经过交换机转发到多个不同的队列,多个不同的队列就多个不同的消费者。

原理:
- 需要创建两个队列 ,每个队列对应一个消费者;
- 队列需要绑定我们交换机
- 生产者投递消息到交换机中,交换机在将消息分配给两个队列中都存放起来;
- 消费者从队列中获取这个消息。
生产者代码
import com.Boyatop.rabbitmq.RabbitMQConnection;import com.rabbitmq.client.Channel;import com.rabbitmq.client.Connection;import java.io.IOException;import java.util.concurrent.TimeoutException;public class ProducerFanout {/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {// 创建ConnectionConnection connection = RabbitMQConnection.getConnection();// 创建ChannelChannel channel = connection.createChannel();// 通道关联交换机channel.exchangeDeclare(EXCHANGE_NAME, "fanout", true);String msg = "Hello Wrold6666";channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());channel.close();connection.close();}
}
消费者代码
邮件消费者
import com.Boyatop.rabbitmq.RabbitMQConnection;import com.rabbitmq.client.*;import java.io.IOException;import java.util.concurrent.TimeoutException;public class MailConsumer {/*** 定义邮件队列*/private static final String QUEUE_NAME = "fanout_email_queue";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("邮件消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("邮件消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}}
短信消费者
public class SmsConsumer {/*** 定义短信队列*/private static final String QUEUE_NAME = "fanout_email_sms";/*** 定义交换机的名称*/private static final String EXCHANGE_NAME = "fanout_exchange";public static void main(String[] args) throws IOException, TimeoutException {System.out.println("短信消费者...");// 创建我们的连接Connection connection = RabbitMQConnection.getConnection();// 创建我们通道final Channel channel = connection.createChannel();// 关联队列消费者关联队列channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {@Overridepublic void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {String msg = new String(body, "UTF-8");System.out.println("短信消费者获取消息:" + msg);}};// 开始监听消息 自动签收channel.basicConsume(QUEUE_NAME, true, defaultConsumer);}
}
5.4 Direct路由模式
当交换机类型为direct类型时,根据队列绑定的路由建转发到具体的队列中存放消息
5.5 Topic主题模式
当交换机类型为topic类型时,根据队列绑定的路由建模糊转发到具体的队列中存放。
#号表示支持匹配多个词;
*号表示只能匹配一个词

6 SpringBoot整合RabbitMQ
6.1 Maven依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.0.RELEASE</version></parent><dependencies><!-- springboot-web组件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 添加springboot对amqp的支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><!--fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.49</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies>
6.2 配置类
import org.springframework.amqp.core.Binding;import org.springframework.amqp.core.BindingBuilder;import org.springframework.amqp.core.FanoutExchange;import org.springframework.amqp.core.Queue;import org.springframework.context.annotation.Bean;import org.springframework.stereotype.Component;/*** @ClassName RabbitMQConfig* @Author * @Version V1.0**/@Componentpublic class RabbitMQConfig {/*** 定义交换机*/private String EXCHANGE_SPRINGBOOT_NAME = "/Boyatop_ex";/*** 短信队列*/private String FANOUT_SMS_QUEUE = "fanout_sms_queue";/*** 邮件队列*/private String FANOUT_EMAIL_QUEUE = "fanout_email_queue";/*** 配置smsQueue** @return*/@Beanpublic Queue smsQueue() {return new Queue(FANOUT_SMS_QUEUE);}/*** 配置emailQueue** @return*/@Beanpublic Queue emailQueue() {return new Queue(FANOUT_EMAIL_QUEUE);}/*** 配置fanoutExchange** @return*/@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange(EXCHANGE_SPRINGBOOT_NAME);}// 绑定交换机 sms@Beanpublic Binding bindingSmsFanoutExchange(Queue smsQueue, FanoutExchange fanoutExchange) {return BindingBuilder.bind(smsQueue).to(fanoutExchange);}// 绑定交换机 email@Beanpublic Binding bindingEmailFanoutExchange(Queue emailQueue, FanoutExchange fanoutExchange) {return BindingBuilder.bind(emailQueue).to(fanoutExchange);}
}
6.3 配置文件
application.yml
spring:rabbitmq:####连接地址host: 127.0.0.1####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: booyatopVirtualHosts
6.4 生产者
import org.springframework.amqp.core.AmqpTemplate;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;/*** @ClassName FanoutProducer* @Author www.Boyatop.com* @Version V1.0**/@RestControllerpublic class FanoutProducer {@Autowiredprivate AmqpTemplate amqpTemplate;/*** 发送消息** @retur*/@RequestMapping("/sendMsg")public String sendMsg(String msg) {/*** 1.交换机名称* 2.路由key名称* 3.发送内容*/amqpTemplate.convertAndSend("/Boyatop_ex", "", msg);return "success";}
}
6.5 消费者
import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.rabbit.annotation.RabbitHandler;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.stereotype.Component;/*** @ClassName FanoutEmailConsumer* @Author www.Boyatop.com* @Version V1.0**/
@Slf4j
@Component
@RabbitListener(queues = "fanout_email_queue")public class FanoutEmailConsumer {@RabbitHandlerpublic void process(String msg) {log.info(">>邮件消费者消息msg:{}<<", msg);}
}
import lombok.extern.slf4j.Slf4j;import org.springframework.amqp.rabbit.annotation.RabbitHandler;import org.springframework.amqp.rabbit.annotation.RabbitListener;import org.springframework.stereotype.Component;/*** @ClassName fanout_sms_queue* @Author www.Boyatop.com* @Version V1.0**/
@Slf4j
@Component
@RabbitListener(queues = "fanout_sms_queue")public class FanoutSmsConsumer {@RabbitHandlerpublic void process(String msg) {log.info(">>短信消费者消息msg:{}<<", msg);}
}
7 生产者如何获取消费结果
-
根据业务来定
消费者消费成功结果:
能够在数据库中插入一条数据
2. Rocketmq 自带全局消息id,能够根据该全局消息获取消费结果
原理:
生产者投递消息到mq服务器,mq服务器端在这时候返回一个全局的消息id,
当我们消费者消费该消息成功之后,消费者会给我们mq服务器端发送通知标记该消息
消费成功。
生产者获取到该消息全局id,每隔2s时间调用mq服务器端接口查询该消息是否
有被消费成功。
- 异步返回一个全局id,前端使用ajax定时主动查询;
- 在rocketmq中,自带根据消息id查询是否消费成功
8 RabbitMQ死信队列
8.1 死信队列产生的背景
RabbitMQ死信队列俗称,备胎队列;消息中间件因为某种原因拒收该消息后,可以转移到死信队列中存放,死信队列也可以有交换机和路由key等。
8.2 产生死信队列的原因
- 消息投递到MQ中存放 消息已经过期 消费者没有及时的获取到我们消息,消息如果存放到mq服务器中过期之后,会转移到备胎死信队列存放。
- 队列达到最大的长度 (队列容器已经满了)
- 消费者消费多次消息失败,就会转移存放到死信队列中

代码整合 参考 Boyatop-springboot-rabbitmq|#中order-dead-letter-queue项目
8.3 死信队列的架构原理
死信队列和普通队列区别不是很大
普通与死信队列都有自己独立的交换机和路由key、队列和消费者。
区别:
1.生产者投递消息先投递到我们普通交换机中,普通交换机在将该消息投到
普通队列中缓存起来,普通队列对应有自己独立普通消费者。
2.如果生产者投递消息到普通队列中,普通队列发现该消息一直没有被消费者消费
的情况下,在这时候会将该消息转移到死信(备胎)交换机中,死信(备胎)交换机
对应有自己独立的 死信(备胎)队列 对应独立死信(备胎)消费者。
8.4 死信队列应用场景
30分钟订单超时设计
- Redis过期key :
- 死信延迟队列实现:
采用死信队列,创建一个普通队列没有对应的消费者消费消息,在30分钟过后
就会将该消息转移到死信备胎消费者实现消费。
备胎死信消费者会根据该订单号码查询是否已经支付过,如果没有支付的情况下
则会开始回滚库存操作。
9 RabbitMQ消息幂等问题
9.1 RabbitMQ消息自动重试机制
1.当我们消费者处理执行我们业务代码的时候,如果抛出异常的情况下
在这时候mq会自动触发重试机制,默认的情况下rabbitmq是无限次数的重试。需要人为指定重试次数限制问题
2.在什么情况下消费者需要实现重试策略?
A.消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试?
该情况下需要实现重试策略,网络延迟只是暂时调用不通,重试多次有可能会调用通。
B.消费者获取消息后,因为代码问题抛出数据异常,是否需要重试?
该情况下是不需要实现重试策略,就算重试多次,最终还是失败的。可以将日志存放起来,后期通过定时任务或者人工补偿形式。
如果是重试多次还是失败消息,需要重新发布消费者版本实现消费。可以使用死信队列
3 Mq在重试的过程中,有可能会引发消费者重复消费的问题。
Mq消费者需要解决 幂等性问题
幂等性 保证数据唯一
方式1:
生产者在投递消息的时候,生成一个全局唯一id,放在我们消息中。
Msg id=123456
Msg id=123456
Msg id=123456
消费者获取到我们该消息,可以根据该全局唯一id实现去重复。
全局唯一id 根据业务来定的 订单号码作为全局的id
实际上还是需要再db层面解决数据防重复。
业务逻辑是在做insert操作 使用唯一主键约束
业务逻辑是在做update操作 使用乐观锁
- 当消费者业务逻辑代码中,抛出异常自动实现重试 (默认是无数次重试)
- 应该对RabbitMQ重试次数实现限制,比如最多重试5次,每次间隔3s;重试多次还是失败的情况下,存放到死信队列或者存放到数据库表中记录后期人工补偿
9.2 如何合理选择消息重试
- 消费者获取消息后,调用第三方接口,但是调用第三方接口失败呢?是否需要重试 ?
- 消费者获取消息后,应该代码问题抛出数据异常,是否需要重试?
总结:如果消费者处理消息时,因为代码原因抛出异常是需要从新发布版本才能解决的,那么就不需要重试,重试也解决不了该问题的。存放到死信队列或者是数据库表记录、后期人工实现补偿。
9.3 Rabbitmq如何开启重试策略
spring:rabbitmq:####连接地址host: 127.0.0.1####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: /booya_rabbitmqlistener:simple:retry:####开启消费者(程序出现异常的情况下会)进行重试enabled: true####最大重试次数max-attempts: 5####重试间隔次数initial-interval: 3000
9.4 消费者重试过程中,如何避免幂等性问题
重试的过程中,为了避免业务逻辑重复执行,建议提前全局id提前查询,如果存在的情况下,就无需再继续做该流程。重试的次数最好有一定间隔次数,在数据库底层层面保证数据唯一性,比如加上唯一id。
9.5 SpringBoot开启消息确认机制
配置文件新增
spring:rabbitmq:####连接地址host: 127.0.0.1####端口号port: 5672####账号username: guest####密码password: guest### 地址virtual-host: /booyaVirtualHostslistener:simple:retry:####开启消费者(程序出现异常的情况下会)进行重试enabled: true####最大重试次数max-attempts: 5####重试间隔次数initial-interval: 3000acknowledge-mode: manualdatasource:url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8username: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver
消费者ack代码
@Slf4j
@Component
@RabbitListener(queues = "fanout_order_queue")public class FanoutOrderConsumer {@Autowiredprivate OrderManager orderManager;@Autowiredprivate OrderMapper orderMapper;@RabbitHandlerpublic void process(OrderEntity orderEntity, Message message, Channel channel) throws IOException {// try {log.info(">>orderEntity:{}<<", orderEntity.toString());String orderId = orderEntity.getOrderId();if (StringUtils.isEmpty(orderId)) {log.error(">>orderId is null<<");return;}OrderEntity dbOrderEntity = orderMapper.getOrder(orderId);if (dbOrderEntity != null) {log.info(">>该订单已经被消费过,无需重复消费!<<");// 无需继续重试channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);return;}int result = orderManager.addOrder(orderEntity);log.info(">>插入数据库中数据成功<<");if (result >= 0) {// 开启消息确认机制 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);}int i = 1 / 0;} catch (Exception e) {// 将失败的消息记录下来,后期采用人工补偿的形式}}
}
相关文章:
RabbitMQ 从入门到精通
1 MQ架构设计原理 1.1 什么是消息中间件 消息中间件基于队列模型实现异步/同步传输数据 作用:可以实现支撑高并发、异步解耦、流量削峰、降低耦合度。 1.2 传统的http请求存在那些缺点 1.Http请求基于请求与响应的模型,在高并发的情况下,…...
考研复试c语言常见问答题汇总2
11. 关键字和一般标识符有什么不同? C语言中关键字与一般标识符区别: 定义:关键字是C语言预定义的特殊单词(如int、for),有固定含义;标识符是自定义的名称(如变量名、函数名…...
Qt表格美化笔记
介绍 表格是一种常见的数据管理界面形式,在大批量的数据交互情形下使用的比较多 表格 可以通过样式表设置线条以及边框的颜色 QTableWidget { gridline-color : rgb(55, 60, 62); border: 1px solid rgb(62,112,181);}表头 如果表头和第一行的分割线显示&#…...
致远互联FE协作办公平台 存在SQL注入漏洞(DVB-2025-8942)
免责声明 本文所描述的漏洞及其复现步骤仅供网络安全研究与教育目的使用。任何人不得将本文提供的信息用于非法目的或未经授权的系统测试。作者不对任何由于使用本文信息而导致的直接或间接损害承担责任。如涉及侵权,请及时与我们联系,我们将尽快处理并删除相关内容。 0x01…...
一、docker的安装
一、docker桌面 二、docker的配置文件 1、docker配置文件位置/etc/docker/daemon.json 使用json格式,graphdata-root {"graph":"/deploy/docker","registry-mirrors": ["https://8auvmfwy.mirror.aliyuncs.com"],"…...
『PostgreSQL』PGSQL备份与还原实操指南
📣读完这篇文章里你能收获到 了解逻辑备份与物理备份的区别及适用场景🔍。掌握全库、指定库、指定表备份还原的命令及参数📝。学会如何根据业务需求选择合适的备份策略📊。熟悉常见备份还原问题的排查与解决方法🔧。 …...
Redis 主从复制详解:实现高可用与数据备份
目录 引言 1. 什么是 Redis 主从复制? 1.1 定义 1.2 核心概念 2. Redis 主从复制的工作原理 2.1 复制流程 2.2 复制流程图 3. Redis 主从复制的配置方法 3.1 通过配置文件配置 主节点配置 从节点配置 3.2 通过命令行配置 设置从节点 取消从节点 4. Re…...
facebook游戏投广:提高广告关键数据的方法
在当今竞争激烈的数字营销领域,游戏广告的投放效果直接关系到游戏公司的市场表现和盈利能力。然而,许多游戏公司在广告投放上面临着诸多挑战,如高昂的成本、低效的转化率以及难以追踪的效果。那么,如何才能通过数据分析真正提升游…...
HybridCLR Generate All 报错UnityLinker.exe
现象: Generate All 报错 Building Library\Bee\artifacts\Android\ManagedStripped failed with output: E:\XingJiKongLong\HybridCLRData\LocalIl2CppData-WindowsEditor\il2cpp\build\deploy\UnityLinker.exe Library\Bee\artifacts\rsp\10776760506222613018.…...
大一新生备战蓝桥杯c/c++B组——2024年省赛真题解题+心得分享
一,握手问题 这个题用点像小学奥数,直接手算就行 答案:1204 二,小球反弹 这个题思路简单,但是运行会显示超时。在思考思考,后续补代码。 三,好数 思路一: #include <iostream&…...
【Java】——数据类型和变量
个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 文章目录: 1.Java中的注释1.1.基本规则1.2.注释规范 2.标识符3.关键字4.字面常量5.数据类型6.变量6.1变量的概念6.2语法6.3整型变量6.3.1整型变量6.3.2长整…...
docker 安装常用镜像
我们在上篇文章中已经修改了daemon.json 安装镜像时如果search超时就直接pull 安装mysql docker pull mysql:5.7 启动命令 docker run --name mysql-docker -p 3306:3306 -e MYSQL_ROOT_PASSWORDroot1234 -d mysql:5.7 ocker run:运行docker容器命令 --name my…...
SpringMVC 基本概念与代码示例
1. SpringMVC 简介 SpringMVC 是 Spring 框架中的一个 Web 层框架,基于 MVC(Model-View-Controller) 设计模式,提供了清晰的分层结构,适用于 Web 应用开发 SpringMVC 主要组件 DispatcherServlet(前端控…...
MKS HA-MFV:半导体制造中的高精度流量验证技术解析
引言 在半导体先进制程(如3nm节点)中,工艺气体流量的精准控制直接决定刻蚀、沉积等关键步骤的均匀性和良率。MKS Instruments推出的 HA-MFV(High Accuracy Mass Flow Verifier) 通过创新设计解决了传统流量验证技术的…...
版本号标识
Visual Studio 16 2019 是 Microsoft Visual Studio 2019 的版本号标识。具体来说: Visual Studio 是微软提供的一款集成开发环境(IDE),用于开发各种应用程序,如桌面软件、Web 应用、移动应用等,支持多种编…...
基于Python实现手写数字识别
KNN实验——手写数字识别 实验目的: 实验内容: 实现最基本的KNN算法,使用trainingDigits文件夹下的数据,对testDigits中的数据进行预测。(K赋值为1,使用欧氏距离,多数投票决定分类结果&#…...
shell的模拟实现 ─── linux第16课
目录 第一版只能维护命令行参数表创建子进程, 执行非内建命令 第一版的执行结果: 第二版能维护命令行参数表执行cd命令 ,判断了是否是自建命令(mysell自己执行自建命令,可以对环境变量发生改变),子进程执行其他命令. 第二版执行结果: 第三版 模拟真实shell从系统文件中获取环…...
游戏引擎学习第153天
仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾 目前正在进行的是一个比较大的系统调整,原本预计今天会继续深入这个改动,但实际上在昨天的开发中,我们已经完成了大部分的代码编写,并且运行之后几乎一切都能正常工作&#x…...
理解C语言中的extern关键字
在C语言编程中,extern关键字是一个非常重要的概念,尤其在多文件编程和全局变量的使用中。本文将详细解释extern的作用、用法以及常见的应用场景。 1. extern关键字的作用 extern关键字用于声明一个变量或函数是在其他文件中定义的。它告诉编译器&#x…...
【MyBatis Plus 逻辑删除详解】
文章目录 MyBatis Plus 逻辑删除详解前言什么是逻辑删除?MyBatis Plus 中的逻辑删除1. 添加逻辑删除字段2. 实体类的配置3. 配置 MyBatis Plus4. 使用逻辑删除5. 查询逻辑删除的记录 MyBatis Plus 逻辑删除详解 前言 MyBatis Plus 是一个强大的持久化框架…...
latex问题汇总
latex问题汇总 环境问题1 环境 texlive2024 TeXstudio 4.8.6 (git 4.8.6) 问题1 编译过程有如下错 ! Misplaced alignment tab character &. l.173 International Conference on Infrared &Millimeter Waves, 2004: 667--... I cant figure out why you would wa…...
基于Redis实现限流
限流尽可能在满足需求的情况下越简单越好! 1、基于Redsi的increment方法实现固定窗口限流 Redis的increment方法保证并发线程安全窗口尽可能越小越好(太大可能某一小段时间就打满请求剩下的都拿不到令牌了)这个原理其实就是用当前时间戳然后除窗口大小 在这个窗口大…...
力扣练习之确定两个字符串是否接近
目录 题目: 题解: 详细题解 题目: 如果可以使用以下操作从一个字符串得到另一个字符串,则认为两个字符串 接近 : 操作 1:交换任意两个 现有 字符。 例如,abcde -> aecdb 操作 2࿱…...
大三下找C++开发实习的感受分享
目录 找实习的过程 阶段一:投简历 阶段二:准备面试 阶段三:面试中 阶段四:面试结束后 面试真题 总结 找实习的过程 阶段一:投简历 第一次找实习还是使用BOSS这个软件进行投简历,这个过程其实挺难说…...
基于hive的电信离线用户的行为分析系统
标题:基于hive的电信离线用户的行为分析系统 内容:1.摘要 随着电信行业的快速发展,用户行为数据呈现出海量、复杂的特点。为了深入了解用户行为模式,提升电信服务质量和精准营销能力,本研究旨在构建基于 Hive 的电信离线用户行为分析系统。通…...
Makefile——make工具编译STM32工程
一、Makefile相关指令 1.1、变量 符号含义替换追加:恒等于 1.2、隐含规则 符号含义%.o任意的.o文件*.o所有的.o文件 1.3、通配符 符号含义$^所有依赖文件$所有目标文件$<所有依赖文件的第一个文件 1.4、编译器指令常用参数功能说明 符号含义举例-E预处理,…...
Java EE 进阶:SpringBoot 配置⽂件
什么是配置文件 “配置文件”是一个用来保护程序或者系统设置信息的文件,它的作用是让程序在启动或者运行中,能够读取这些设置并按预期进行工作,而不需要手动的设置。 Spring Boot 配置文件 设置服务器端口、编码格式配置数据库连接控制日…...
【redis】五种数据类型和编码方式
文章目录 五种数据类型编码方式stringhashlistsetzset查询内部编码 五种数据类型 字符串:Java 中的 String哈希:Java 中的 HashMap列表:Java 中的 List集合:Java 中的 Set有序集合:除了存 member 之外,还有…...
基于Python的电商销售数据分析与可视化系统实
一、系统架构设计 1.1系统流程图 #mermaid-svg-Pdo9oZWrVHNuOoTT {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-icon{fill:#552222;}#mermaid-svg-Pdo9oZWrVHNuOoTT .error-text{fill:#5…...
色板在数据可视化中的创新应用
色板在数据可视化中的创新应用:基于色彩感知理论的优化实践 引言 在数据可视化领域,色彩编码系统的设计已成为决定信息传递效能的核心要素。根据《Nature》期刊2024年发布的视觉认知研究,人类大脑对色彩的识别速度比形状快40%,色…...


