Spring Cache 多租户缓存隔离解决方案实践
作者:@小匠
本文提出通过自定义SpringCache的CacheResolver实现多租户数据隔离,动态添加租户标识至缓存键,解决缓存污染、数据泄露及失效范围控制问题,需要的朋友们下面随着小编来一起学习学习吧
在构建多租户 SaaS 应用时,确保不同租户数据隔离是至关重要的。Spring Cache 作为常用的缓存框架,在多租户场景下需要特殊处理以避免数据泄露和缓存污染。本文将分享一种通用的多租户缓存解决方案。
问题背景
在多租户系统中,所有租户共享同一套应用实例,但数据必须严格隔离。使用 Spring Cache 时,如果不做特殊处理,可能会出现以下问题:
- 不同租户的数据缓存到同一个 key 下,导致数据混乱
- 租户 A 查询的数据被租户 B 获取到,造成数据泄露
- 缓存失效时影响所有租户,而非仅影响特定租户
解决方案:自定义 CacheResolver
核心思路
通过自定义 CacheResolver
,动态生成包含租户标识的缓存名称,从而实现租户间缓存的完全隔离。
实现代码
@Component("tenantCacheResolver") public class GenericTenantCacheResolver implements CacheResolver { @Autowired private CacheManager cacheManager; @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) { // 从方法注解中获取缓存名称模板 String cacheNameTemplate = getCacheNameTemplate(context); // 获取当前租户ID String tenantId = getCurrentTenantId(); String finalCacheName = cacheNameTemplate.replace("{tenant}", tenantId != null ? tenantId : "default"); Cache cache = cacheManager.getCache(finalCacheName); return Collections.singletonList(cache); } private String getCacheNameTemplate(CacheOperationInvocationContext<?> context) { Cacheable cacheable = context.getMethod().getAnnotation(Cacheable.class); if (cacheable != null && cacheable.value().length > 0) { return cacheable.value()[0]; } return "default"; } private String getCurrentTenantId() { // 根据实际项目情况获取租户ID // 可能是从 SecurityContext、ThreadLocal 或其他上下文获取 return TenantContext.getCurrentTenantId(); } }
使用示例
@Service public class BudgetItemsService { @Cacheable(cacheResolver = "tenantCacheResolver", value = "budgetItems:list:{tenant}", key = "#itemName ?: 'default'", unless = "#result.isEmpty()") public List<BudgetItems> getList(String itemName) { // 业务逻辑 return budgetItemsMapper.selectList(wrapper); } @Cacheable(cacheResolver = "tenantCacheResolver", value = "user:info:{tenant}", key = "#userId", unless = "#result == null") public User getUserInfo(Long userId) { // 业务逻辑 return userMapper.selectById(userId); } }
高级版本:支持多个缓存
@Component("tenantCacheResolver") public class AdvancedTenantCacheResolver implements CacheResolver { @Autowired private CacheManager cacheManager; @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) { String tenantId = getCurrentTenantId(); String tenantSuffix = tenantId != null ? tenantId : "default"; List<Cache> caches = new ArrayList<>(); String[] cacheNames = getCacheNames(context); for (String cacheName : cacheNames) { String finalCacheName = cacheName.replace("{tenant}", tenantSuffix); Cache cache = cacheManager.getCache(finalCacheName); if (cache != null) { caches.add(cache); } } return caches; } private String[] getCacheNames(CacheOperationInvocationContext<?> context) { Cacheable cacheable = context.getMethod().getAnnotation(Cacheable.class); if (cacheable != null) { return cacheable.value(); } return new String[]{"default"}; } private String getCurrentTenantId() { // 实际实现根据项目情况而定 return SecurityUtils.getTenantId(); } }
优势分析
- 通用性强:一个
CacheResolver
可以处理所有需要租户隔离的缓存场景 - 配置简单:只需在
@Cacheable
的 value 中使用{tenant}
占位符 - 维护方便:租户逻辑集中在一个地方处理
- 兼容性好:自动处理租户ID为null的情况
- 扩展性佳:可以轻松添加其他维度的缓存隔离(如用户、角色等)
注意事项
- 确保租户ID获取逻辑的正确性和性能
- 在租户ID为null时提供默认值,避免缓存键为空
- 合理设计缓存名称,避免过长或特殊字符
- 监控缓存使用情况,避免缓存膨胀
总结
通过自定义 CacheResolver
实现多租户缓存隔离,是一种优雅且实用的解决方案。它不仅解决了多租户场景下的缓存隔离问题,还保持了代码的简洁性和可维护性。这种方案可以广泛应用于各种多租户 SaaS 应用中,为系统提供安全可靠的缓存机制。
到此这篇关于Spring Cache 多租户缓存隔离解决方案实践的文章就介绍到这了,更多相关Spring Cache 多租户缓存隔离内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!