背景
项目部署重新启动时,用户访问系统出现鉴权超时,严重影响运营效率(ps:受到了客户diss)。主要原因是项目重新启动获取权限配置时间较长。权限配置需要查询公司的鉴权中心,本地获取到后进行解析并存入本地缓存(caffeine cache)。因此需要对权限缓存进行优化,最简单的解决方式是直接加一个redis缓存,但在gitee上有更好的二级缓存解决方案——J2Cache。借此机会学习下这个二级缓存框架。
1、简述
1.1、概述
J2Cache 是 OSChina 目前正在使用的两级缓存框架(要求至少 Java 8)。第一级缓存使用内存(同时支持 Ehcache 2.x、Ehcache 3.x 和 Caffeine),第二级缓存使用 Redis(推荐)/Memcached 。 由于大量的缓存读取会导致 L2 的网络成为整个系统的瓶颈,因此 L1 的目标是降低对 L2 的读取次数。 该缓存框架主要用于集群环境中。单机也可使用,用于避免应用重启导致的缓存冷启动后对后端业务的冲击。详细介绍请看J2Cache官网内容
1.2、设计思路
将Ehcache和Redis结合起来,将Ehcache(也可以使用caffeine cache)作为一级缓存、将redis(也可以使用memcached )作为二级缓存,取长补短。尽量从本机取数据,取不到的时候再去redis里面取。这样结合可以保证高性能。数据基本上都是从Ehcache里面取得,有效的缓解应用冷启动对数据库的压力,应用和redis之间不会频繁的有大量数据传输。数据传输只存在应用冷启动及缓存变更时。
J2Cache 目前提供两种节点间数据同步的方案 —— Redis Pub/Sub 和 JGroups 。当某个节点的缓存数据需要更新时,J2Cache 会通过 Redis 的消息订阅机制或者是 JGroups 的组播来通知集群内其他节点。当其他节点收到缓存数据更新的通知时,它会清掉自己内存里的数据,然后重新从 Redis 中读取最新数据。这就完成了 J2Cache 缓存数据读写的闭环。
那么为什么不用 Ehcache 的集群方案?红薯大佬已经回答了:
对 Ehcache 比较熟悉的人还会问的就是这个问题,Ehcache 本身是提供集群模式的,可以在多个节点同步缓存数据。但是 Ehcache 的做法是将整个缓存数据在节点间进行传输。如咱们前面的说的,一个页面需要读取 50K 的缓存数据,当这 50K 的缓存数据有更新时,那么需要在几个节点间传递整个 50K 的数据。这也会造成应用节点间大量的数据传输,这个情况完全不可控。
补充:当然这个单个数据传输量本身并不比使用 J2Cache 多,但是 ehcache 利用 jgroups 来同步数据的做法,在实际测试过程中发现可靠性还是略低,而且 jgroups 的同步数据在云主机上无法使用。而 J2Cache 传输的仅仅是缓存的 key 而已,因此相比 Ehcache 的集群模式,J2Cache 要传输的数据极其小,对节点间的数据通信完全不会产生大的影响。
1.3、主要组成部分介绍
组成部分 介绍 Cache 缓存数据操作接口,有两个子类为【Level1Cache、Level2Cache】,各缓存分别实现Level1Cache、Level2Cache接口,具体如图 ClusterPolicy 缓存集群策略接口,具体实现类如下 CacheProviderHolder 两级缓存管理器。初始化L1、L2缓存以及缓存过期处理Listener CacheChannel 封装J2cahce缓存操作,对用户提供标准的缓存操作方法。如:get、exists、check、set、evict、clear等。 CacheProviderHolder 两级缓存管理器。初始化L1、L2缓存以及缓存过期处理Listener SpringRedisMessageListener J2Cache集成spring boot特有,监听redis缓存更新通知。 SpringRedisActiveMessageListener J2Cache集成spring boot特有,监听二级缓存key失效,主动清除本地缓存。 J2CacheCacheManger J2Cache集成spring boot特有。J2Cache集成SpringCache,可以使用SpringCache注解进行数据操作。2、原理解析
由于项目使用的是SpringBoot,因此此处解析的是J2Cahce与SpringBoot相关内容。 项目中J2Cache的配置文件如下:
2.1、Spring容器注册CacheChannel
配置文件使用【@Configuration】注释作为注册CacheChanne Bean入口。
- 注册主流程
- 读取配置
- 构建CacheChannel
- 配置CacheChannel 如果缓存过期Listener能够支持扩展用户自定义逻辑更好。
- 初始化集群消息通知机制
2.2、数据操作原理
2.2.1、存储缓存数据
代码中直接使用set方法即可存储数据,具体操作及redis数据存储结果如下:
redis中的数据
- 设置数据
- 命令发送 数据删除命令跟set数据方法在同一个类中。
数据删除、清空命令实现在创建CacheChannel中实现,命令发送具体内容在每个策略类中实现
- 具体信息发送
redis消息队列
- redis消息清除Listener
- 清除本地一级缓存
至此,设置缓存数据流程已经完成,对于清空、删除数据操作命令通知也与上相同 。
2.2.2、读取数据缓存
获取数据直接使用get方法即可,具体操如下:
- CacheChannel中get方法
需要关注的是,J2Cache 提供了一个可以传入数据加载器loader的方法。我们自定义取数逻辑,当一二级缓存没有数据时,采用此方法获取数据。
2.2.3、清空缓存数据
需要注意的是清除数据是将某个区域内所有的缓存数据清除掉,清空命令逻辑与设置数据删除命令的逻辑相同,详细可以看上面的设置数据
2.2.4、删除缓存数据
2.2.5、缓存key是否存在
exists与check是两个方法,我们可以查询是否存在,可以以单独调用查看缓存key在第几层级。
至此,缓存数据操作逻辑到此已经结束。
2.3、J2cache集成Spring Cache
J2cache集成了SpringCache,我们可以通过Spring cache的注释使用J2cahce。 项目中的配置需要增加【open-spring-cache】配置
J2cache源码中根据【open-spring-cache】配置选择性注册CacheManager,如下图
开发代码 #EE3F4D==中使用【@Cacheable】注释,这里要注意key的拼接方法。
执行日志如下图
3、使用demo
- 引入jar包
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-spring-boot2-starter</artifactId>
<version>2.8.0-release</version>
</dependency>
<dependency>
<groupId>net.oschina.j2cache</groupId>
<artifactId>j2cache-core</artifactId>
<version>2.8.0-release</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
- 添加J2Cache配置
- 代码编写 CacheChannel使用【@Autowired】注释自动注入
4、总结
项目中使用J2Cache还是非常方便的,省去了自己去开发Caffeine Cache+Redis功能,而且最重要的是分布式项目本地缓存的一致性得到解决,大大提高开发效率。而且J2Cache与SpringCache注释结合大大减少了代码的侵入,非常推荐使用。