扩展spring cache 支持缓存多租户及其自动过期
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<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 的扩展即可实现对缓存 一些透明操作
- cachemanager 是springcache 对外提供的API 扩展入口
- 以上源码参考个人项目 基于Spring Cloud、OAuth2.0开发基于Vue前后分离的开发平台
- QQ: 2270033969 一起来聊聊你们是咋用 spring cloud 的吧。
- 欢迎关注我们的公众号获得更多的好玩JavaEE 实践
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Kafka连接器深度解读之转换器和序列化
Kafka连接器是Apache Kafka®的一部分,提供数据存储与Kafka之间的流式集成。对于数据工程师来说,只需要使用JSON格式配置文件即可。目前已经有很多数据存储的连接器,仅举几例来说,包括JDBC,Elasticsearch,IBM MQ,S3和BigQuery。 对于开发者,Kafka连接器有丰富的API,如有必要,可以开发自己的连接器。此外它还具有用于配置和管理连接器的REST API。 Kafka连接器本身是模块化的,提供了非常强大的满足集成需求的方法,部分关键组件包括: 连接器:定义了一组如何与数据存储集成的JAR文件; 转换器:处理数据的序列化和反序列化; 变换:传输过程中的消息处理(可选)。 围绕Kafka连接器,常见的错误或者误解之一是数据的序列化,这是Kafka连接器通过转换器进行处理的,下面会介绍它们的工作机制,并说明一些常见问题如何处理。 Kafka消息只是字节 Kafka消息是按照主题进行组织的。每条消息都是一个键/值对,不过Kafka就需要这些。当数据在Kafka中存储时都只是字节,这使得Kafka可以适用于各种场景,但这也意味着开发者有责任决定如何...
- 下一篇
复盘一次生产问题
有整整 10 天木有更文了,这段时间确实比较忙。 有加我微信的朋友知道我上周末出去春游了,部门组织去了趟外伶仃岛,环境挺不错的,这段时间去的人也比较少,值得去玩。 今天讲讲上周末一次生产问题的复盘。 1 事情经过 周日中午从外伶仃岛回来就直奔公司,因为生产出了些问题。问题是这样的:HBase 的一些节点挂了,导致一些数据丢失。丢失数据的客户来授信或者借款,都会卡件。在确定数据短时间没法恢复时,就决定从系统的层面去解决这个问题。这时我咨询了 2 位老员工,这些数据虽然是规则的入参数据,但是规则可能没用这些数据去做决策,能否先跟规则的同事确认这些数据是否有使用,如果没有,就可以先暂停这些数据的获取,减少影响面,再来细致的分析数据。得到的回复都是这些数据很早前就上线了,肯定有在用。这时只能分析系统数据,恰巧丢失的数据是原始数据,不是加工数据,原始数据不做规则入参,所以就简单的修改了获取数据源的代码。 在测试同事进行简单回归测试时,发现了一个奇怪的现象,旧数据被覆盖,检查了各种 SQL 配置,没有发现问题,因为以前也有很多模型和规则入参都是这样配置的,接着就陷入历史问题的 debug 中,还是...
相关文章
文章评论
共有0条评论来说两句吧...