资讯专栏INFORMATION COLUMN

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

hover_lew / 1766人阅读

摘要:在多租户下租户所请求的并不是同一入参虽然看起来参数名参数值都是一样的,更不能返回同一个结果。默认的根据入参来区分不能满足多租户系统的设计需求不能实现根据租户隔离。

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);
    }
}}

多租户下缓存问题分析

默认情况 A租户入参为K1 请求 应用,spring cache 会自动缓存 K1 的值,如果B租户 入参同时为K1 请求应用时,spring cache 还是会自动关联到同一个 Redis K1 上边查询数据。

在多租户下 A/B 租户所请求的K1 并不是同一入参(虽然看起来参数名 参数值都是一样的),更不能返回同一个结果。

默认的spring cache 根据入参来区分 不能满足多租户系统的设计需求,不能实现根据租户隔离。

区分缓存增加租户标识

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 implements SysMenuService {
    private final SysRoleMenuMapper sysRoleMenuMapper;

    @Override
    @Cacheable(value = "menu_details#2000", key = "#roleId  + "_menu"")
    public List 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 的扩展即可实现对缓存 一些透明操作

cachemanager 是springcache 对外提供的API 扩展入口

以上源码参考个人项目 基于Spring Cloud、OAuth2.0开发基于Vue前后分离的开发平台

QQ: 2270033969 一起来聊聊你们是咋用 spring cloud 的吧。

欢迎关注我们的公众号获得更多的好玩JavaEE 实践

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73932.html

相关文章

  • Spring Data Redis 让 NoSQL 快如闪电 (1)

    摘要:以远程缓存服务器见长,对易挥发数据来说是极快型数据库。即使成功写入数据库,最后也可能会因为网络故障而使得缓存服务器以失败告终。 【编者按】本文作者为 Xinyu Liu,详细介绍了 Redis 的特性,并辅之以丰富的用例。在本文的第一部分,将重点概述 Redis 的方方面面。文章系国内 ITOM 管理平台 OneAPM 编译呈现。 建立在 Java 企业版之上的多层体系结构是强大的服务...

    JerryC 评论0 收藏0
  • java | Spring Boot 与 Redis 实现 Cache 以及 Session 共享

    摘要:完成状态编写中已完成维护中原文是一个使用编写的开源支持网络基于内存可选持久性的键值对存储数据库维基百科是目前业界使用广泛的基于内存的数据库。 完成状态 [ ] 编写中 [ ] 已完成 [x] 维护中 原文 Redis Redis是一个使用ANSI C编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库 ------ 维基百科 Redis 是目前业界使用广泛的基于内存的...

    ssshooter 评论0 收藏0
  • 用友云微服务架构下配置文件管理利器:配置中心

    摘要:而且,用友云配置中心以服务的方式提供统一的管理界面,结合用友云的认证中心可以提供可靠的安全保障。 微服务架构是这几年IT领域的一个高频词汇,越来越多的项目和应用正在以微服务的思想进行重构。相比于单体应用和SOA架构,微服务优势也逐渐凸显,被广大架构师和技术人员引入和推崇。当然,单体应用、SOA、微服务等各有优势和不足。单体架构在早期的企业内部信息化或者搭建中小型项目时很常见,简单说就是...

    jayce 评论0 收藏0
  • Spring Boot 参考指南(通用的应用程序属性 ①)

    摘要:第章附录附录通用的应用程序属性可以在文件,文件,或作为命令行开关,中指定各种属性,本附录提供了一个通用的属性列表和对使用它们的底层类的引用。本示例文件仅作为指南,不要将整个内容复制粘贴到应用程序中,相反,只选择你需要的属性。 第X章. 附录 附录A. 通用的应用程序属性 可以在application.properties文件,application.yml文件,或作为命令行开关,中指定...

    ispring 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<