SpringCache
- 在启动类上开启注解 @EnableCaching
- 在需要的地方上加上 @Cacheable
- cacheNames = xxx 指定cache容器
- key = #xxx 指定缓存key,默认是参数的值
- less = xxx 过滤条件 遵循SpEL
- sync = true 开启异步缓存
- 等等
- @CachePut
- @CacheEvict
- 用springCache的便利之处在于,查询完数据库后,根据@Cache相关注解的配置,智能的新增、更新、删除缓存信息,不需要再额外编写缓存操作的代码
- 在并发量小的时候,缓存的更新删除可以随意操作,但是在高并发的情况下,尽可能的不要做缓存的更新删除,性能开销太大,并且可能会导致“热点”数据的丢失
- 咖啡因缓存管理器
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder().maximumSize(10)
.expireAfterWrite(10L, TimeUnit.SECONDS));
Cache c1 = caffeineCacheManager.getCache("C1");
Cache c2 = caffeineCacheManager.getCache("C2");
c1.put("k1", "v1");
c2.put("k2", "v2");
System.err.println(c1.get("k1").get());
System.err.println(c2.get("k2").get());
TimeUnit.SECONDS.sleep(8);
System.err.println(c1.get("k1").get());
System.err.println(c2.get("k2").get());
TimeUnit.SECONDS.sleep(3);
System.err.println(c1.get("k1").get());
System.err.println(c2.get("k2").get());
- spring的缓存管理器
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
Set<Cache> cacheSet = new HashSet<>();
cacheSet.add(new CaffeineCache("s1", Caffeine.newBuilder().expireAfterWrite(10L, TimeUnit.SECONDS).build()));
cacheSet.add(new CaffeineCache("s2", Caffeine.newBuilder().expireAfterWrite(10L, TimeUnit.SECONDS).build()));
simpleCacheManager.setCaches(cacheSet);
Cache s1 = simpleCacheManager.getCache("s1");
Cache s2 = simpleCacheManager.getCache("s2");
s1.put("k1", "v1");
s2.put("k2", "v2");
System.err.println(s1.get("k1").get());
System.err.println(s2.get("k2").get());
TimeUnit.SECONDS.sleep(8);
System.err.println(s1.get("k1").get());
System.err.println(s2.get("k2").get());
TimeUnit.SECONDS.sleep(3);
System.err.println(s1.get("k1").get());
System.err.println(s2.get("k2").get());
github地址&依赖
- github.com/ben-manes/c…
- For Java 11 or above, use 3.x otherwise use 2.x.
com.github.ben-manes.caffeine:caffeine:xxx
简介
- 对于现在的开发来讲,高并发属于一个核心话题,在这个话题里面有一个最为重要的技术项,那么就是缓存,通过缓存可以减少数据库数据的查询压力(按照正常的设计来讲,数据库在进行数据查询的时候是需要进行寻址的,会有严重的性能开销,可以过索引的技术来提升部分的性能,但是仅仅是一个提升),缓存是一个庞大的话题,并不是说你在整个的项目之中整一个内存或者是分布式的缓存就可以解决的问题。
- 由于计算机体系结构的设计问题,所有的程序都会在CPU之中进行运算,然而考虑到计算数据的完整性,所有的数据不会通过磁盘加载,而是会通过内存进行数据的缓存,最终才·会被加载到CPU之中,这样一来在整个项目的运行过程之中,如果磁盘IO的操作性能差,那么最终就会导致程序变慢
- 是spring框架默认的缓存组件
计算机运算过程
所有的程序中的数据都是通过内存获取到的,但是如果内存没有数据,就通过IO通道进行磁盘数据的加载,实际上就存在性能问题了,有两个性能问题:
- 磁盘通道的帯宽:帯宽越带,数据的加载速度就越快,但是带宽的设计是需要由IO接口来决定的,比如PCIe5.0
- 磁盘的速度问题:传统的机械硬盘是需要根据转速来解决数据的读取问题,因为需要进行磁盘的磁道寻址
- 只有IO发挥到了极致,才会极快
缓存的两种类型
- 单机缓存组件
- Ehcache组件:一个随 Hibernate框架同时推广的缓存组件,也是 Hibernate之中默认的缓存实现其属于一个纯粹的Java缓存框架,具有快速、简单等操作特点,同时支持有更多的缓存处理功能
- Google Guava:是一个非常方便易用的本地化缓存组件,基于LRU算法实现,支持多种缓存过期策略
- Caffeine:是对 Guava缓存组件的重写版本,虽然功能不如Ehcache多,但是其提供了最优的缓存命中率
- 分布式缓存
- Memcached
- Redis
Caffeine本地缓存组件的特点
- 可以自动将数据加载到缓存之中,也可以采用
异步的方式进行加载 - 当基于频率和最近访问的缓存达到最大容量时,该组件会自动切换到基于大小的模式
- 可以根据上一次缓存访问或上一次的数据写入来决定缓存的过期处理
- 当某一条缓存数据出现了过期访问后可以自动进行异步刷新
- 考虑到JVM内存的管理机制,所有的缓存KEY自动包含在弱引用之中, VALUE包含在弱引用或软引用中
- 当缓存数据被清理后,将会收到相应的通知信息
- 缓存数据的写入可以传播到外部存储
- 自动记录缓存数据被访问的次数
Caffeine提供的缓存操作
过期策略
缓存驱逐算法
FIFO 先进先出
LRU 最近最久未使用
LFU 最近最少使用
TinyLFU
W-TinyLFU
缓存数据结构源码分析
- LocalManualCache
- BoundedLocalManualCache
- BoundedLocalCache
- 数据保存使用的是 final ConcurrentHashMap<Object, Node<K,V>> data
- abstract class Node<K, V> implements AccessOrder<Node<K, V>>, WriteOrder<Node<K, V>> { }
- final class AccessOrderDeque<E extends AccessOrder> extends AbstractLinkedDeque { }
- abstract class AbstractLinkedDeque extends AbstractCollection implements LinkedDeque { }
- interface LinkedDeque extends Deque { }
- 因此node实现了Deque(双端队列)父接口
- Node抽象类的核心属性
public static final int WINDOW = 0;public static final int PROBATION = 1;public static final int PROTECTED = 2;