[031][缓存模块]RedisTemplate工具的租户隔离设计自动Key前缀机制本项目代码:https://gitee.com/yunjiao-source/tutorials4j/tree/master/framework在多租户SaaS系统中不同租户的数据必须严格隔离。当多个租户共享同一套Redis缓存时如何保证缓存Key不会冲突本文以一个轻量级框架的实现为例分析其利用租户上下文自动为所有缓存Key添加租户前缀的设计思路。一、背景与需求假设系统中有租户A和租户B他们都访问同一个缓存数据项user:123。若不加以区分租户A将可能读到租户B的用户信息造成严重的数据泄露。解决方案通常有两种为每个租户部署独立Redis实例—— 隔离彻底但成本高。在Key中嵌入租户标识—— 共享实例但Key自动区分。本文分析的代码采用了第二种方案且实现方式对业务代码完全透明开发者无需手动拼接租户ID只需在请求入口设置租户上下文框架便会自动为所有缓存Key添加租户前缀。二、核心组件与租户前缀生成2.1 租户上下文持有者代码中使用了TenantContextHolder.get()来获取当前租户标识。这是一个典型的基于ThreadLocal的工具类其实现不在本次代码片段中但作用非常清晰返回当前请求对应的租户ID例如tenantA。2.2 前缀策略工厂RedisUtilsRedisUtils接口中定义了静态方法defaultCacheKeyPrefix()它返回一个CacheKeyPrefix函数式接口实例staticCacheKeyPrefixdefaultCacheKeyPrefix(){returnname-TenantContextHolder.get():name::;}name缓存名称例如users返回值示例若租户ID为acme缓存名为users则生成的前缀为acme:users::2.3 带自定义二级前缀的重载方法defaultCacheKeyPrefix(String prefix)允许在租户前缀和缓存名之间再插入一段自定义前缀例如defaultCacheKeyPrefix(v2)// 租户acme缓存users - acme:v2:users::这种设计支持更细粒度的Key版本管理或业务分类。三、自动前缀的注入方式3.1 自定义Key序列化器PrefixKeyStringRedisSerializer该类继承自StringRedisSerializer并重写了serialize方法publicbyte[]serialize(Stringvalue){returnsuper.serialize(RedisUtils.defaultCacheKeyPrefix().compute(value));}传入的value是原始的Key如user:123compute(value)将原始Key转换为带租户前缀的完整Key如acme:userCache::user:123然后调用父类的字符串序列化逻辑这意味着任何使用该序列化器的RedisTemplate在写入或读取Key时都会自动加上租户前缀。3.2 配置类中的装配RedisConfigurationRedisConfiguration内部有一个条件配置类InnerConfiguration它会在StringRedisTemplate和RedisTemplateBean存在时自动执行PostConstructpublicvoidpostConstruct(){PrefixKeyStringRedisSerializerserializernewPrefixKeyStringRedisSerializer();stringRedisTemplate.setKeySerializer(serializer);stringRedisTemplate.setHashKeySerializer(serializer);redisTemplate.setKeySerializer(serializer);redisTemplate.setHashKeySerializer(serializer);}同时替换了普通Key和Hash结构的Key序列化器由于PostConstruct在Bean初始化后执行所有后续操作都会自动生效3.3 缓存管理器的前缀配置除了RedisTemplateSpring Cache抽象层Cacheable等也使用了相同的前缀策略。在RedisUtils.fillConfiguration方法中configurationconfiguration.computePrefixWith(RedisUtils.defaultCacheKeyPrefix());该方法被用于构建RedisCacheManager的每个缓存配置确保通过注解生成的缓存Key也自动携带租户前缀。四、租户隔离效果演示假设两个租户同时调用以下代码// 租户AtenantId aredisTemplate.opsForValue().set(user:1,Alice);// 租户BtenantId bredisTemplate.opsForValue().set(user:1,Bob);由于序列化器自动添加前缀Redis中实际存储的Key为a:userCache::user:1→Aliceb:userCache::user:1→Bob租户A永远无法读取到租户B的数据且业务层代码完全无感知。五、设计优点与注意事项优点无侵入性业务开发者无需关心租户隔离只需确保请求入口设置了TenantContextHolder。统一管理所有缓存Key的前缀规则集中定义在RedisUtils中易于调整。支持自定义二级前缀允许不同模块或版本使用不同的Key前缀避免升级时的缓存混乱。透明覆盖通过替换RedisTemplate的序列化器对已有代码零修改。注意事项租户上下文必须正确传递在异步线程或消息消费场景下需要手动拷贝租户ID到子线程可使用装饰器或TTL。前缀长度影响内存过长的租户ID 缓存名会产生较长的Key但相比隔离性带来的收益通常可接受。清除缓存时需注意RedisTemplate.keys(acme:*)扫描时需指定正确前缀避免跨租户删除。六、总结本文分析的代码通过自定义Key序列化器 租户上下文的组合实现了透明、高效的Redis缓存租户隔离。其核心思路非常简洁在Key进入Redis之前“悄悄”加上租户前缀。对于希望共享Redis实例但又必须保证数据隔离的多租户系统这种模式极具参考价值。该设计不仅适用于租户隔离也可推广至环境隔离dev/test/prod、应用标识注入等场景是一种通用的缓存Key“装饰器”模式。
[031][缓存模块]RedisTemplate工具的租户隔离设计:自动Key前缀机制
发布时间:2026/6/12 1:15:33
[031][缓存模块]RedisTemplate工具的租户隔离设计自动Key前缀机制本项目代码:https://gitee.com/yunjiao-source/tutorials4j/tree/master/framework在多租户SaaS系统中不同租户的数据必须严格隔离。当多个租户共享同一套Redis缓存时如何保证缓存Key不会冲突本文以一个轻量级框架的实现为例分析其利用租户上下文自动为所有缓存Key添加租户前缀的设计思路。一、背景与需求假设系统中有租户A和租户B他们都访问同一个缓存数据项user:123。若不加以区分租户A将可能读到租户B的用户信息造成严重的数据泄露。解决方案通常有两种为每个租户部署独立Redis实例—— 隔离彻底但成本高。在Key中嵌入租户标识—— 共享实例但Key自动区分。本文分析的代码采用了第二种方案且实现方式对业务代码完全透明开发者无需手动拼接租户ID只需在请求入口设置租户上下文框架便会自动为所有缓存Key添加租户前缀。二、核心组件与租户前缀生成2.1 租户上下文持有者代码中使用了TenantContextHolder.get()来获取当前租户标识。这是一个典型的基于ThreadLocal的工具类其实现不在本次代码片段中但作用非常清晰返回当前请求对应的租户ID例如tenantA。2.2 前缀策略工厂RedisUtilsRedisUtils接口中定义了静态方法defaultCacheKeyPrefix()它返回一个CacheKeyPrefix函数式接口实例staticCacheKeyPrefixdefaultCacheKeyPrefix(){returnname-TenantContextHolder.get():name::;}name缓存名称例如users返回值示例若租户ID为acme缓存名为users则生成的前缀为acme:users::2.3 带自定义二级前缀的重载方法defaultCacheKeyPrefix(String prefix)允许在租户前缀和缓存名之间再插入一段自定义前缀例如defaultCacheKeyPrefix(v2)// 租户acme缓存users - acme:v2:users::这种设计支持更细粒度的Key版本管理或业务分类。三、自动前缀的注入方式3.1 自定义Key序列化器PrefixKeyStringRedisSerializer该类继承自StringRedisSerializer并重写了serialize方法publicbyte[]serialize(Stringvalue){returnsuper.serialize(RedisUtils.defaultCacheKeyPrefix().compute(value));}传入的value是原始的Key如user:123compute(value)将原始Key转换为带租户前缀的完整Key如acme:userCache::user:123然后调用父类的字符串序列化逻辑这意味着任何使用该序列化器的RedisTemplate在写入或读取Key时都会自动加上租户前缀。3.2 配置类中的装配RedisConfigurationRedisConfiguration内部有一个条件配置类InnerConfiguration它会在StringRedisTemplate和RedisTemplateBean存在时自动执行PostConstructpublicvoidpostConstruct(){PrefixKeyStringRedisSerializerserializernewPrefixKeyStringRedisSerializer();stringRedisTemplate.setKeySerializer(serializer);stringRedisTemplate.setHashKeySerializer(serializer);redisTemplate.setKeySerializer(serializer);redisTemplate.setHashKeySerializer(serializer);}同时替换了普通Key和Hash结构的Key序列化器由于PostConstruct在Bean初始化后执行所有后续操作都会自动生效3.3 缓存管理器的前缀配置除了RedisTemplateSpring Cache抽象层Cacheable等也使用了相同的前缀策略。在RedisUtils.fillConfiguration方法中configurationconfiguration.computePrefixWith(RedisUtils.defaultCacheKeyPrefix());该方法被用于构建RedisCacheManager的每个缓存配置确保通过注解生成的缓存Key也自动携带租户前缀。四、租户隔离效果演示假设两个租户同时调用以下代码// 租户AtenantId aredisTemplate.opsForValue().set(user:1,Alice);// 租户BtenantId bredisTemplate.opsForValue().set(user:1,Bob);由于序列化器自动添加前缀Redis中实际存储的Key为a:userCache::user:1→Aliceb:userCache::user:1→Bob租户A永远无法读取到租户B的数据且业务层代码完全无感知。五、设计优点与注意事项优点无侵入性业务开发者无需关心租户隔离只需确保请求入口设置了TenantContextHolder。统一管理所有缓存Key的前缀规则集中定义在RedisUtils中易于调整。支持自定义二级前缀允许不同模块或版本使用不同的Key前缀避免升级时的缓存混乱。透明覆盖通过替换RedisTemplate的序列化器对已有代码零修改。注意事项租户上下文必须正确传递在异步线程或消息消费场景下需要手动拷贝租户ID到子线程可使用装饰器或TTL。前缀长度影响内存过长的租户ID 缓存名会产生较长的Key但相比隔离性带来的收益通常可接受。清除缓存时需注意RedisTemplate.keys(acme:*)扫描时需指定正确前缀避免跨租户删除。六、总结本文分析的代码通过自定义Key序列化器 租户上下文的组合实现了透明、高效的Redis缓存租户隔离。其核心思路非常简洁在Key进入Redis之前“悄悄”加上租户前缀。对于希望共享Redis实例但又必须保证数据隔离的多租户系统这种模式极具参考价值。该设计不仅适用于租户隔离也可推广至环境隔离dev/test/prod、应用标识注入等场景是一种通用的缓存Key“装饰器”模式。