Mybatis源码4 Cache的实现和其原理
一丶Cache的实现类
- TransactionalCache 事务缓存,一次性存入多个缓存,移除多个缓存
- PerpetualCache 基于HashMap的缓存实现
- LoggingCache 提供打印命中率的功能
- SynchronizedCache 同步缓存,防止多线程问题
- LruCache LRU淘汰算法的缓存
- SoftCache 软引用缓存
- ScheduledCache 定时清除缓存
- WeakCache 弱引用缓存
- FifoCache 先进先出淘汰策略缓存
- BlockingCache 阻塞缓存
- SerializedCache 序列化缓存
二丶TransactionalCache
//缓存事务真正存储的位置
private Cache delegate;
//commit时要不要清缓存
private boolean clearOnCommit;
//commit时要添加的元素
private Map<Object, Object> entriesToAddOnCommit;
//未命中的缓存key
private Set<Object> entriesMissedInCache;
-
提供了暂存区
查询的数据暂时存放在entriesToAddOnCommit 而不是直接提交到二级缓存
- entriesToAddOnCommit暂存区避免了脏读
将要提交的数据暂存在暂存区(提交的时候将暂存区的数据存储到二级缓存,回滚则不影响),而不是立刻提交到二级缓存
假如允许马上提交到二级缓存,回话回滚的时候无法回滚二级缓存中的数据,将导致另外的查询读到回滚掉的数据
-
entriesMissedInCache 可以避免缓存穿透么
TransactionalCache 在提交的时候会把没有名字的key赋值对应null值,但是cachingExceutor查询的时候为null还是查询了数据库,如果为了防止缓存穿透,不应该赋值一个默认定义为空对象的值,然后查询的时候比较结果是否是空对象,如果是空返回空
防止缓存穿透也存在弊端,那么就是另外一个服务新增数据到数据库中,数据库有数据了但是二级缓存没有,会造成错误
三丶LoggingCache
private Cache delegate;
//请求缓存次数
protected int requests = 0;
//命中次数
protected int hits = 0;
如果命中了hits++
查询缓存的时候打印命中率
如果是分布式缓存,岂不是只能打印当前机器的命中率,而且重启就归0了,只能说少即是多
四丶SynchronizedCache
原理和Collections.synchronizedList()一样,利用synchronized保证线程安全,粒度很大
五丶LruCache
mybatis 实现LRU淘汰策略,是基于LinkedHashMap,构造LinkedHashMap的时候设置accessOrder为true重写其removeEldestEntry方法,在当前缓存大小大于指定缓存大小的时候返回true将会删除LinkedHashMap的头结点,从而删除最近最少使用缓存,且此为了删除真正缓存区中的数据,mybatis将要删除的key存储到自己的属性中,put的时候删除二级缓存中的数据
具体见 LinkedHashMap源码学习笔记
六丶SoftCache
//链表用来引用元素,防垃圾回收
private final Deque<Object> hardLinksToAvoidGarbageCollection;
//被垃圾回收的引用队列
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
//被装饰者
private final Cache delegate;
//强引用数量 配合 hardLinksToAvoidGarbageCollection 使用,指定数量的缓存将不被gc回收
private int numberOfHardLinks;
mybatis 使用 hardLinksToAvoidGarbageCollection 指向缓存对象从而保证缓存数据不被gc回收
使用queueOfGarbageCollectedEntries 在缓存对象被gc回收,将二级缓存的数据进行清除
-
如何使用hardLinksToAvoidGarbageCollection 防止被回收
-
如何使用queueOfGarbageCollectedEntries 在缓存对象被gc回收,将二级缓存的数据进行清除
目标:gc回收缓存缓存中的数据的时候,我们可以删除二级缓存中的数据
- 我们要存储的数据应该是被软引用包装的
- 我们存储的数据被回收后任可以拿到原来数据对应的key,才可以去删除二级缓存中的数据
- 缓存数据要存在缓存队列中,这样才可以知道哪些被回收了
加入我们使用的是PerpetualCache 作为二级缓存 PerpetualCache是基于hashmap这个时候hashMap存储了缓存内容那么SoftCache 还有效么 有效,从GC root(可达性分析) 加入缓存数据没有被方法栈Reference(没有任何变量执行缓存的值)那么缓存value的指向是 hashMap----》SoftEntry(mybatis定义的继承了 SoftReference )----》缓存值 对应缓存值来说只有SoftReference 指向它,gc自然能回收咯
七丶WeakCache
弱引用缓存,可以看到代码和SoftCache如出一辙,就是SoftReference变成了WeakReference
八丶ScheduledCache
第一反应是不是mybatis启动了另外一个线程啊,每隔一段时间清理,结果
private Cache delegate;
//清理间隔
protected long clearInterval;
//上一次清理时间
protected long lastClear;
mybatis的定时清理是每隔一段时间进行清理,依赖于用户要使用到二级缓存,也对,不用二级缓存 那么怎么可能存在数据过多oom呢 (啥叫少即是多啊 战术后仰)
九丶FifoCache
这个类就是维护一个FIFO队列,其他都委托给所包装的cache去做。
private final Cache delegate;
private Deque<Object> keyList;
private int size;
十丶SerializedCache
提供缓存的序列化,可以将缓存的数据转化二进制存入已经从二进制转化为对象,默认是使用SerializedCache装饰的(readWrite默认是true,为true那么将使用SerializedCache再次装饰)
````
为什么要使用SerializedCache装饰,其实根据 readWrite 的名字就可以知道,readWrite可以让"读写分离",即读取对象改变对象的属性不会影响到二级缓存,用过是反序列化生成的对象,深拷贝是不同的内存区域。
````
十一丶BlockingCache
为了达到阻塞的目的(当前线程没从缓存获取到key对应的值,那么后续此key对应的缓存将无法被操作)
//锁超时时长
private long timeout;
//被装饰者
private final Cache delegate;
//key:缓存的key value :该缓存对应的锁
private final ConcurrentHashMap<Object, ReentrantLock> locks;
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
//缓存的key没对应的锁 那么就new 一个锁
ReentrantLock previous = locks.putIfAbsent(key, lock);
return previous == null ? lock : previous;
}
private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
//指定时间 企图拿到锁 如果 拿不到抛出异常
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
//上锁
lock.lock();
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
//锁被当前线程持有 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}