1. 背景与设计初衷
在许多项目中,Redis 常被以原生方式使用,导致以下问题:
- 各模块 Redis 操作零散重复,开发与维护成本高;
- 缺乏统一的序列化与命名规范;
- 分布式锁、缓存策略等功能实现不一致;
- 复制相同的代码到不同项目中;
cache-service 的设计初衷是: 对原生 Redis 进行统一封装,提供简洁、易用、可扩展的接口,使业务开发者专注核心业务逻辑,无需关注底层 Redis 细节。同时,为未来功能扩展与客户端升级提供便利。 整体设计难度一般,结合函数式编程思想,实现开发简化,同时提供方法封装和注解支持。
项目地址:点此进入
2. 设计理念与核心特性
cache-service 提供“开箱即用”的 Redis 集成方式:
- 如果项目已有自定义 Redis Bean,则优先使用;
- 若无,则由组件自动创建 Redis Bean;
- 组件依赖的版本号优先使用项目环境提供版本,实现无缝接入;
✅ 核心特性
| 功能点 | 说明 | | ———————- | ——————————————— | | 缓存击穿保护 | 默认关闭,可按需启用 (仅对String类型有效) | | 注解式缓存隔离 | 通过命名空间区分不同业务模块 | | Lambda 回源支持 | 支持 function 函数执行后回填缓存 | | 复杂对象序列化 | 支持集合、对象类型序列化与反序列化 | | 分布式锁与幂等保护 | 提供注解式声明使用 |
💡 注意:缓存穿透保护并非所有业务都必须开启。对于数据稳定或访问量较小的系统,缓存空值可能造成内存浪费或逻辑混乱,因此默认关闭。 过期时间策略说明
- 单key的API 默认过期时间:30 分钟(Redis 仅用于缓存,不作为持久化数据库使用)
- 批量操作不设置默认过期时间,因为批量操作本身旨在减少 RPC 调用,如果循环调用设置过期时间会失去批量操作的意义;
集合类缓存为何不启用击穿保护
- 集合类型通常包含多条数据,只要存在任意一条即可防止击穿;
- 与 String 类型不同,String 缓存击穿风险更高,因此默认只对 String 开启保护;
- 代码中已预留相关字段,便于未来按需扩展;
3. 使用示例
1️⃣ 基本缓存使用
// 声明 user 模块的缓存key前缀
@RedisCache("user")
private MyRedisCache redisCache;
// 获取缓存,若不存在则回源数据库并自动回填
TestUser result = redisCache.get(key, TestUser.class, () -> {
log.info("Cache miss, loading from DB...");
return userMapper.getUserById("id");
}, 30, TimeUnit.MINUTES);
// order 模块开启缓存击穿保护
@RedisCache(value = "order", preventCachePenetration = true)
private MyRedisCache orderCache;
// 若回源数据为空,则缓存空值 1 分钟
TestUser result2 = orderCache.get(key, TestUser.class, () -> {
log.info("Cache miss, loading from DB...");
return userMapper.getUserById("id");
}, 30, TimeUnit.MINUTES);
2️⃣ Redis 集合操作
// 声明模块的缓存key前缀
@RedisCache("test")
private MyRedisCache redisCache;
private RedisRawListCache<TestUser> listCache;
@PostConstruct
public void init() {
listCache = redisCache.listCache(TestUser.class);
}
TestUser user1 = new TestUser(11111L, "bob", Instant.now());
Long result1 = listCache.leftPushIfPresent(key, user1);
3️⃣ 分布式锁使用
- key:字符串或对象字段组合
- lockExpiredTime:锁过期时间(ms),默认使用 WatchDog
- tryLockTime:尝试获取锁时间,默认 2 秒 ```java // 字符串参数作为 key @RedisLock(key = “‘user:’ + #userId”) public void testRedisLock(String userId) throws InterruptedException { Thread.sleep(50000L); log.info(“testRedisLock userId: {}”, userId); }
// 对象字段组合为 key @RedisLock(key = “‘order:’ + #order.id + ‘:user:’ + #user.id”) public void createOrder(OrderDTO order, UserDTO user) { … }
⚠️ 注意:
+ 若对象字段为 null,生成的 key 会使用 "null" 字符串;
+ 若对象本身为 null,会直接报错;
+ 多注解代理对象时,注意执行顺序;
4️⃣ 幂等注解使用
• 用法与分布式锁类似
• expiredTime:幂等过期时间(ms),默认一天
```java
@RedisIdempotent(key = "'order:' + #order.id", expiredTime = 5000)
public void submitOrder(OrderDTO order) { ... }
4. 后续规划(ToDo)
- ✅ 支持本地二级缓存
- ✅ 缓存命中率与性能指标统计
- ✅ 支持缓存数据压缩
- ✅ 扩展多实例 Redis 支持