美文网首页Spring-Boot
扩展spring cache 支持缓存多租户及其自动过期

扩展spring cache 支持缓存多租户及其自动过期

作者: 冷冷zz | 来源:发表于2019-03-25 20:32 被阅读20次

    spring cache 的概念

    Spring 支持基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案(例如 EHCache 或者 OSCache),而是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果。

    • @Cacheable 使用效果 ,更具 cacheName(value) + 请求入参 (key) 组成保存redis中的key
    public class PigxClientDetailsService extends JdbcClientDetailsService {
        @Cacheable(value = SecurityConstants.CLIENT_DETAILS_KEY, key = "#clientId")
        public ClientDetails loadClientByClientId(String clientId) {
            return super.loadClientByClientId(clientId);
        }
    }}
    
    image

    多租户下缓存问题分析

    image
    • 默认情况 A租户入参为K1 请求 应用,spring cache 会自动缓存 K1 的值,如果B租户 入参同时为K1 请求应用时,spring cache 还是会自动关联到同一个 Redis K1 上边查询数据。
    • 在多租户下 A/B 租户所请求的K1 并不是同一入参(虽然看起来参数名 参数值都是一样的),更不能返回同一个结果。
    • 默认的spring cache 根据入参来区分 不能满足多租户系统的设计需求,不能实现根据租户隔离。

    区分缓存增加租户标识

    image
    • A租户入参为K1 ,spring cache 维护Redis Key 在拼接一个租户信息
    • KEY = cacheName + 入参 + 租户标识
    • 这样A/B 租户请求参数相同时,读取的也是不同的Key 里面的值,避免数据脏读,保证隔离型

    重写Spring Cache 的 cacheManager 缓存管理器

    • 从上下文中获取租户ID,重写@Cacheable value 值即可完成,然后注入这个 cacheManager
    @Slf4j
    public class RedisAutoCacheManager extends RedisCacheManager {
        /**
         * 从上下文中获取租户ID,重写@Cacheable value 值
         * @param name
         * @return
         */
        @Override
        public Cache getCache(String name) {
            return super.getCache(TenantContextHolder.getTenantId() + StrUtil.COLON + name);
        }
    }
    
    • 为什么要用 StrUtil.COLON 即 ':' 分割
      在GUI 工具中,会通过':'的分隔符,进行分组,展示效果会更好

    增加 spring cache 的主动过期功能

    • 默认的注解里面没有关于时间的入参,如下图
    public @interface Cacheable {
    
        @AliasFor("cacheNames")
        String[] value() default {};
    
        @AliasFor("value")
        String[] cacheNames() default {};
    
        String key() default "";
    
        String keyGenerator() default "";
    
        String cacheManager() default "";
    
        String cacheResolver() default "";
    
        String condition() default "";
    
        String unless() default "";
    
        boolean sync() default false;
    
    }
    
    
    • 还是以value作为入口 value = "menu_details#2000" 通过对vaue 追加一个数字 并通过特殊字符分割,作为过期时间入参
    @Service
    @AllArgsConstructor
    public class PigXMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {
        private final SysRoleMenuMapper sysRoleMenuMapper;
    
        @Override
        @Cacheable(value = "menu_details#2000", key = "#roleId  + '_menu'")
        public List<MenuVO> findMenuByRoleId(Integer roleId) {
            return baseMapper.listMenusByRoleId(roleId);
        }
    }
    
    • 重写cachemanager 另个重要的方法 创建缓存的方法,通过截取 value 中设置的过期时间,赋值给你RedisCacheConfiguration
    public class RedisAutoCacheManager extends RedisCacheManager {
        private static final String SPLIT_FLAG = "#";
        private static final int CACHE_LENGTH = 2;
    
        @Override
        protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
            if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
                return super.createRedisCache(name, cacheConfig);
            }
    
            String[] cacheArray = name.split(SPLIT_FLAG);
            if (cacheArray.length < CACHE_LENGTH) {
                return super.createRedisCache(name, cacheConfig);
            }
    
            if (cacheConfig != null) {
                long cacheAge = Long.parseLong(cacheArray[1]);
                cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(cacheAge));
            }
            return super.createRedisCache(name, cacheConfig);
        }
    }
    
    • spring cache 操作缓存时 获取到上步设置的ttl 赋值给key
        @Override
        public void put(Object key, @Nullable Object value) {
    
            Object cacheValue = preProcessCacheValue(value);
    
            if (!isAllowNullValues() && cacheValue == null) {
    
                throw new IllegalArgumentException(String.format(
                        "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                        name));
            }
    
            cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
        }
    

    总结

    相关文章

      网友评论

        本文标题:扩展spring cache 支持缓存多租户及其自动过期

        本文链接:https://www.haomeiwen.com/subject/ujqivqtx.html