本文目录导读:

设计统一的 Java 缓存键名结构,核心目标是 可读性、可管理性、避免冲突、以及方便失效,没有绝对的“最佳”,但有公认的“最佳实践”。
下面为你提供一套层次分明、经过大量生产环境验证的统一键名结构方案。
核心原则
- 分层命名:像包名一样,从大到小排列,用分隔符分开。
- 可读性强:看到键名就能大致知道它缓存了什么。
- 唯一性:不同业务、不同参数组合的键不会碰撞。
- 支持通配符:方便批量失效(这是为什么结构比无规则拼接重要的原因)。
- 避免过长:在可读性和性能之间取得平衡(Redis 等键越短,查询效率越高,但可读性优先)。
推荐方案:业务域:实体名:操作:唯一标识[:属性]
这是一个经典的冒号分隔的树形结构。
格式:
[系统]:[模块]:[业务域]:[实体名]:[操作]:[唯一标识]
详细解析
- 系统 (可选):多系统共用缓存时区分。
- 模块:粗粒度分组,
user,order,product。 - 业务域:进一步细分,
user模块下的account,profile,permission。 - 实体名:具体的数据对象,
User,OrderItem,ProductSku。 - 操作:缓存是为了什么操作生成的,
detail,list,count。 - 唯一标识:通常是数据库主键 ID,或复杂的组合键(用下划线或进一步冒号连接)。
- 属性 (可选):需要缓存实体的某个具体字段时使用。
例子
| 业务场景 | 缓存键 | 解释 |
|---|---|---|
| 用户详情 | user:account:User:detail:123 |
用户模块,账户域,User实体,详情,ID=123 |
| 用户订单列表 | user:order:Order:list:456 |
用户模块,订单域,Order实体,列表,用户ID=456 |
| 商品SKU详情(带租户) | product:goods:ProductSku:detail:SKU001 |
商品模块,商品域,SKU实体,详情,SKU编码 |
| 用户权限列表 | user:permission:Permission:list:123 |
用户模块,权限域,Permission实体,列表,用户ID=123 |
| 过期时间刷新 | user:session:Session:token:a1b2c3 |
用户模块,会话域,Session实体,token值 |
| 热点文章标签 | content:article:ArticleTag:list:789 |
内容模块,文章域,ArticleTag实体,列表,文章ID=789 |
| 用户最近登录时间 | user:account:User:attr:lastLoginTime:123 |
用户模块,账户域,User实体,属性(最近登录时间),用户ID=123 |
如何用代码实现(Java + Spring Cache)
使用 RedisCacheManager 时,可以自定义 CacheKeyPrefix 或通过 @Cacheable 的 key 属性生成。
定义常量 + 工具类(推荐)
public final class CacheKeyBuilder {
private static final String SEPARATOR = ":";
// 1. 基础方法
public static String build(String... parts) {
return String.join(SEPARATOR, parts);
}
// 2. 针对常见场景的封装方法
public static String userDetail(Long userId) {
return build("user", "account", "User", "detail", String.valueOf(userId));
}
public static String userOrderList(Long userId) {
return build("user", "order", "Order", "list", String.valueOf(userId));
}
public static String productSkuDetail(String skuCode) {
return build("product", "goods", "ProductSku", "detail", skuCode);
}
// 3. 针对组合键的场景
public static String buildCompositeKey(Object... keys) {
// 用下划线连接多个标识,作为唯一标识的一部分
// user:profile:User:detail:123_456 (如果ID是组合主键)
return Arrays.stream(keys)
.map(String::valueOf)
.collect(Collectors.joining("_"));
}
}
使用示例:
@Service
public class UserServiceImpl implements UserService {
@Override
@Cacheable(value = "user_detail_cache", key = "#userId")
public User getUserById(Long userId) {
// ... 查询数据库
}
// 在Service中使用KeyBuilder手动生成key,更灵活
@Override
@Cacheable(value = "user_detail_cache",
key = "T(com.example.utils.CacheKeyBuilder).userDetail(#userId)")
public User getCachedUser(Long userId) {
// ...
}
}
使用 Spring Expression (SpEL) 在注解中直接构造
适用于简单场景,但复杂时影响可读性。
@Override
@Cacheable(value = "user_detail_cache",
key = "'user:account:User:detail:' + #userId")
public User getUserById(Long userId) {
// ...
}
// 更复杂的组合
@Override
@Cacheable(value = "order_detail_cache",
key = "'order:order:Order:detail:' + #orderId")
public Order getOrderById(String orderId) {
// ...
}
特殊场景处理
带租户的数据 (SaaS)
格式: [系统]:[租户ID]:[模块]:[实体]:[操作]:[ID]
// tenantId = 1001 // user:1001:account:User:detail:123
带版本号的数据(用于缓存刷新或平滑迁移)
格式: v[版本号]:[业务域]:[实体]:[ID]
// v2:user:account:User:detail:123 // 当数据结构变更时,只需要修改版本号,旧缓存自动失效
分页/列表查询 (谨慎使用!)
列表查询的键通常包含查询参数,这会导致缓存键爆炸,不可控。
- 不推荐:
product:goods:Product:list:page=1&size=10&sort=price - 推荐方案:
- 对列表查询使用本地内存缓存(Caffeine),并设置极短的 TTL(如 1 秒)。
- 如果非要缓存,使用 Bloom Filter 先判断,然后回源 DB。
- 如果非要缓存列表结果,将键设计为统计维度,而不是用户参数维度:
product:goods:Product:list:hot_100(缓存热门前100的商品,而非用户任意搜索参数)。
缓存键前缀(Namespace)
在 Redis 或 Caffeine 中,可以在配置时统一添加一个系统级别的前缀,防止不同应用或环境冲突。
格式: [应用名称]:[环境]:[自定义键]
// 配置类
@Bean
public StringRedisTemplate redisTemplate(RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
// 全局键前缀(可选)
// 实际存储:myapp:prod:user:account:User:detail:123
template.setKeySerializer(new StringRedisSerializer() {
@Override
public String serialize(String key) {
return "myapp:prod:" + key;
}
});
return template;
}
| 场景 | 推荐键结构 |
|---|---|
| 单实体详情 | user:account:User:detail:{id} |
| 列表(热点数据) | product:goods:Product:list:hot_100 |
| 用户维度列表 | user:order:Order:list:{userId} (注意缓存失效和穿透问题) |
| 组合主键 | order:item:OrderItem:detail:{orderId}_{itemId} |
| 租户隔离 | user:{tenantId}:account:User:detail:{id} |
| 需要版本管理 | v2:user:account:User:detail:{id} |
最终建议:
- 全员对齐:整个团队或项目组统一使用一套规则,建立文档。
- 避免复杂:键的可读性 > 性能微优化,不建议使用 MD5 或 64位数字。
- 列表缓存慎用:列表查询(尤其是分页、排序、搜索)尽量不加缓存,或使用本地缓存 + 极短 TTL。
- 利用冒号: 在 Redis 中并不是特殊字符,但在
keys *扫描时可以配合pattern实现批量模糊删除(生产环境谨慎使用keys,可以考虑使用SCAN或redis-cli的--pattern)。