Skip to content

Redis 与本地缓存 ​

这篇解决什么 ​

热点数据不要每次打库:令牌、角色、字典、文件客户端各有落点。读完能改 RedisTemplate / @Cacheable,会把租户写进 Key,并判断该用 Redis 还是进程内缓存。

默认连本机 Redis 127.0.0.1:6379,库号 0。管理端前缀仍是 /admin-api。

示意图:业务模块只调 DAO 或注解;框架连 Redis,或把有状态对象放进进程内缓存。

组件位置 ​

名称说明仓库路径
Redis StarterRedisson 客户端、RedisTemplate、Spring Cacheruoyi-office/yudao-framework/yudao-spring-boot-starter-redis/
连接本机 host / port / databaseruoyi-office/yudao-server/src/main/resources/application-local.yaml
默认 TTLspring.cache.type=REDIS,time-to-live: 1hruoyi-office/yudao-server/src/main/resources/application.yaml
租户 Cache@Cacheable 的 name 后拼 tenantIdyudao-spring-boot-starter-biz-tenant/.../TenantRedisCacheManager.java
Key 常量每个模块一份,像对待表名例如 yudao-module-system-server/.../dal/redis/RedisKeyConstants.java
本地工具Guava LoadingCache,上限 10000ruoyi-office/yudao-framework/yudao-common/.../CacheUtils.java
监控页基础信息、命令、内存ruoyi-office-vben/apps/web-antd/src/views/infra/redis/

多数读模型用 Redis。进程内缓存留给「带连接的 Java 对象」和框架热路径。业务数据不要再抄一份本地 Map。

编程式缓存 ​

过期时间和 Key 要自己算时,用 RedisTemplate / StringRedisTemplate。客户端是 Redisson,Starter 在 RedissonAutoConfigurationV2 之前注册自己的 RedisTemplate。

连接与序列化 ​

application-local.yaml 只配地址。生产再开密码,不要把共享环境的 Redis 写进本地默认。

yaml
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      database: 0

YudaoRedisAutoConfiguration:Key 用 String,Value 用 JSON,并注册 JavaTimeModule,避免 LocalDateTime 序列化失败。

java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    template.setKeySerializer(RedisSerializer.string());
    template.setHashKeySerializer(RedisSerializer.string());
    RedisSerializer<?> redisSerializer = buildRedisSerializer();
    template.setValueSerializer(redisSerializer);
    template.setHashValueSerializer(redisSerializer);
    return template;
}

多系统共用一个 Redis 库时,给 spring.cache.redis.key-prefix 加前缀。computePrefixWith 用单冒号 cacheName:,方便客户端按目录看 Key。

Redis Key 常量 ​

每个 yudao-module-xxx-server 的 dal/redis/RedisKeyConstants 集中声明 Key。查一个模块用了哪些缓存,先打开这个接口。

名称说明仓库路径
oauth2_access_token:%s访问令牌,TTL 跟过期时间走RedisKeyConstants.OAUTH2_ACCESS_TOKEN
weather:now:%s:%s天气结果,实例内跨租户共享WEATHER_NOW
weather:lock:%s:%s天气单飞锁WEATHER_LOCK
role / user_role_idsSpring Cache 的 cacheName同文件其它常量

简单值可以直接 opsForValue。复杂对象建 DO,放 dal/dataobject,再配一个 RedisDAO。

访问令牌 ​

OAuth2AccessTokenDO 映表 system_oauth2_access_token,并继承 TenantBaseDO。令牌过期时间按客户端配置,不适合固定 1 小时的 @Cacheable。

名称说明仓库路径
OAuth2AccessTokenDO令牌实体,租户在字段里yudao-module-system-server/.../oauth2/OAuth2AccessTokenDO.java
OAuth2AccessTokenRedisDAOStringRedisTemplate + JsonUtils.../dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java
OAuth2TokenServiceImpl先 Redis,没有再回源 MySQL.../service/oauth2/OAuth2TokenServiceImpl.java
java
public void set(OAuth2AccessTokenDO accessTokenDO) {
    String redisKey = formatKey(accessTokenDO.getAccessToken());
    accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null)
            .setCreator(null).setDeleted(null);
    long time = LocalDateTimeUtil.between(LocalDateTime.now(),
            accessTokenDO.getExpiresTime(), ChronoUnit.SECONDS);
    if (time > 0) {
        stringRedisTemplate.opsForValue().set(redisKey,
                JsonUtils.toJsonString(accessTokenDO), time, TimeUnit.SECONDS);
    }
}

