Skip to content

缓存层详解

模块路径: 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

序列化策略:

组件序列化器说明
KeyStringRedisSerializer字符串
HashKeyStringRedisSerializer字符串
ValueGenericJackson2JsonRedisSerializerJSON(带类型)

特性:

  • 支持泛型和多态类型
  • 兼容 Kotlin Final 类
  • 启用事务支持

2. 方法级缓存

配置项 (extend.redis.cache):

属性默认值说明
enabledfalse是否启用缓存
prefixredis_plus_cache_缓存Key前缀
timeout86400全局过期时间(秒)
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启用看门狗)
waitTime3L等待时间
timeUnitSECONDS时间单位
delimiter":"分隔符
message"请求过快,稍后再试"失败提示
unlocktrue是否自动释放
lockTypeREENTRANT_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前缀
limit50放行数量
timeout1时间窗口大小
retryTime0重试次数
unitSECONDS时间单位
typeOVERALL限流类型
useArgsfalse是否包含参数
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 = -1leaseTime = -1 时,Redisson 会自动启用看门狗机制。

工作原理:

  1. 看门狗默认每 10 秒续期一次
  2. 将锁的过期时间重置为 30 秒
  3. 只要持有锁的线程还存活,就会持续续期
  4. 线程结束后自动释放锁
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: 使用令牌桶算法

  1. 以固定速率向桶中放入令牌
  2. 请求到来时从桶中获取令牌
  3. 桶中有令牌则通过,否则拒绝
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  序号

学习建议

  1. 掌握核心概念:

    • Redis 序列化策略
    • Spring Cache 工作原理
    • 分布式锁的实现方式
  2. 实践:

    • 使用 @Cacheable 优化查询接口
    • 使用 @RedisLock 保护临界资源
    • 使用 @RedisLimit 防止接口滥用
  3. 进阶:

    • 理解看门狗机制
    • 选择合适的锁类型
    • 配置合理的限流阈值

下一步学习

  • 消息队列详解 - 异步消息处理
  • 定时任务详解 - 分布式任务调度