缓存层详解
模块路径:
wemirr-platform-framework/redis-plus-spring-boot-starter包路径:com.wemirr.framework.redis.plus
概述
redis-plus-spring-boot-starter 是基于 Redisson 的 Redis 增强模块,提供:
- 统一 RedisTemplate 配置(JSON 序列化)
- 方法级缓存(Spring Cache 集成)
- 分布式锁(可重入/公平/读写锁)
- 分布式限流(令牌桶算法)
- 分布式序列号生成
目录结构
redis-plus/
├── RedisPlusAutoConfiguration.java # 主自动配置
├── RedisPlusProperties.java # 配置属性
├── cache/ # 缓存
│ ├── RedisCacheAutoConfiguration.java # 缓存自动配置
│ └── RedisCacheRepository.java # 缓存仓库实现
├── lock/ # 分布式锁
│ ├── DistributedLock.java # 流式锁API
│ ├── RedisLockHelper.java # 锁助手
│ ├── LockInfo.java # 锁信息
│ └── LockResult.java # 锁结果
├── limit/ # 限流
│ ├── DistributedRateLimiter.java # 流式限流API
│ └── RateLimitInfo.java # 限流信息
├── sequence/ # 序列号
│ ├── RedisSequenceHelper.java # 序列号助手
│ └── Sequence.java # 序列号接口
├── anontation/ # 注解
│ ├── RedisLock.java # 分布式锁注解
│ └── RedisLimit.java # 限流注解
└── interceptor/ # 拦截器
├── RedisLockInterceptor.java # 锁拦截器
└── RedisLimitInterceptor.java # 限流拦截器核心组件详解
1. RedisTemplate 配置
文件: RedisPlusAutoConfiguration.java
序列化策略:
| 组件 | 序列化器 | 说明 |
|---|---|---|
| Key | StringRedisSerializer | 字符串 |
| HashKey | StringRedisSerializer | 字符串 |
| Value | GenericJackson2JsonRedisSerializer | JSON(带类型) |
特性:
- 支持泛型和多态类型
- 兼容 Kotlin Final 类
- 启用事务支持
2. 方法级缓存
配置项 (extend.redis.cache):
| 属性 | 默认值 | 说明 |
|---|---|---|
enabled | false | 是否启用缓存 |
prefix | redis_plus_cache_ | 缓存Key前缀 |
timeout | 86400 | 全局过期时间(秒) |
items | - | 缓存项配置列表 |
缓存项配置:
yaml
extend:
redis:
cache:
enabled: true
prefix: "app_cache_"
timeout: 3600 # 1小时
items:
- name: userCache
timeout: 1800 # 30分钟
enabled: true
- name: dictCache
timeout: 7200 # 2小时
enabled: true使用示例:
java
// 使用Spring Cache注解
@Cacheable(value = "userCache", key = "#id")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
@CacheEvict(value = "userCache", key = "#user.id")
public void updateUser(User user) {
userMapper.updateById(user);
}
@CachePut(value = "userCache", key = "#result.id")
public User saveUser(User user) {
userMapper.insert(user);
return user;
}Key生成策略:
java
// 默认Key生成器(MD5)
// 包含:类名 + 方法名 + 所有参数
key = MD5(target + method + params)
// 格式: {prefix}_fn_{cacheName}_{md5}
// 示例: redis_plus_cache__fn_userCache_a1b2c3d4...3. 分布式锁
3.1 注解方式 @RedisLock
注解参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
prefix | "" | 锁Key前缀 |
expire | -1 | 过期时间(-1启用看门狗) |
waitTime | 3L | 等待时间 |
timeUnit | SECONDS | 时间单位 |
delimiter | ":" | 分隔符 |
message | "请求过快,稍后再试" | 失败提示 |
unlock | true | 是否自动释放 |
lockType | REENTRANT_LOCK | 锁类型 |
锁类型:
java
public enum LockType {
REENTRANT_LOCK, // 可重入锁(默认)
FAIR_LOCK, // 公平锁
READ_LOCK, // 读锁
WRITE_LOCK, // 写锁
MULTI_LOCK // 联锁
}使用示例:
java
// 简单使用
@RedisLock(prefix = "order:update", expire = 10, waitTime = 3)
public void updateOrder(Long orderId) {
// 自动加锁/解锁
}
// 参数化锁Key
@RedisLock(prefix = "user:update", useArgs = true)
public void updateUser(Long userId, User user) {
// Key格式: user:update:1001
}3.2 流式 API DistributedLock
基础用法:
java
// 1. 简单锁
String result = DistributedLock.key("order:123")
.execute(() -> processOrder());
// 2. 自定义配置
DistributedLock.key("user:456")
.waitTime(5, TimeUnit.SECONDS)
.leaseTime(30, TimeUnit.SECONDS)
.lockType(LockInfo.LockType.FAIR)
.execute(() -> updateUser());
// 3. 看门狗模式(自动续期)
DistributedLock.key("task:long")
.leaseTime(-1) // 启用看门狗
.execute(() -> longRunningTask());
// 4. 无返回值
DistributedLock.key("task:001")
.run(() -> doTask());
// 5. 尝试执行(不抛异常)
LockResult<Order> result = DistributedLock.key("order:789")
.tryExecute(() -> createOrder());
result.onSuccess(order -> log.info("Created: {}", order))
.onFailure(e -> log.error("Failed", e));
// 6. 快捷方式
DistributedLock.key("resource").fair().execute(...);
DistributedLock.key("resource").readLock().execute(...);
DistributedLock.key("resource").writeLock().execute(...);3.3 RedisLockHelper
java
@Autowired
private RedisLockHelper lockHelper;
// 函数式回调
Order order = lockHelper.execute("order:create", 3, TimeUnit.SECONDS, () -> {
return orderService.create(order);
});
// 自定义租约时间
lockHelper.execute("key", 3, 30, TimeUnit.SECONDS, () -> {
// 等待3秒,持有30秒
});
// 批量锁
List<RLock> locks = lockHelper.batchLock(keys, 3);
try {
// 业务逻辑
} finally {
lockHelper.unlock(locks);
}4. 分布式限流
4.1 注解方式 @RedisLimit
注解参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
prefix | "" | 限流Key前缀 |
limit | 50 | 放行数量 |
timeout | 1 | 时间窗口大小 |
retryTime | 0 | 重试次数 |
unit | SECONDS | 时间单位 |
type | OVERALL | 限流类型 |
useArgs | false | 是否包含参数 |
message | "请求过于频繁" | 失败提示 |
限流类型:
OVERALL: 全局限流(所有节点共享)PER_CLIENT: 单机限流(每个客户端独立)
使用示例:
java
// 登录接口:每分钟最多5次
@RedisLimit(prefix = "api:user:login", limit = 5, timeout = 60, unit = TimeUnit.MINUTES)
public Result login(LoginRequest request) {
// ...
}
// 参数化限流:每个用户独立限流
@RedisLimit(prefix = "api:user:info", limit = 10, timeout = 60, useArgs = true)
public User getUserInfo(Long userId) {
// 每个用户每分钟最多10次
}4.2 流式 API DistributedRateLimiter
基础用法:
java
// 1. 简单限流(默认每秒50个请求)
String result = DistributedRateLimiter.key("api:user:list")
.execute(() -> userService.list());
// 2. 自定义QPS(每秒100个请求)
DistributedRateLimiter.key("api:order:create")
.permits(100)
.execute(() -> orderService.create(order));
// 3. 每分钟限流
DistributedRateLimiter.key("api:sms:send")
.permits(10)
.perMinute()
.execute(() -> smsService.send(phone));
// 4. 每天限流
DistributedRateLimiter.key("api:export")
.permits(100)
.perDay()
.execute(() -> exportService.export());
// 5. 单机限流
DistributedRateLimiter.key("api:upload")
.permits(5)
.perClient()
.execute(() -> fileService.upload(file));
// 6. 尝试执行(不抛异常)
boolean success = DistributedRateLimiter.key("api:login")
.tryAcquire();
// 7. 带默认值
User user = DistributedRateLimiter.key("api:user:get")
.tryExecuteOrDefault(() -> userService.get(id), null);
// 8. 查看可用令牌数
long available = DistributedRateLimiter.key("api:test")
.availablePermits();限流算法: 令牌桶算法(Redisson RRateLimiter)
5. 序列号生成
接口: Sequence.java
格式: 前缀 + 日期时间 + 分隔符 + 自增序号
使用示例:
java
// 定义序列规则
public enum OrderSequence implements Sequence {
ORDER_NO("order:seq", "ORD", "", 6);
private final String key;
private final String prefix;
private final String delimiter;
private final int size;
// 构造函数、getter...
}
// 生成序列号
@Autowired
private RedisSequenceHelper sequenceHelper;
// 格式: ORD20260409000001
String orderNo = sequenceHelper.generate(OrderSequence.ORDER_NO);
// 带租户隔离
// 格式: ORD20260409_1001_000001
String orderNo = sequenceHelper.generate(OrderSequence.ORDER_NO, tenantId);配置说明
application.yml 完整配置
yaml
extend:
redis:
# 全局开关
enabled: true
# 分布式锁配置
lock:
enabled: true
prefix: "redis_lock_"
interceptor: true # 是否启用@RedisLock拦截器
# 限流配置
limit:
enabled: true
prefix: "redis_limit_"
interceptor: true # 是否启用@RedisLimit拦截器
# 缓存配置
cache:
enabled: true
prefix: "redis_cache_"
timeout: 86400 # 24小时
items:
- name: userCache
timeout: 1800
enabled: true
- name: dictCache
timeout: 7200
enabled: true常见问题 (Q&A)
Q1: 看门狗(Watchdog)是什么?
A: 当设置 expire = -1 或 leaseTime = -1 时,Redisson 会自动启用看门狗机制。
工作原理:
- 看门狗默认每 10 秒续期一次
- 将锁的过期时间重置为 30 秒
- 只要持有锁的线程还存活,就会持续续期
- 线程结束后自动释放锁
java
// 启用看门狗
@RedisLock(prefix = "long:task", expire = -1)
public void longRunningTask() {
// 任务执行期间锁会自动续期
}Q2: 可重入锁 vs 公平锁?
A:
| 类型 | 特点 | 适用场景 |
|---|---|---|
| 可重入锁 | 同一线程可多次获取,效率高 | 大多数业务场景 |
| 公平锁 | 按请求顺序获取锁,避免饥饿 | 需要严格顺序的场景 |
java
// 可重入锁(默认)
@RedisLock(prefix = "task", lockType = LockType.REENTRANT_LOCK)
public void method1() {
method2(); // 同一线程可以再次获取锁
}
@RedisLock(prefix = "task")
public void method2() {
// ...
}
// 公平锁
@RedisLock(prefix = "task", lockType = LockType.FAIR_LOCK)
public void fairTask() {
// 按请求顺序执行
}Q3: 读写锁的使用场景?
A: 读写锁适用于读多写少的场景。
- 读锁: 多个线程可以同时持有
- 写锁: 独占,阻塞所有读锁和写锁
java
// 读操作(可以并发)
@RedisLock(prefix = "config", lockType = LockType.READ_LOCK)
public Config getConfig() {
return configRepository.find();
}
// 写操作(独占)
@RedisLock(prefix = "config", lockType = LockType.WRITE_LOCK)
public void updateConfig(Config config) {
configRepository.update(config);
}Q4: 限流的时间窗口是如何计算的?
A: 使用令牌桶算法。
- 以固定速率向桶中放入令牌
- 请求到来时从桶中获取令牌
- 桶中有令牌则通过,否则拒绝
java
// 每秒 100 个令牌
@RedisLimit(limit = 100, timeout = 1, unit = TimeUnit.SECONDS)
public Result api() {
// 令牌以 100/秒 的速度补充
}
// 每分钟 10 个令牌
@RedisLimit(limit = 10, timeout = 1, unit = TimeUnit.MINUTES)
public Result api() {
// 令牌以 10/分钟 的速度补充
}Q5: 如何选择限流类型?
A:
| 类型 | 说明 | 适用场景 |
|---|---|---|
OVERALL | 所有节点共享限流配额 | 保护后端服务/数据库 |
PER_CLIENT | 每个客户端独立配额 | 防止单个用户滥用 |
java
// 全局限流:保护数据库
@RedisLimit(limit = 1000, type = RateType.OVERALL)
public List<Order> listOrders() {
// 所有节点加起来每秒最多1000次请求
}
// 单机限流:防止单个用户滥用
@RedisLimit(limit = 10, type = RateType.PER_CLIENT, useArgs = true)
public void sendSms(@RedisParam String phone) {
// 每个用户每秒最多10次请求
}Q6: 缓存的 Key 是如何生成的?
A: 默认使用 MD5 生成,包含以下信息:
java
// Key 组成部分
{
"target": "com.example.service.UserService", // 类名
"method": "getUserById", // 方法名
"params-0": 1001 // 参数
}
// 最终格式
redis_plus_cache__fn_userCache_a1b2c3d4e5f6...
// ↑ ↑ ↑
// 前缀 缓存名 MD5值可以自定义 Key 生成器:
java
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
return "custom:" + method.getName() + ":" + params[0];
};
}Q7: 如何清空特定缓存?
A:
java
// 清空单个Key
@CacheEvict(value = "userCache", key = "#id")
public void deleteUser(Long id) {
// ...
}
// 清空整个缓存
@CacheEvict(value = "userCache", allEntries = true)
public void clearAllUserCache() {
// ...
}
// 编程式清空
@Autowired
private RedisCacheRepository userCache;
public void clearUserCache(Object key) {
userCache.evict(key);
}
public void clearAllUserCache() {
userCache.clear();
}Q8: 序列号生成的规则是什么?
A:
java
// 格式: 前缀 + 日期 + 分隔符 + 自增序号
String orderNo = sequenceHelper.generate(sequence);
// 示例 1: ORD20260409000001
// ↑↑↑ ↑↑↑↑↑↑↑↑ ↑↑↑↑↑↑
// 前缀 日期 序号(6位)
// 示例 2: ORD20260409_1001_000001(带租户隔离)
// ↑↑↑↑ ↑↑↑↑↑↑
// 租户ID 序号学习建议
掌握核心概念:
- Redis 序列化策略
- Spring Cache 工作原理
- 分布式锁的实现方式
实践:
- 使用
@Cacheable优化查询接口 - 使用
@RedisLock保护临界资源 - 使用
@RedisLimit防止接口滥用
- 使用
进阶:
- 理解看门狗机制
- 选择合适的锁类型
- 配置合理的限流阈值
下一步学习
消息队列详解- 异步消息处理定时任务详解- 分布式任务调度