当前位置: 首页 > news >正文

Spring Boot+Redis 实现消息队列实践示例

Spring Boot+Redis 实现一个轻量级的消息队列

文章目录

  • Spring Boot+Redis 实现一个轻量级的消息队列
  • 0.前言
  • 1.基础介绍
  • 2.步骤
    • 2.1. 引入依赖
    • 2.2. 配置文件
    • 2.3. 核心源码
  • 4.总结
    • 答疑
  • 5.参考文档
  • 6. Redis从入门到精通系列文章

在这里插入图片描述

0.前言

本文将介绍如何利用Spring Boot与Redis结合实现消息队列的实践示例。消息队列是一种常用的解耦和异步通信的机制,可以在系统中实现高效的消息传递和处理。而Redis作为一种高性能的内存数据库,具备发布/订阅功能,非常适合用于构建消息队列系统。

在本实践示例中,我们将使用Spring Boot框架和Redis作为基础,通过Redis的发布/订阅模型来实现简单的消息队列。 如何通过消息通道进行消息的发布和订阅。

在开始之前,你需要对Spring Boot和Redis有一定的了解,并确保已正确配置好开发环境。本文将提供实际的代码示例和详细的步骤说明, 并能够根据具体业务需求进行扩展和定制。

1.基础介绍

Redis提供了发布/订阅(Publish/Subscribe)模式,用于实现消息的广播和异步通信。下面是关于Redis发布/订阅模式的介绍:

发布/订阅模式(Pub/Sub)
发布/订阅模式是一种消息通信模式,其中消息的发布者(Publisher)将消息发送到特定的频道(Channel),而订阅者(Subscriber)可以订阅一个或多个频道以接收消息。这种模式允许消息的广播和异步传递,发送者和接收者之间解耦。

Redis的发布/订阅功能
Redis提供了原生的发布/订阅功能,使得开发者可以使用Redis作为消息中间件来实现高效的消息传递。以下是与Redis发布/订阅相关的关键概念和操作:
下面是将Redis发布/订阅功能整理成表格的示例:

概念说明
频道(Channel)Redis中用于发布和订阅消息的通道。每个消息都被发布到一个特定的频道,而订阅者可以选择订阅一个或多个感兴趣的频道。
发布消息(Publish)通过使用PUBLISH命令,发布者可以将消息发送到指定的频道。一旦消息被发布到频道,所有订阅该频道的客户端将接收到该消息。
订阅频道(Subscribe)通过使用SUBSCRIBE命令,客户端可以订阅一个或多个频道。一旦订阅成功,客户端将成为该频道的订阅者,可以接收到该频道上发布的消息。
取消订阅频道(Unsubscribe)通过使用UNSUBSCRIBE命令,客户端可以取消对一个或多个频道的订阅。当客户端不再对某个频道感兴趣时,可以选择取消订阅。
模式匹配订阅(Pattern Subscription)Redis支持使用PSUBSCRIBE命令进行模式匹配订阅。通过指定一个模式,可以订阅与该模式匹配的多个频道。

通过Redis的发布/订阅功能,可以构建高效的消息系统,实现消息的广播和异步传递。发布者将消息发布到特定的频道,而订阅者可以选择订阅感兴趣的频道来接收消息。这种模式的优点包括解耦、异步通信和实时性等,适用于很多场景,如实时通知、事件驱动系统和分布式系统间的消息传递等。

本文我们通过Spring Boot +Redis 实现一个轻量级的消息队列。

2.步骤

2.1. 引入依赖

<dependencies><!-- Spring Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

2.2. 配置文件

# Redis连接配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=your_password
spring.redis.database=0# Redis连接池配置
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-wait=-1

在上面的配置中,您可以根据实际情况修改以下属性:

  • spring.redis.host:Redis服务器的主机名或IP地址。
  • spring.redis.port:Redis服务器的端口号。
  • spring.redis.password:Redis服务器的密码(如果有的话)。
  • spring.redis.database:Redis数据库的索引,默认为0。