getAccessToken 先 oauth2AccessTokenRedisDAO.get。没有则查库,未过期再回写。删除令牌时库和 Redis 一起清。

RedisTemplate 还是 @Cacheable

TTL 跟业务字段走、要 SETNX 锁:用 RedisDAO。固定过期、读多写少:用 @Cacheable。两种可以同时出现在一个模块里。

声明式缓存 ​

YudaoCacheAutoConfiguration 打开 @EnableCaching,Cache 落 Redis。读方法加注解即可。

java
@Cacheable(value = "users", key = "#id")
UserDO getUserById(Integer id);

注解 ​

名称说明仓库路径
@Cacheable有缓存直接返回;没有则执行方法再写入Spring Cache
@CachePut每次执行方法,再按条件写入同上
@CacheEvict删一条或 allEntries 清空该 name同上
unless例如 #result == null 不缓存空值RoleServiceImpl

allEntries 靠 SCAN,批量大小是 yudao.cache.redis-scan-batch-size,默认 30。要按前缀删一批,Spring Cache 做不到,改 RedisDAO 或拆更细的 Key。

TenantRedisCacheManager 和 TimeoutRedisCacheManager 都 setTransactionAware(true)。@Transactional 里的 @CacheEvict / @CachePut 等到 afterCommit。没事务则立刻生效。

角色缓存 ​

角色走被动读:第一次 getRoleFromCache 回源并写入;改库后再删缓存。不必把全表提前灌进 Redis。

java
@Cacheable(value = RedisKeyConstants.ROLE, key = "#id", unless = "#result == null")
public RoleDO getRoleFromCache(Long id) {
    return roleMapper.selectById(id);
}

@CacheEvict(value = RedisKeyConstants.ROLE, key = "#updateReqVO.id")
public void updateRole(RoleSaveReqVO updateReqVO) { /* 先改库 */ }

@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
public void deleteRole(Long id) { /* 先删库 */ }

批量按 id 取缓存时,Spring Cache 没有批量 get。getRoleListFromCache 拿代理 getSelf(),对每个 id 调 getRoleFromCache,让注解生效。

同类写法还有部门子节点、用户角色、菜单、短信/邮件模板、OAuth 客户端。cacheName 都来自该模块的 RedisKeyConstants。

示意图:先读缓存;写库成功后再删 Key。

过期时间 ​

没写后缀时,用 spring.cache.redis.time-to-live,默认 1 小时。TimeoutRedisCacheManager 认 cacheNames 里的 name#ttl。

名称说明仓库路径
默认 1h未写 #ttl 的 @Cacheableapplication.yaml 的 spring.cache.redis
name#30m该 Cache 单独 30 分钟TimeoutRedisCacheManager
单位d 天、h 小时、m 分钟、s 秒;纯数字当秒同上
资产分类asset_category_simple_list#30myudao-module-asset-server/.../RedisKeyConstants.java
java
String CATEGORY_SIMPLE_LIST = "asset_category_simple_list#30m";

# 后面若还带冒号,只取时间段。解析后的 cacheName 不含 ttl 后缀。

租户与 Key ​

开启多租户后,主 RedisCacheManager 是 TenantRedisCacheManager。getCache(name) 会变成 name:{tenantId},再交给父类拼 Key。

当前上下文忽略租户、或 tenantId 为空、或 name 在 yudao.tenant.ignore-caches 里,则不拼。

名称说明仓库路径
默认分桶role + 租户 1 + id 10 → role:1:10TenantRedisCacheManager
ignore-caches全局共享的 cacheNameyudao-server/.../application.yaml
已忽略示例user_role_ids、oauth_client、sms_template 等同上 yudao.tenant.ignore-caches
编程式 Keyoauth2_access_token:{token} 不自动加租户OAuth2AccessTokenRedisDAO

oauth2_access_token 的租户在 JSON 的 tenantId 里,不在 Key 上。令牌本身全局唯一。天气 Key 也是网格坐标,跨租户共享。

租户必须进隔离 Key

角色、部门、业务列表默认按租户分桶。全局字典式缓存才写进 ignore-caches。自己用 RedisTemplate 拼 Key 时,隔离数据要带 tenantId,不要抄令牌那种「值里带租户、Key 不带」。

