EBEasyBuild Docs
文档/后端/Redis 缓存

Cache-Redis 分布式缓存组件

Spring Cache + Redis 注解驱动与编程式双模式阅读时间 ~20 min

一、组件概述

cache-redis 组件是 EasyFK 框架的分布式 Redis 缓存方案,提供两种使用模式:

  1. 注解驱动模式:通过 @Cacheable@CachePut@CacheEvict 等 Spring Cache 标准注解声明式管理缓存
  2. 编程式模式:通过 ICacheService 接口直接进行缓存的增删查改操作

核心特性:

  • 自动配置 RedisCacheManager,引入即用
  • 支持为不同缓存空间配置独立的 TTL(过期时间)
  • 基于 EasyFK db-redis 组件的多数据源连接管理,支持指定缓存使用的 Redis 数据源
  • Key 使用 StringRedisSerializer 序列化,Value 序列化方式跟随 db-redis 全局配置
  • 默认禁止缓存 null 值,避免缓存穿透

依赖关系

plaintext
cache-redis
├── easyfk-cache(缓存接口层,定义 ICacheService)
└── db-redis(Redis 连接管理,提供 RedisConnectionManager、RedisOptManager 等)

二、快速开始

2.1 引入依赖

Gradle 方式

groovy
dependencies {
    implementation("com.mcst:cache-redis")
}

Maven 方式

xml
<dependency>
    <groupId>com.mcst</groupId>
    <artifactId>cache-redis</artifactId>
</dependency>
TIP
版本由 EasyFK BOM 统一管理,无需手动指定版本号。

2.2 基础配置

默认情况下,使用 Spring Boot 标准的 Redis 配置即可:

yaml
# Spring Redis 标准配置
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password: your-password
      database: 0

# cache-redis 缓存配置
easyfk:
  config:
    cache:
      redis:
        time-to-live: 30m                  # 全局默认 TTL
TIP
如需多数据源支持,可通过 db-redis 组件的自定义配置指定缓存使用的数据源,详见多数据源场景。

2.3 使用示例

注解方式

java
@Service
public class UserService {

    @Cacheable(value = "users", key = "#userId")
    public UserDTO getUserById(Long userId) {
        return userMapper.selectById(userId);
    }
}

编程方式

java
@Service
public class UserService {

    @Resource
    private ICacheService cacheService;

    public UserDTO getUserById(Long userId) {
        String key = "user:" + userId;

        // 查询缓存
        UserDTO user = cacheService.queryByKey(key, "user-ns");
        if (user != null) {
            return user;
        }

        // 缓存未命中,查询数据库
        user = userMapper.selectById(userId);
        if (user != null) {
            cacheService.cacheObject(key, user, Duration.ofMinutes(30), "user-ns");
        }
        return user;
    }
}

三、配置详解

3.1 全局配置

配置前缀:easyfk.config.cache.redis

对应属性类:RedisCacheProperties

配置项类型默认值说明
datasourceStringdefaultRedis 数据源名称(对应 db-redis 中配置的数据源)
database-nameStringdefaultRedis 数据库名称(对应数据源下的库配置)
time-to-liveDuration0全局默认缓存过期时间(0 表示永不过期)
cachesList(空)自定义缓存空间配置列表

Duration 格式示例

  • 30m — 30 分钟
  • 2h — 2 小时
  • 1d — 1 天
  • PT30S — 30 秒(ISO-8601 格式)
  • 0 — 永不过期

3.2 多缓存空间独立 TTL

不同的业务数据通常需要不同的过期时间。通过 caches 配置为每个缓存空间(cacheName)设置独立的 TTL:

yaml
easyfk:
  config:
    cache:
      redis:
        time-to-live: 30m
        caches:
          - cache-name: users
            time-to-live: 2h
          - cache-name: verify-code
            time-to-live: 5m
          - cache-name: dict
            time-to-live: 1d

字段说明

字段类型必填默认值说明
cache-nameString缓存空间名称,对应 @Cacheable(value = "xxx")
time-to-liveDuration30m该缓存空间的过期时间

3.3 caches 配置与未配置的行为差异

注意
配置了 caches 列表时RedisCacheManager 仅对列表中声明的缓存空间应用自定义 TTL 和序列化配置。未在列表中声明的缓存空间将使用 Spring Cache 的默认行为(无 TTL,允许缓存 null 值)
TIP
未配置 caches:全局配置(包含 time-to-live、序列化器、禁止缓存 null 值)将通过 cacheDefaults 应用到所有缓存空间,并启用事务感知(transactionAware)。