另外,您还可以配置Redis连接池的属性,以控制连接池的行为。在示例配置中,设置了以下连接池属性:

  • spring.redis.jedis.pool.max-active:连接池中的最大活动连接数。
  • spring.redis.jedis.pool.max-idle:连接池中的最大空闲连接数。
  • spring.redis.jedis.pool.min-idle:连接池中的最小空闲连接数。
  • spring.redis.jedis.pool.max-wait:从连接池获取连接的最大等待时间(毫秒),-1表示无限等待。

如果 使用的是YAML格式的配置文件(application.yml),可以将上述配置转换为相应的格式:

spring:redis:host: 127.0.0.1port: 6379password: your_passworddatabase: 0redis.jedis.pool:max-active: 50max-idle: 10min-idle: 5max-wait: -1

请根据您的实际Redis服务器配置进行调整,并根据需要添加其他相关配置,如超时设置、SSL配置等。

2.3. 核心源码

当使用Spring Boot与Redis结合实现消息队列时,可以使用Redis的发布/订阅功能来处理消息的发布和订阅。下面是

接下来,创建一个消息发布者和一个消息订阅者的类。在发布者类中,使用RedisTemplate来发布消息,而在订阅者类中,通过实现MessageListener接口来处理接收到的消息。

// 消息发布者
@Component
public class MessagePublisher {private final RedisTemplate<String, String> redisTemplate;private final ChannelTopic topic;public MessagePublisher(RedisTemplate<String, String> redisTemplate, ChannelTopic topic) {this.redisTemplate = redisTemplate;this.topic = topic;}public void publishMessage(String message) {redisTemplate.convertAndSend(topic.getTopic(), message);}
}

消息订阅者

// 消息订阅者
@Component
public class MessageSubscriber implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {String receivedMessage = message.toString();// 处理接收到的消息System.out.println("Received message: " + receivedMessage);}
}

接下来,配置Redis消息监听容器,以便启动消息监听器:

@Configuration
public class RedisConfig {@Beanpublic ChannelTopic topic() {return new ChannelTopic("messageQueue"); // 定义消息队列的通道名称}@Beanpublic MessageListenerAdapter messageListenerAdapter(MessageSubscriber messageSubscriber) {return new MessageListenerAdapter(messageSubscriber);}@Beanpublic RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory,MessageListenerAdapter messageListenerAdapter,ChannelTopic topic) {RedisMessageListenerContainer container = new RedisMessageListenerContainer();container.setConnectionFactory(redisConnectionFactory);container.addMessageListener(messageListenerAdapter, topic);return container;}
}

最后,在需要发布消息的地方,注入MessagePublisher并调用publishMessage()方法来发布消息:

@Service
public class MyService {private final MessagePublisher messagePublisher;public MyService(MessagePublisher messagePublisher) {this.messagePublisher = messagePublisher;}public void doSomethingAndPublishMessage() {// 执行一些操作String message = "Hello, Redis message queue!";messagePublisher.publishMessage(message);}
}

这样,当调用MyService中的doSomethingAndPublishMessage()方法时,消息将被发布到Redis的消息队列中,并由订阅者接收和处理。

请注意,以上示例是一个简化的实现,仅用于演示基本的消息发布和订阅流程。在实际的生产环境中,可能需要考虑更多的细节,如错误处理、消息确认机制、并发处理等。

4.总结

通过Redis的发布/订阅模型,Spring Boot利用RedisTemplate将消息发布到指定的Redis通道,同时使用MessageListenerAdapter将订阅者与消息通道绑定。当有消息发布到通道时,Redis会将消息广播给所有订阅该通道的客户端,然后MessageListenerAdapter将接收到的消息传递给相应的消息监听器进行处理。

这样,利用Redis的发布/订阅功能,我们可以实现基于Spring Boot与Redis的消息队列系统,实现消息的发布和订阅。通过这种异步的方式,消息发布者和订阅者可以解耦,提高系统的可伸缩性和性能。

答疑

上次介绍了 Redis 的pub和sub,以及Stream 有很多同学有疑惑项目中应该使用哪种。那么我们在此处再聊聊
Redis提供了两种不同的功能来实现消息队列:发布/订阅(Pub/Sub)和流(Stream)。选择使用哪种功能作为消息队列取决于您的具体需求和使用场景。

特点Redis发布/订阅(Pub/Sub)Redis流(Stream)
适用场景简单的消息广播和异步通信场景更复杂的消息队列需求,包括任务队列、事件溯源、日志收集等
消息持久化不支持消息的持久化和历史记录支持消息的持久化和历史记录
消息订阅方式订阅者选择订阅感兴趣的频道来接收消息多个消费者对消息进行消费
订阅前发布的消息无法获取先于订阅的消息可以获取订阅前发布的消息
实时性适用于需要即时通知、实时事件处理和实时数据传递的场景实时性较高,但受消费者的处理速度影响
消费者组管理不支持消费者组管理支持消费者组管理
消息顺序性消息的顺序不保证消息的顺序保证
用例示例即时聊天应用、实时通知系统、发布/订阅模式的消息传递任务队列、事件溯源、日志收集等

所以如果你的需求是简单的消息广播和异步通信,并且对消息的持久化和历史记录要求不高,可以选择使用Redis发布/订阅功能作为消息队列。而如果您需要更丰富的消息队列功能,包括消息的持久化、多个消费者的支持和消息历史记录等,那么Redis流是更好的选择。根据具体的使用场景和需求,选择适合的功能来实现消息队列。

5.参考文档