菜单全表缓存在 menu_list。表是全局的,查库不按租户滤;未列入 ignore-caches 时,Spring Cache 仍可能按当前租户分一份。

缓存击穿 ​

热点 Key 同时过期,并发会一起打库或打外部 HTTP。@Cacheable 本身不加锁。

天气用 SETNX 单飞:先读 weather:now,没有再抢 weather:lock,只有拿到锁的线程去拉数据。

java
public boolean tryLock(String part1, String part2) {
    Boolean ok = stringRedisTemplate.opsForValue().setIfAbsent(
            formatLockKey(part1, part2), "1", 10L, TimeUnit.SECONDS);
    return Boolean.TRUE.equals(ok);
}

本地 CacheUtils 用 refreshAfterWrite:刷新时其它线程仍读旧值。异步版只阻塞加载线程。

热点回源要单飞

外部接口、大 SQL 不要裸 @Cacheable 然后集体失效。编程式用短 TTL 锁;进程内用 refreshAfterWrite。空结果要 unless 或单独的空值 TTL,避免把「没有」缓存成永久 miss。

本地缓存 ​

Redis 够用就不要再加一层 JVM 缓存。多节点时本地副本不会自动对齐。

还在用本地缓存的,多半是「有状态」对象:文件客户端、微信 SDK、敏感词引擎。它们不好序列化进 Redis。

名称说明仓库路径
buildAsyncReloadingCache异步刷新;全局、与人无关CacheUtils
buildCache同步刷新;和登录用户 / ThreadLocal 有关同上
租户校验1 分钟,validTenant / getTenantIdsTenantFrameworkServiceImpl
字典1 分钟,dictType → ListDictFrameworkUtils
权限1 分钟,Key 带 userId:deptIdSecurityFrameworkServiceImpl
文件客户端10 秒,FileClient 单例FileConfigServiceImpl
微信 SDK10 秒,WxMpService / WxMaServiceSocialClientServiceImpl
Caffeine只给 JSQLParser 解析 SQL,不是业务缓存YudaoMybatisAutoConfiguration

和「人」有关的(当前用户、部门、权限)用 buildCache,避免异步线程丢 ThreadLocal。和「系统」有关的用 buildAsyncReloadingCache。

java
private final LoadingCache<Long, FileClient> clientCache =
        buildAsyncReloadingCache(Duration.ofSeconds(10L),
                new CacheLoader<Long, FileClient>() {
                    @Override
                    public FileClient load(Long id) {
                        FileConfigDO config = Objects.equals(0L, id)
                                ? fileConfigMapper.selectByMaster()
                                : fileConfigMapper.selectById(id);
                        // createOrUpdateFileClient ...
                        return fileClientFactory.getFileClient(
                                config == null ? id : config.getId());
                    }
                });

SecurityFrameworkServiceImpl 的权限 Key 必须带会话部门。多组织切换任职后,只按 userId 缓存会继续用旧权限大约 1 分钟。false 不进权限缓存,避免负缓存卡住。

公众号账号仍用 volatile Map + @PostConstruct。initLocalCache 包在 TenantUtils.executeIgnore 里,避免只加载当前租户。多节点靠 @Scheduled 每 60 秒看 updateTime,有变更再全量重建,并重做 MpServiceFactory。

新业务优先 Redis。只有对象带连接、或框架每次请求都要读时,再加 CacheUtils。

Redis 监控 ​

RedisController 调 INFO、DBSIZE、INFO commandstats,拼成 RedisMonitorRespVO。

名称说明仓库路径
接口GET /infra/redis/get-monitor-infoyudao-module-infra-server/.../redis/RedisController.java
权限infra:redis:get-monitor-info同上 @PreAuthorize
页面基础信息、命令统计、内存ruoyi-office-vben/apps/web-antd/src/views/infra/redis/
APIgetRedisMonitorInfo().../src/api/infra/redis/index.ts

本机连不上 127.0.0.1:6379 时,先看进程和 application-local.yaml,不要改成别的环境的 Redis。

配置与操作 ​

业务缓存用 @Cacheable,底层是 Redis,不是 Caffeine。本地 LoadingCache 走 Guava CacheUtils。Redis 监控页只看运维,不改 TTL。

开启见 框架层。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版