如果需要让所有缓存空间都使用全局配置,不要配置 caches 列表,仅设置全局 time-to-live 即可。

3.4 多数据源场景(进阶)

默认情况下,使用 Spring Boot 标准 Redis 配置(spring.data.redis.*)即可,无需额外配置数据源。当需要多 Redis 数据源时,可通过 db-redis 组件管理多个数据源,并在 cache-redis 中通过 datasource + database-name 指定缓存使用哪个数据源和数据库:

yaml
easyfk:
  config:
    db:
      redis:
        redis-serializer: FastJSON           # 序列化器,可选:DEFAULT / FastJSON / KRYO
        default-data-source: default
        datasource:
          default:
            host: 127.0.0.1
            port: 6379
            password: your-password
            database: 0
            databases:
              default: 0
              cache-db: 1                    # 数据库别名 cache-db → 索引 1

    cache:
      redis:
        datasource: default                  # 指定使用 db-redis 中的数据源名称
        database-name: cache-db              # 指定使用数据源中别名为 cache-db 的库(索引 1)
        time-to-live: 30m
        caches:
          - cache-name: users
            time-to-live: 2h
          - cache-name: verify-code
            time-to-live: 5m
  • datasourcedatabase-name 的默认值均为 default,单数据源场景下无需配置
  • database-name 对应数据源配置中 databases Map 的 key(别名),而不是 Redis 数据库索引数字
TIP
建议:在生产环境中,如果缓存数据量大,建议使用独立的 Redis 实例,避免与业务数据争抢资源。

四、核心组件详解

4.1 RedisCacheManager(注解驱动)

组件自动配置的 RedisCacheManager 作为 Spring Cache 的缓存管理器,支持 @Cacheable@CachePut@CacheEvict 等标准注解。

