序言
本文,我想详细地讲讲缓存的使用。我们都知道,根据现代计算机存储介质的不同,我们引入了Cache 这个概念, Cache 在计算机芯片, 各级内存,硬盘,乃至于各种软件设计中都是非常常见的,Cache 使用的好,能够合理分层,我们能解决百分之八十以上的性能问题,由于目前大部分的
互联网服务应用都是 重io为主的服务,所以 网络服务的质量和速度 跟缓存的使用有着密不可分的关系。这节会结合 各种场景下 缓存的使用,推荐一些最佳的使用场景,结合JetCache 达到一种比较边界的提升接口性能的代码编写方式。
JetCache 的优势:
https://blog.csdn.net/dongjijiaoxiangqu/article/details/109643337
正文
Cache 作为 热点数据的备份, 可以分为进程内缓存,和分布式缓存。 我们常见用的 redis,Memcached 是 分布式缓存, 一般这种缓存的好处是有集中式管理的地方,而且分布式部署缓存能够同步。而进程内缓存,因为每台机器的应用进程是不同的,而一般应用部署会采用多台机器部署,所以进程内缓存 就没有多台机器同步的功能。
而 Cache 的主要关注元素有:
- 命中率
- Cache 容量
- 达到Cache 最大容量后的数据淘汰策略, 常见淘汰策略有 LRU,FIFO,LFU, Sketch-Min 算法等
而常见互联网应用中的Cache 可能是直接用 redis了,通过使用 redis 客户端,异步客户端有 lettuce, 同步客户端较老的有 jedis ,本身来说 redis 有着足够强劲的性能,能够配置主从,和集群的方式,来提高Cache 系统的性能, 那么有没有提高的方式呢,
是有的,在Redis (或其他 远程分布式缓存)的基础上,我们考虑再加上一层进程内级别的缓存。 类似于现代计算机的缓存构成
-进程级别Cache
-- 远程分布式级别 Cache
读取数据如果能从 进程级别内找到数据,就不用走一次网络开销去查找远程Cache 数据,那么势必能够系统的 接口性能带来一定量的提升。
那么我们介绍多级 Cache的使用, JetCache 也是实现了多级缓存的功能,由于进程级别直接的Cache 数据不能共享,但是应用部署多台服务器,我们希望 进程级别的Cache 数据,当某一 Cache Item 被删除时候,其他服务器的进程级别Cache 相同Item 也能被删除,尽量减少脏缓存数据的产生,尽量进程级别的缓存数据也能够同步删除。
JetCache 的基本依赖
- 首先是如何引进 依赖,以最常见的 springboot 方式依赖来引入,其他 spring 引入,在此不表。 然后需要声明 @EnableMethodCache(basePackages = "com.company.mypackage") @EnableCreateCacheAnnotation 表明支持 JetCache 的注解使用
|
如果是 api 接口包,不想引入太多依赖,可以只引入 jetcache-anno-api 包,可以直接使用注解, 但是注解生效还得把 redis 客户端依赖, 和 jetcache-core 依赖引入
JetCache 常见的配置设置
|
属性 | 默认值 | 说明 |
jetcache.statIntervalMinutes | 0 | 间隔时间,0表示不统计 |
jetcache.areaInCacheName | true | jetcache-anno把cacheName作为远程缓存key前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。2.4.4以后可以配置,为了保持远程key兼容默认值为true,但是新项目的话false更合理些。 |
jetcache.hiddenPackages | 无 | @Cached和@CreateCache自动生成name的时候,为了不让name太长,hiddenPackages指定的包名前缀被截掉 |
jetcache.[local|remote].${area}.type | 无 | 缓存类型,tair、redis为当前支持的远程缓存,linkedhashmap、caffeine为当前支持的本地缓存类型 |
jetcache.[local|remote].${area}.keyConvertor | 无 | key转换器的全局配置,当前只有一个已经实现的keyConvertor:fastjson。仅当使用@CreateCache且缓存类型为LOCAL是可以指定为none,此时通过equals方法来识别key。方法缓存必须指定keyConvertor |
jetcache.[local|remote].${area}.valueEncoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.valueDecoder | java | 序列化器的全局配置。仅remote类型的缓存需要指定,可选java和kryo |
jetcache.[local|remote].${area}.limit | 100 | 每个缓存实例的最大元素的全局配置,仅local类型的缓存需要指定。注意是每个缓存实例的限制,而不是全部 |
jetcache.[local|remote].${area}.expireAfterWriteInMillis | 无穷大 | 以毫秒为单位指定超时时间的全局配置,以前为defaultExpireInMillis |
jetcache.local.${area}.expireAfterAccessInMillis | 0 | 需要jetcache2.2以上,以毫秒为单位,指定多长时间没有访问,就让缓存失效,当前只有本地缓存支持。0表示不使用这个功能。 |
jetcache.local.${area}.expireAfterAccessInMillis | 里面的area 可以设置不同的redis 链接 和 不同的redis 数据库, @CreateCache 里面没有声明 area 的,默认使用default area |
这里值得一说的是, Cache 的type ,其中 redis 和 redis.lettuce的差距,这里推荐使用 redis.lettuce的方式来配置, lettuce 使用基于nio的 redis客户端,支持异步和reactive 方式来调用redis命令。
Cache 的声明式的使用
生成Cache ,手动调用cache 方法,配置完成以后, 推荐使用方式,用注解的方式来 声明Cache ,因为通过注解声明出来的 Cache 会走默认的生成Cache 策略,会把全局配置给加上, 并且有数据监控,
采用多级缓存情况下,一级缓存(local)删除会有同步通知,并且删除其他机器的一级缓存进行同步删除动作,这是利用 Monitor 监听 删除事件,然后 从 Redis 发布远程命令,订阅者接受命令,删除进程缓存value来实现的。(也就是 redis PubSub 发布订阅模式,来做到多台服务器 进程缓存同步更新的策略)。
例子:
|
可以通过 CreateCache 的方式创建 cache,如果需要手动设置一些没有提到的 Config, 可以通过上文方式, 通过调用 Cache.config() 来设置一些属性。
手动设置 config 可以设置, Loader, 监听 缓存数据操作的事件(包括放置,删除等)Monitors,RefreshPolicy(刷新策略),cachePenetrationProtect(缓存穿透保护等)
创建 Cache 的声明 @CreateCache ,我们经常配置了全局配置参数(也就是前面提到的 properties 或者 yml 方式来配置),这时候,我们使用 @CreateCache(name=xxx, cacheType=CacheType.BOTH), 通常来说这两个参数就够了,其他没设置的注解参数会默认继承全局配置。
cacheType 有 local, remote, both(两级缓存),意思也是很容易理解的,对应CacheType 为 CacheType.LOCAL, CacheType.REMOTE, cacheType = CacheType.BOTH, 如果不写CacheType ,默认走的是 REMOTE 方式。
这种方式适用于手工创建cache 的过程,当然也可以通过 CacheBuilder 方式来创建,但是推荐上述注解方式来创建Cache,这样可以加入统一aop创建,加入数据监控 log和多级缓存的一级缓存同步更新。
通过接口可以看出,其支持同步和异步的方式取值
|
由于JetCache 基于 lettuce 可以实现异步取值,异步放置值的调用,这有时候对于我们不关心放置缓存 的同步时候,可以采用异步调用的方式来减少同步调用带来时间开销,当我们不关注返回值或者是想要用异步的方式来放置缓存时候,这类方法都是 大写开头的方法,返回了CacheGetResult 结果,然后采用异步方式可以获取结果,或者不关注结果
例子:
|
CacheGetResult 取值方式可以以更清楚方式获取值的状态,不仅仅是一个值
|
也可以支持 Spring Annotation 方式进行 方法的声明式缓存 , 也支持 spring EL 表达式
例子:
|
上文代码中的意图比较明显,这里值得一提的是,更新操作,可以直接把 Cache的值 Invaildate ,然后让其取数据库加载。如要防止缓存穿透,可以在取值过程中加上 @CachePenetrationProtect 注解
其中 @CreateCache和 @Cached 注解都可以 声明是否是多级 Cache, 通过 cacheType = CacheType.BOTH ( CacheType.REMOTE, CacheType.LOCAL)来声明,value过期时间等其他配置如果不设置都会设置为
配置文件里面的默认值。
如果需要定期更新 Cache值(当然这种情况,我觉得是很少会有这种场景的,也可以用 refresh loader注解) @CacheRefresh ,会定期去取 loader 中的数据
例子
|
分布式锁的简单支持
通过 实现一个较为简单的分布式锁,指的是 key not exist 并加上特有的 key 和其超时时间,不会解锁其他线程的key ,和自己目前实现的不复杂分布式锁是基本一致的。如果需要更高级严谨的分布式锁,可以考虑 redission 的 redlock 。 MutliCache 会默认使用分布式锁。
|
上文的意图也很明显了,在此不表。
在此 JetCache 的简单使用 基本是介绍完了。
结尾
本文意在介绍 JetCache 的 常规和推荐用法, 通过声明式的注解,就能为接口赋予不俗的 Cache 性能