  1. Redis官方文档 - 发布/订阅:

    • 链接:https://redis.io/topics/pubsub ↗
    • 该文档提供了Redis发布/订阅功能的详细说明,包括基本概念、命令使用、示例代码和常见问题解答等。
  2. Redis命令文档 - PUBLISH:

    • 链接:https://redis.io/commands/publish ↗
    • 该文档介绍了PUBLISH命令的语法、参数和用法,以及与发布消息相关的注意事项。
  3. Redis命令文档 - SUBSCRIBE:

    • 链接:https://redis.io/commands/subscribe ↗
    • 该文档详细说明了SUBSCRIBE命令的语法、参数和用法,以及如何订阅频道并接收发布的消息。
  4. Redis命令文档 - UNSUBSCRIBE:

    • 链接:https://redis.io/commands/unsubscribe ↗
    • 该文档提供了UNSUBSCRIBE命令的说明,包括取消订阅频道的语法和参数。
  5. Redis命令文档 - PSUBSCRIBE:

    • 链接:https://redis.io/commands/psubscribe ↗
    • 该文档介绍了PSUBSCRIBE命令的用法,用于进行模式匹配订阅,可以订阅与指定模式匹配的多个频道。

6. Redis从入门到精通系列文章

  • 《Redis使用Lua脚本和Redisson来保证库存扣减中的原子性和一致性》
  • 《SpringBoot Redis 使用Lettuce和Jedis配置哨兵模式》
  • 《Redis【应用篇】之RedisTemplate基本操作》
  • 《Redis 从入门到精通【实践篇】之SpringBoot配置Redis多数据源》
  • 《Redis 从入门到精通【进阶篇】之三分钟了解Redis HyperLogLog 数据结构》
  • 《Redis 从入门到精通【进阶篇】之三分钟了解Redis地理位置数据结构GeoHash》
  • 《Redis 从入门到精通【进阶篇】之高可用哨兵机制(Redis Sentinel)详解》
  • 《Redis 从入门到精通【进阶篇】之redis主从复制详解》
  • 《Redis 从入门到精通【进阶篇】之Redis事务详解》
  • 《Redis从入门到精通【进阶篇】之对象机制详解》
  • 《Redis从入门到精通【进阶篇】之消息传递发布订阅模式详解》
  • 《Redis从入门到精通【进阶篇】之持久化 AOF详解》
  • 《Redis从入门到精通【进阶篇】之持久化RDB详解》
  • 《Redis从入门到精通【高阶篇】之底层数据结构字典(Dictionary)详解》
  • 《Redis从入门到精通【高阶篇】之底层数据结构快表QuickList详解》
  • 《Redis从入门到精通【高阶篇】之底层数据结构简单动态字符串(SDS)详解》
  • 《Redis从入门到精通【高阶篇】之底层数据结构压缩列表(ZipList)详解》
  • 《Redis从入门到精通【进阶篇】之数据类型Stream详解和使用示例》
    在这里插入图片描述大家好,我是冰点,今天的 Spring Boot+Redis 实现消息队列实践示例,全部内容就是这些。如果你有疑问或见解可以在评论区留言。

相关文章:

Spring Boot+Redis 实现消息队列实践示例

Spring BootRedis 实现一个轻量级的消息队列 文章目录 Spring BootRedis 实现一个轻量级的消息队列0.前言1.基础介绍2.步骤2.1. 引入依赖2.2. 配置文件2.3. 核心源码 4.总结答疑 5.参考文档6. Redis从入门到精通系列文章 0.前言 本文将介绍如何利用Spring Boot与Redis结合实现…...

11. 实现业务功能--获取用户信息

目录 1. 实现 Controller 2. 单体测试 3. 修复返回值存在的缺陷 3.1 用户的隐私数据&#xff1a;密码的密文和盐不能显示 3.2 将值为 null 的字段可以进行过滤 3.3 时间的格式需要进行处理&#xff0c;如 yyyy-mmmm-ddd HH:mm:ss 3.4 data 属性没有返回 4. 实现前端页…...

HTTPS

HTTPS是什么 HTTPS 属于应用层协议&#xff0c;其原理是通过SSL/TLS协议在HTTP和TCP之间插入一层安全机制。通过SSL/TLS握手过程&#xff0c;客户端和服务器协商出一个对称密钥&#xff0c;用于后续的数据加密和解密&#xff0c;从而保证数据的机密性和完整性。 为什么会需要…...

spring详解

spring是于2003年兴起的一款轻量级的&#xff0c;非侵入式的IOC和AOP的一站式的java开发框架&#xff0c;为简化企业级应用开发而生。 轻量级的&#xff1a;指的是spring核心功能的jar包不大。 非侵入式的&#xff1a;业务代码不需要继承或实现spring中任何的类或接口 IOC&…...

香港服务器备案会通过吗?

​  对于企业或个人来说&#xff0c;合规备案是网络运营的基本要求&#xff0c;也是保护自身权益的重要举措。以下内容围绕备案展开话题&#xff0c;希望为您解开疑惑。 香港服务器备案会通过吗? 目前&#xff0c;香港服务器无法备案&#xff0c;这是由于国内管理规定的限制…...

乐鑫推出 ESP ZeroCode 控制台

乐鑫科技 ESP ZeroCode 控制台是一个网页应用&#xff0c;用户只需点击鼠标&#xff0c;描述想要创建的产品类型、功能及其硬件配置&#xff0c;即可按照自身需求&#xff0c;快速生成符合 Matter 认证的固件&#xff0c;并在硬件上进行试用。试用过程中&#xff0c;如有任何不…...

从NLP到聊天机器人

一、说明 今天&#xff0c;当打电话给银行或其他公司时&#xff0c;听到电话另一端的机器人向你打招呼是很常见的&#xff1a;“你好&#xff0c;我是你的数字助理。请问你的问题。是的&#xff0c;机器人现在不仅可以说人类语言&#xff0c;还可以用人类语言与用户互动。这是由…...

相关搜索引擎常用搜索语法(Google hacking语法和FOFA语法)

一&#xff1a;Google Hack语法 Google Hacking原指利用Google搜索引擎搜索信息来进行入侵的技术和行为&#xff0c;现指利用各种搜索引擎并使用一些高级的搜索语法来搜索信息。既利用搜索引擎强大的搜索功能&#xff0c;在在浩瀚的互联网中搜索到我们需要的信息。 &#xff0…...

Mysql查询

第三章&#xff1a;select 语句 SELECT employees.employee_id,employees.department_id FROM employees WHERE employees.employee_id176; DESC departments;SELECT * FROM departments;第四章&#xff1a;运算符使用 SELECT employees.last_name,employees.salary FROM em…...

解决http下navigator.clipboard为undefined问题

开发环境下使用navigator.clipboard进行复制操作&#xff0c;打包部署到服务器上后&#xff0c;发现该功能显示为undefined&#xff1b;查相关资料后&#xff0c;发现clipboard只有在安全域名下才可以访问(https、localhost)&#xff0c;在http域名下只能得到undefined&#xf…...

mysql之host is blocked问题

程序上线一段时间之后&#xff0c;更新程序总是遇到这个问题 每次都是重启几次程序&#xff0c;或者执行 flush hosts; 毕竟指标不治本&#xff0c;抽出时间决定分析一下问题&#xff0c;查阅了几篇博客。&#xff08;感谢这几位大佬&#xff09; https://blog.51cto.com/u_…...

每日一题:2337 移动片段得到字符串

给你两个字符串 start 和 target &#xff0c;长度均为 n 。每个字符串 仅 由字符 L、R 和 _ 组成&#xff0c;其中&#xff1a; 字符 L 和 R 表示片段&#xff0c;其中片段 L 只有在其左侧直接存在一个 空位 时才能向 左 移动&#xff0c;而片段 R 只有在其右侧直接存在一个 …...

嵌入式设备的 Json 库基本使用

大家好&#xff0c;今天给介绍一款基于 C 语言的轻量级的 Json 库 – cJson。可用于资源受限的嵌入式设备中。 cJSON 是一个超轻巧&#xff0c;携带方便&#xff0c;单文件&#xff0c;简单的可以作为 ANSI-C 标准的 JSON 解析器。 cJSON 是一个开源项目&#xff0c;github 下…...

GEEMAP 中如何拉伸图像

图像拉伸是最基础的图像增强显示处理方法&#xff0c;主要用来改善图像显示的对比度&#xff0c;地物提取流程中往往首先要对图像进行拉伸处理。图像拉伸主要有三种方式&#xff1a;线性拉伸、直方图均衡化拉伸和直方图归一化拉伸。 GEE 中使用 .sldStyle() 的方法来进行图像的…...

软件测试学术顶会——ISSTA 2023 论文(网络安全方向)清单、摘要与总结

总结 本次会议涵盖的安全研究主题广泛,包括源代码分析、二进制代码分析、恶意软件检测、漏洞检测、模糊测试、程序验证等。一些热门的研究方向包括:基于机器学习的漏洞检测、大型语言模型在软件安全中的应用、区块链智能合约安全分析。这些方向都在最近几年持续发展。一些较冷门…...

基于YOLOv8模型和PCB电子线路板缺陷目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOv8模型PCB电子线路板缺陷目标检测系统可用于日常生活中检测与定位PCB线路板瑕疵&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检…...

centos安装mysql8

检查是否有mariadb rpm -qa|grep mariadb rpm -e --nodeps mariadb-server安装mysql-8.0.31-el7-x86_64.tar.gz包 安装mysql-8.0.31-el7-x86_64.tar.gz包 cd datatar -xvf mysql-8.0.31-el7-x86_64.tar.gzmv mysql-8.0.31-el7-x86_64 mysql 依赖安装 报错解决&#xff0c;安…...

【Apollo】阿波罗自动驾驶技术:引领汽车行业革新

前言 Apollo (阿波罗)是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自动驾驶领域的合作伙伴结合车辆和硬件系统&#xff0c;快速搭建一套属于自己的自动驾驶系统。 开放能力、共享资源、加速创新、持续共赢是 Apollo 开放平台的口号。百度把自己所拥有的强大、…...

一文看懂!数据管道和数据流在数据分析中的作用

当我们谈论数据分析时&#xff0c;我们通常会想到一系列的步骤&#xff0c;包括数据收集、数据清洗、数据分析和数据可视化等。然而&#xff0c;在这些步骤中&#xff0c;有两个非常重要的概念&#xff1a;数据管道和数据流。这两个概念在数据分析过程中起着至关重要的作用。本…...

Linux系统下检验Tensorflow 2.xx版本和1.xx版本是否安装成功

目录 版本问题Tensorflow 1.xx的测试代码&#xff1a;Tensorflow 2.xx的测试代码&#xff1a;Tensorflow 2.6版本实际的测验结果 总结 版本问题 查询资料发现&#xff0c;多数检验Tensorflow是否安装成功的方法&#xff0c;多数方法都是1.xx版本的&#xff0c;直接使用1.xx版本…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器

第一章 引言&#xff1a;语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域&#xff0c;文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量&#xff0c;支撑着搜索引擎、推荐系统、…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表

##鸿蒙核心技术##运动开发##Sensor Service Kit&#xff08;传感器服务&#xff09;# 前言 在运动类应用中&#xff0c;运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据&#xff0c;如配速、距离、卡路里消耗等&#xff0c;用户可以更清晰…...