关键行为

  • Key 序列化:StringRedisSerializer(字符串格式,便于在 Redis 客户端查看)
  • Value 序列化:跟随 db-redis 组件的 redis-serializer 全局配置
  • 禁止缓存 null 值(disableCachingNullValues
  • 使用 @Primary 标记,当存在多个 CacheManager 时默认使用此实例
  • 使用 @Lazy 延迟初始化,确保 Redis 连接工厂在初始化时已就绪

4.2 ICacheService(编程式操作)

ICacheService 是 EasyFK 缓存抽象接口,RedisCacheServiceImpl 为其 Redis 实现。

API 一览

方法参数返回值说明
cacheObject(key, value, namespace)键, 值, 命名空间BaseResult<?>写入缓存(永不过期)
cacheObject(key, value, times, namespace)键, 值, 过期时间, 命名空间BaseResult<?>写入缓存(指定过期时间)
queryByKey(key, namespace)键, 命名空间T查询缓存值
existKey(key, namespace)键, 命名空间boolean判断 Key 是否存在
removeKey(key, namespace)键, 命名空间BaseResult<?>删除缓存
expireKey(key, times, namespace)键, 过期时间, 命名空间void设置过期时间
expireKeyAt(key, date, namespace)键, 到期日期, 命名空间void设置到期时间点
getKeyExpire(key, namespace)键, 命名空间long获取剩余过期时间
getSetCountByKey(pattern, namespace)模式, 命名空间Long获取 Set 集合大小

参数说明

  • key:缓存键,由业务自行定义
  • namespace:命名空间,用于 Key 前缀隔离不同业务的数据
  • timesjava.time.Duration 类型的过期时间
  • 所有操作自动路由到配置的 datasource + databaseName 指定的 Redis 实例

五、典型使用场景

5.1 Spring Cache 注解方式

基础查询缓存

java
@Service
public class ProductService {

    @Cacheable(value = "products", key = "#productId")
    public ProductDTO getProduct(Long productId) {
        return productMapper.selectById(productId);
    }

    @CachePut(value = "products", key = "#product.id")
    public ProductDTO updateProduct(ProductDTO product) {
        productMapper.updateById(product);
        return product;
    }

    @CacheEvict(value = "products", key = "#productId")
    public void deleteProduct(Long productId) {
        productMapper.deleteById(productId);
    }

    @CacheEvict(value = "products", allEntries = true)
    public void refreshAllProducts() {
        // 触发全量刷新
    }
}

组合键

java
@Cacheable(value = "user-roles", key = "#userId + ':' + #appId")
public List<RoleDTO> getUserRoles(Long userId, String appId) {
    return roleMapper.selectByUserAndApp(userId, appId);
}

条件缓存

java
@Cacheable(value = "users", key = "#userId", unless = "#result == null")
public UserDTO getUser(Long userId) {
    return userMapper.selectById(userId);
}

@Cacheable(value = "users", key = "#userId", condition = "#userId > 0")
public UserDTO getUser(Long userId) {
    return userMapper.selectById(userId);
}

5.2 编程式缓存操作

java
@Service
public class OrderService {

    @Resource
    private ICacheService cacheService;

    private static final String NS = "order";

    public OrderDTO getOrder(String orderId) {
        OrderDTO order = cacheService.queryByKey(orderId, NS);
        if (order != null) {
            return order;
        }

        order = orderMapper.selectById(orderId);
        if (order != null) {
            cacheService.cacheObject(orderId, order, NS);
        }
        return order;
    }
}

5.3 带过期时间的缓存

java
public void cacheVerifyCode(String phone, String code) {
    cacheService.cacheObject("verify:" + phone, code, Duration.ofMinutes(5), "auth");
}

public void cacheToken(String token, UserSession session) {
    cacheService.cacheObject(token, session, Duration.ofHours(2), "session");
}

5.4 缓存键判断与删除

java
boolean exists = cacheService.existKey("user:10001", "user");

cacheService.removeKey("user:10001", "user");

5.5 过期时间管理

java
cacheService.expireKey("token:abc123", Duration.ofHours(2), "session");

Date activityEndTime = parseDate("2026-12-31 23:59:59");
cacheService.expireKeyAt("activity:config", activityEndTime, "activity");

long ttl = cacheService.getKeyExpire("token:abc123", "session");
log.info("Token 剩余有效期: {} 秒", ttl);

六、注意事项

6.1 Redis 连接配置

默认情况下使用 Spring Boot 标准 Redis 配置(spring.data.redis.*)。如果需要多数据源,通过 db-redis 组件的自定义配置(easyfk.config.db.redis)管理,详见多数据源场景。

6.2 null 值不会被缓存

组件默认配置了 disableCachingNullValues(),这意味着:

  • @Cacheable 方法返回 null不会写入缓存
  • 下次相同参数仍会穿透到实际方法执行
  • 如果需要防止缓存穿透,请在业务层返回空对象代替 null

6.3 @Cacheable 的 value 与 caches 配置的对应关系

@Cacheable(value = "users") 中的 value 对应 YAML 配置中 caches 列表里的 cache-name

yaml
caches:
  - cache-name: users
    time-to-live: 2h
注意
如果使用了未在 caches 中声明的缓存名称,该缓存空间将使用 Spring Cache 的默认行为(无 TTL,允许缓存 null 值),而非全局 time-to-live

6.4 注解方式与编程方式的 Key 格式不同

  • 注解方式:Key 格式为 cacheName::key(Spring Cache 默认的 :: 分隔)
  • 编程方式:Key 格式为 namespace::key(由 RedisUtil.wrapKey() 拼接)
注意
两种方式的 Key 不互通,不要混用来读写同一个缓存数据。选择一种方式并保持统一。

6.5 序列化器配置

Value 序列化器由 db-rediseasyfk.config.db.redisson.redis-serializer 全局控制,可选值为 RedisValueSerializer 枚举:

说明
DEFAULT默认序列化器(SmartRedisSerializer)
FastJSONFastJSON 序列化
KRYOKryo 序列化

确保缓存对象可被配置的序列化器正确序列化/反序列化。

6.6 缓存空间名称(cacheName)命名规范

建议使用小写字母 + 短横线的命名规范:

plaintext
✅ 推荐: users, order-details, verify-code, sys-config
❌ 避免: Users, orderDetails, VERIFY_CODE, sys.config

七、性能优化指南

7.1 合理设置 TTL

plaintext
TTL 过短 → 缓存命中率低 → Redis 压力小,数据源压力大
TTL 过长 → 缓存命中率高 → 数据一致性差,内存占用大

推荐 TTL 配置

数据类型推荐 TTL说明
验证码5m短时效
登录 Token / Session2h ~ 24h根据安全策略
用户信息30m ~ 2h变更后主动失效
商品详情15m ~ 1h取决于更新频率
字典/配置数据6h ~ 24h几乎不变
统计数据5m ~ 30m允许短暂延迟

7.2 避免大 Key / 大 Value

  • 单个缓存 Value 建议不超过 10KB,最大不超过 1MB
  • 避免将大集合(如 10000+ 条记录的 List)整体缓存
  • 大对象考虑拆分为多个小 Key,或使用 Redis Hash 结构

7.3 批量操作

对于需要批量查询的场景,避免循环调用单个 Key 查询,考虑使用 db-redis 组件的 Pipeline 或 Multi 操作。

7.4 缓存预热

对于冷启动后短时间大量请求的场景,建议在应用启动时主动预加载热点数据到缓存:

java
@Component
public class CacheWarmer implements CommandLineRunner {

    @Resource
    private ICacheService cacheService;

    @Resource
    private DictMapper dictMapper;

    @Override
    public void run(String... args) {
        List<DictDTO> dictList = dictMapper.selectAll();
        dictList.forEach(dict ->
            cacheService.cacheObject(
                dict.getType() + ":" + dict.getCode(),
                dict,
                Duration.ofHours(24),
                "dict"
            )
        );
        log.info("字典缓存预热完成,共 {} 条", dictList.size());
    }
}

7.5 使用独立 Redis 实例

生产环境建议缓存使用独立的 Redis 实例(与业务数据分离),通过 datasource 配置指向专用实例:

  • 避免缓存淘汰影响业务数据
  • 缓存 Redis 可使用 allkeys-lru 淘汰策略
  • 业务 Redis 使用 noeviction 策略保证数据不丢失

八、FAQ 常见问题

Q1: 报错 No qualifying bean of type ‘RedisConnectionManager’

原因:未配置 db-redis 组件的 Redis 数据源。cache-redis 依赖 db-redis 提供连接管理。

解决:在 application.yml 中添加 easyfk.config.db.redis.datasource 配置。

Q2: @Cacheable 不生效?

常见原因:

  1. 自调用问题:同一个类内部方法 A 调用方法 B,B 上的 @Cacheable 不生效(Spring AOP 代理限制)
  2. 方法不是 public
  3. 方法参数未正确参与 Key 生成
java
// 错误:自调用不走代理
@Service
public class UserService {
    public void process() {
        getUserById(1L); // ← 不会走缓存!
    }

    @Cacheable(value = "users", key = "#userId")
    public UserDTO getUserById(Long userId) { ... }
}

// 正确:通过注入自身或拆分到不同 Service
@Service
public class UserService {
    @Resource
    private UserCacheService userCacheService;

    public void process() {
        userCacheService.getUserById(1L); // ← 正常走缓存
    }
}

Q3: 缓存数据更新后还是读到旧数据?

  1. 确认更新操作是否使用了 @CachePut@CacheEvict
  2. 确认注解的 valuekey 与查询时一致
  3. 如果使用编程式缓存,确认更新后调用了 removeKey 或重新 cacheObject

Q4: 如何查看 Redis 中实际的缓存数据?

使用 Redis 客户端工具(如 Redis Insight、redis-cli)连接对应实例:

bash
redis-cli> keys users::*
redis-cli> get users::10001
redis-cli> keys *order*

Q5: 全局 time-to-live 设为 0 是什么效果?

time-to-live: 0 表示缓存永不过期(需手动失效或等 Redis 内存淘汰),不建议在生产环境使用。建议至少配置一个合理的全局默认值。

Q6: cache-redis 和 cache-mult 能同时使用吗?

不建议同时引入。两者都会注册 CacheManager Bean,可能导致冲突。选择其一:

  • 只需要 Redis 缓存 → 使用 cache-redis
  • 需要本地 + Redis 多级缓存 → 使用 cache-mult

Q7: 如何在不同方法中使用不同的缓存空间?

直接在注解的 value 参数中指定不同的缓存空间名称:

java
@Cacheable(value = "users", key = "#userId")
public UserDTO getUser(Long userId) { ... }

@Cacheable(value = "products", key = "#productId")
public ProductDTO getProduct(Long productId) { ... }

每个缓存空间的 TTL 在 YAML 的 caches 列表中独立配置。

cache-redis — 分布式缓存,加速数据访问。

— END —