Files
cn-rdms/rdms-framework/rdms-spring-boot-starter-redis/README.md
2026-03-11 19:32:37 +08:00

390 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# rdms-spring-boot-starter-redis
## 模块定位
`rdms-spring-boot-starter-redis` 不是单纯把 Redis 依赖引进来,而是把项目里常用的 Redis 使用约定统一收口。
当前模块主要承担三类职责:
1. 统一 `RedisTemplate` 的序列化策略
2. 统一 Spring Cache 基于 Redis 的实现方式
3. 引入 Redisson为分布式锁、限流等上层模块提供基础能力
它的重点不是封装大量 Redis 工具类而是先把“Redis 怎么接入、怎么序列化、缓存怎么配置”这些基础设施统一好。
## 设计思路
## 1. 先统一 RedisTemplate再让上层模块复用
模块通过 `RdmsRedisAutoConfiguration` 注册了统一的 `RedisTemplate<String, Object>`,核心约定是:
1. Key 使用字符串序列化
2. Hash Key 使用字符串序列化
3. Value 使用 JSON 序列化
4. Hash Value 使用 JSON 序列化
这样可以避免各模块各自定义一套 `RedisTemplate`,导致同一个 Redis 里出现不同的序列化格式。
## 2. 优先解决“可读性和兼容性”问题
这个模块没有采用 JDK 默认序列化,而是直接使用 JSON 序列化。这样做的直接收益是:
1. Redis 中的数据结构更容易观察和排查
2. 避免 JDK 序列化带来的可读性差和兼容性问题
3. 方便不同模块围绕同一种序列化格式工作
同时,它还额外处理了 `LocalDateTime` 的序列化问题,避免 Java 时间类型在 Redis 中读写异常。
## 3. 把 Cache 也纳入统一约定
模块不只提供 `RedisTemplate`,还把 Spring Cache 一起接入 Redis
1. 开启 `@EnableCaching`
2. 统一 `RedisCacheConfiguration`
3. 统一 `RedisCacheManager`
4. 扩展支持按缓存名声明过期时间
这说明模块的目标不是“给你 Redis 客户端自己玩”,而是先把项目里高频使用的缓存场景标准化。
## 4. 通过小扩展解决 Spring Cache 默认能力不够用的问题
模块里的 `TimeoutRedisCacheManager` 做了一个很实用的扩展:
1. 如果缓存名是普通格式,例如 `user`
2. 则按全局默认 TTL 工作
3. 如果缓存名写成 `user#10m`
4. 则这个缓存自动按 10 分钟过期
这相当于把“单个缓存项的过期时间”从配置文件和自定义代码里,收口到了缓存名约定里。
## 5. 给上层能力模块提供 Redis 和 Redisson 基座
这个模块本身不直接实现分布式锁、限流、幂等,但它提供了这些上层能力所依赖的基础能力:
1. `StringRedisTemplate`
2. `RedisTemplate<String, Object>`
3. `RedissonClient`
4. 基于 Redis 的 Spring Cache
因此像 `rdms-spring-boot-starter-protection``rdms-spring-boot-starter-mq` 这样的模块,都可以把 Redis 能力建立在这个 starter 之上。
## 自动装配链路
## 1. Spring Boot 自动配置入口
`META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports`
1. `com.njcn.rdms.framework.redis.config.RdmsRedisAutoConfiguration`
2. `com.njcn.rdms.framework.redis.config.RdmsCacheAutoConfiguration`
## 2. 各配置类负责的事情
| 配置类 | 作用 |
| --- | --- |
| `RdmsRedisAutoConfiguration` | 注册统一序列化规则的 `RedisTemplate<String, Object>` |
| `RdmsCacheAutoConfiguration` | 开启 Spring Cache注册 `RedisCacheConfiguration` 和自定义 `RedisCacheManager` |
## 核心组件
| 组件 | 作用 |
| --- | --- |
| `RdmsRedisAutoConfiguration` | 提供统一 JSON 序列化的 RedisTemplate并处理 `LocalDateTime` 序列化 |
| `RdmsCacheAutoConfiguration` | 统一 Redis Cache 前缀、TTL、空值缓存策略和 CacheManager |
| `RdmsCacheProperties` | 提供 `rdms.cache.*` 配置项,目前包含 `redis-scan-batch-size` |
| `TimeoutRedisCacheManager` | 支持通过 `cacheName#ttl` 的形式为单个缓存声明过期时间 |
## 功能说明
## 1. 统一 RedisTemplate 序列化
核心类:
1. `RdmsRedisAutoConfiguration`
实现方式:
1. `RedisTemplate<String, Object>` 统一由框架注册
2. Key/Hash Key 使用字符串序列化
3. Value/Hash Value 使用 JSON 序列化
4. 通过 `JavaTimeModule` 兼容 `LocalDateTime`
适合场景:
1. 业务模块直接注入 `RedisTemplate<String, Object>` 使用
2. 需要缓存对象、列表、Map 等结构
3. 希望 Redis 中的数据具有一定可读性
示例:
```java
@Resource
private RedisTemplate<String, Object> redisTemplate;
public void saveUser(Long userId, UserRespVO user) {
redisTemplate.opsForValue().set("user:" + userId, user);
}
```
## 2. 统一 Spring Cache 基于 Redis 的实现
核心类:
1. `RdmsCacheAutoConfiguration`
2. `RdmsCacheProperties`
实现方式:
1. 开启 `@EnableCaching`
2. 创建统一的 `RedisCacheConfiguration`
3.`spring.cache.redis.*` 读取默认 TTL、前缀、空值缓存等配置
4. 使用 JSON 序列化缓存值
5. 使用自定义 `RedisCacheManager`
这个模块还额外做了一个约定:
1. Cache Key 前缀使用单个 `:`,而不是默认的 `::`
2. 这样 Redis 中的 Key 更紧凑,也更方便在可视化工具中查看
Spring Cache 在这个模块里的作用,可以理解为:
1. 让“方法查询结果缓存”变成注解式能力,而不是每次都手写 `RedisTemplate`
2. 让缓存的写入、读取、失效围绕业务方法本身声明
3. 让“查库 -> 放缓存 -> 下次命中缓存”这类标准流程交给框架处理
它更适合下面这类场景:
1. 根据主键查询角色、菜单、模板、配置
2. 根据某个唯一业务字段查询对象,例如 `clientId``code`
3. 根据某个参数查询相对稳定的数据,例如部门子节点列表、权限对应菜单列表
这类场景通常有几个共同点:
1. 输入参数比较明确
2. 返回结果可以直接缓存
3. 数据更新时可以找到明确的缓存失效点
典型流程是:
1. 在查询方法上加 `@Cacheable`
2. 第一次执行时,方法正常查数据库或远程接口
3. 返回结果自动写入 Redis
4. 后续相同参数调用时,直接命中缓存,不再进入方法体
5. 在更新、删除方法上通过 `@CacheEvict` 清理对应缓存
和直接使用 `RedisTemplate` 相比Spring Cache 的优势主要是:
1. 不需要手写 `get -> 判空 -> set -> expire` 这类模板代码
2. 缓存逻辑和业务方法绑定更紧,读代码时更容易看出哪里有缓存
3. 适合“查询结果缓存”这种标准模式
当前项目里,权限、角色、菜单、模板这类数据就大量采用这种方式;而像 Token、验证码这类更像“直接把业务对象存到 Redis”的场景则更适合单独写 RedisDAO。
示例:
```java
@Cacheable(cacheNames = "user", key = "#id")
public UserRespVO getUser(Long id) {
return userApi.getUser(id);
}
```
如果某个写操作会影响缓存,需要配套清理缓存:
```java
@CacheEvict(cacheNames = "user", key = "#id")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
```
在当前项目语境下,更贴近实际的使用案例包括:
1. 根据角色 ID 查询角色信息,并在角色更新或删除时清理缓存
2. 根据权限标识查询菜单 ID 列表,并在菜单新增、修改、删除时清理缓存
3. 根据模板编码查询通知模板、邮件模板,并在模板变更时清理缓存
## 3. 支持按缓存名自定义过期时间
核心类:
1. `TimeoutRedisCacheManager`
实现方式:
1. 解析 `@Cacheable(cacheNames = "...")` 中的缓存名
2. 如果命中 `cacheName#ttl` 格式,则为该缓存单独设置 TTL
3. 支持的单位有:
- `d`
- `h` 小时
- `m` 分钟
- `s`
4. 如果不带单位,默认按秒处理
示例:
```java
@Cacheable(cacheNames = "user#10m", key = "#id")
public UserRespVO getUser(Long id) {
return userApi.getUser(id);
}
```
说明:
1. `#10m` 只用于声明这个缓存的过期时间是 10 分钟
2. 它不会作为最终 Redis key 的一部分长期保留
3. 如果 `id = 1`
4.`spring.cache.redis.key-prefix = rdms`
5. 那么实际 Redis key 一般形如 `rdms:user:1`
6. 其中 `user` 是缓存名,`1``key = "#id"` 计算出的缓存 Key
```java
@Cacheable(cacheNames = "config#30s", key = "#key")
public ConfigRespVO getConfig(String key) {
return configService.getConfig(key);
}
```
说明:
1. 如果 `key = "sms"`
2.`spring.cache.redis.key-prefix = rdms`
3. 那么实际 Redis key 一般形如 `rdms:config:sms`
4. `#30s` 同样只影响 TTL不直接体现在最终 key 名上
这种方式适合“绝大多数缓存共用默认 TTL少数缓存按场景单独缩短或延长过期时间”的场景。
补充说明:
1. `cacheNames = "user#10m"` 中的 `user` 是缓存名,`10m` 是 TTL 描述
2. `key = "#id"``key = "#key"` 才决定具体缓存项的业务 Key
3. 如果配置了 `spring.cache.redis.key-prefix = rdms`,最终 Redis key 通常是 `rdms:user:1``rdms:config:sms` 这种形式
4. `#10m``#30s` 这类 TTL 后缀用于创建缓存时解析过期时间,不应理解成最终 key 名的一部分
## 4. 提供 Redisson 基础能力
从依赖上看,模块直接引入了 `redisson-spring-boot-starter`,因此除了 Spring Data Redis 以外,也会把 `RedissonClient` 这类能力带入项目。
这部分的直接收益是:
1. 分布式锁能力可被上层模块直接复用
2. 限流等基于 Redisson 的能力可直接建立在此模块之上
3. 项目不需要在多个模块里重复引入 Redisson
## 如何使用
## 1. 引入依赖
业务模块通常直接依赖:
```xml
<dependency>
<groupId>com.njcn</groupId>
<artifactId>rdms-spring-boot-starter-redis</artifactId>
</dependency>
```
## 2. 基础配置
最常见的配置重点包括:
1. `spring.data.redis.*`
2. `spring.cache.redis.*`
3. `rdms.cache.redis-scan-batch-size`
示例:
```yaml
spring:
data:
redis:
host: 127.0.0.1 # Redis 地址
port: 6379 # Redis 端口
database: 0 # 使用的 Redis 库编号
cache:
type: REDIS # Spring Cache 底层使用 Redis
redis:
time-to-live: 1h # 全局默认缓存过期时间
cache-null-values: false # 是否缓存空值,通常建议关闭
use-key-prefix: true # 是否启用缓存 Key 前缀
key-prefix: rdms # 缓存 Key 的统一前缀
rdms:
cache:
redis-scan-batch-size: 100 # Redis CacheWriter 使用 SCAN 批量处理时的单次扫描数量
```
说明:
1. `spring.cache.redis.time-to-live` 是全局默认缓存过期时间
2. `cacheName#ttl` 可以覆盖单个缓存的 TTL
3. `rdms.cache.redis-scan-batch-size` 用于控制 Redis CacheWriter 使用 `SCAN` 批量处理时的单次返回数量
## 3. 直接使用 RedisTemplate
如果场景不适合 Spring Cache例如
1. 需要操作复杂数据结构
2. 需要精细控制过期时间
3. 需要手动删除、递增、集合操作
可以直接注入 `RedisTemplate<String, Object>``StringRedisTemplate` 使用。
## 4. 优先使用 Spring Cache 的场景
如果场景是:
1. 查询结果缓存
2. 参数到结果的简单映射
3. 希望通过注解快速声明缓存
则优先使用 `@Cacheable``@CacheEvict``@CachePut`,并利用统一的 Redis Cache 约定。
这三个注解的典型使用场景可以这样区分:
1. `@Cacheable`
用于“先查缓存,缓存没有再执行方法,并把结果写入缓存”。
适合读操作,例如“根据 ID 查询角色”“根据编码查询模板”。
```java
@Cacheable(cacheNames = "role", key = "#id", unless = "#result == null")
public RoleDO getRoleFromCache(Long id) {
return roleMapper.selectById(id);
}
```
2. `@CacheEvict`
用于“执行方法后删除缓存”,避免更新或删除数据后缓存还是旧值。
适合写操作,例如“更新角色后清理角色缓存”“删除模板后清理模板缓存”。
```java
@CacheEvict(cacheNames = "role", key = "#updateReqVO.id")
public void updateRole(RoleSaveReqVO updateReqVO) {
roleMapper.updateById(BeanUtils.toBean(updateReqVO, RoleDO.class));
}
```
3. `@CachePut`
用于“执行方法后,把方法返回值直接写回缓存”。
适合更新后希望立即刷新缓存,而不是简单删除缓存的场景。
```java
@CachePut(cacheNames = "role", key = "#result.id")
public RoleDO updateRoleAndReturn(RoleSaveReqVO updateReqVO) {
RoleDO role = BeanUtils.toBean(updateReqVO, RoleDO.class);
roleMapper.updateById(role);
return role;
}
```
可以把它们简单记成:
1. `@Cacheable`:查的时候用,没有缓存才执行方法
2. `@CacheEvict`:改或删的时候用,执行后清缓存
3. `@CachePut`:改的时候用,执行后直接刷新缓存
## 注意事项
1. `RedisTemplate<String, Object>` 使用 JSON 序列化,适合对象缓存,但跨版本变更字段时仍应关注兼容性。
2. `LocalDateTime` 已做序列化兼容处理,但复杂对象结构仍应关注 Jackson 序列化结果。
3. `cacheName#ttl` 是这个模块扩展出来的约定,不是 Spring Cache 默认语法。
4. 通过 `cacheName#ttl` 设置过期时间时TTL 是针对这个缓存名生效的,不是针对单条 Key 动态计算。
5. `RdmsCacheAutoConfiguration` 将 Cache Key 前缀改成了单冒号 `:` 风格,如果已有依赖默认 `::` 的脚本或习惯,需要注意。
6. 这个模块本身主要解决“接入和约定”问题,不提供大量 Redis 业务工具方法;复杂 Redis 场景通常还是由业务模块或上层 starter 自己封装。
## 总结
这个模块的核心价值,不是简单引入 Redis 依赖,而是统一项目里的 Redis 使用方式:
1. 统一 `RedisTemplate` 的序列化规则
2. 统一 Spring Cache 的 Redis 落地方式
3. 提供按缓存名声明 TTL 的扩展能力
4. 为 Redisson 相关的上层能力提供基础环境
如果把它理解成一个“Redis 接入基座 + Cache 约定基座”的 starter这个模块的设计会比较清晰。