J2Cache源码阅读(二)---CacheChannel
J2Cache二级缓存框架源码阅读
因为最近在看Redis的设计与实现,想写一个用java实现的缓存框架,于是就找了一个开源的J2Cache框架源码来学习,这里是记录的第一篇文章。
一.CacheObject
CacheObject,是面向用户的一个缓存对象,所有的缓存返回值,都是封装成CacheObject,先来看一下它的成员变量,应该非常容易理解。
public class CacheObject {
public final static byte LEVEL_1 = 1; //一级缓存数据
public final static byte LEVEL_2 = 2; //二级缓存数据
public final static byte LEVEL_OUTER = 3; //外部数据
// 获取数据所在的缓存区域
private String region;
// 缓存数据键值
private String key;
// 返回实际缓存的对象
private Object value;
// 缓存所在的层级
private byte level;
...
}
二.CacheChannel
CacheChannel,是所有的缓存操作提供者,是一个抽象类。
先来看它的成员变量。
- _g_keyLocks : 在CacheChannel使用CHM集合来实现,作用是为了并发控制,防止在多线程的情况下,多个线程同时来获取数据,在一级缓存或者二级缓存失效的情况下,发生缓存穿透,它可以在最先进入的线程前加入以region%key命名的key-v键值对,在后面的线程访问时发现存在该key,则被阻塞,当最先进入的线程访问数据并写入缓存后,所有数据直接读取缓存。
- config : J2Cache的配置文件。
- holder : J2Cache的缓存管理器,可以获取一级缓存管理器和二级缓存管理器。
- defaultCacheNullObject : 是否开启缓存空对象,它会在构造方法初始化时,根据config配置的来决定是否开启缓存空对象。
- closed : 管道是否关闭。
private static final Map<String, Object> _g_keyLocks = new ConcurrentHashMap<>();
private J2CacheConfig config;
private CacheProviderHolder holder;
private boolean defaultCacheNullObject ;
private boolean closed;
再来看它的构造方法和通用方法。
构造方法进行初始化的赋值,没有什么特殊点。
另外两个方法,newNullObject返回一个空对象,用于缓存空对象时使用,assertNotClose检查管道是否关闭,在所有方法执行前都会调用。
public CacheChannel(J2CacheConfig config, CacheProviderHolder holder) {
this.config = config;
this.holder = holder;
this.defaultCacheNullObject = config.isDefaultCacheNullObject();
this.closed = false;
}
private NullObject newNullObject() {
return new NullObject();
}
private void assertNotClose() {
if(closed)
throw new IllegalStateException("CacheChannel closed");
}
还有两个抽象方法,主要作用是为了广播,在集群模式中使用。
protected abstract void sendClearCmd(String region);
protected abstract void sendEvictCmd(String region, String...keys);
剩下就是它执行缓存的crud操作了,先来看它是如何获取缓存的,看它的读取缓存的get方法。
使用时需要传入它的region区域,key名称,还有可变参数的是否缓存空对象boolean数组。
这里我也不知道为什么要传入可变参数,感觉一个boolean值就可以用了,希望了解的兄弟可以留个言告诉我一下,谢谢啦。
方法的具体流程都在注释上写了,首先会检查管道是否关闭,关闭则直接抛出异常,如果没有则先new出要返回的CacheObject缓存对象。
首先先从一级缓存尝试获取数据,当一级缓存没有获取到,则在_g_keyLocks中加锁,主要是为了并发控制,防止缓存穿透。
然后尝试从二级缓存中获取数据,当二级缓存也没有获取到时,会根据是否缓存空对象来决定是否需要缓存一个空对象在一二级缓存中。
/**
* 读取缓存(用户无需判断返回的对象是否为空)
* @param region Cache region name
* @param key Cache data key
* @param cacheNullObject 是否缓存空对象
* @return cache object
*/
public CacheObject get(String region, String key, boolean...cacheNullObject) {
// 判断管道是否关闭
this.assertNotClose();
// 创建要返回的缓存对象
CacheObject obj = new CacheObject(region, key, CacheObject.LEVEL_1);
// 从一级缓存中对应的region获取对应key
obj.setValue(holder.getLevel1Cache(region).get(key));
// 一级缓存存在则直接返回
if(obj.rawValue() != null)
return obj;
/**
* 这里进行加锁 是为了防止并发问题,保证在一级缓存没有读取到时,
* 其他线程阻塞,在当前线程走完流程并成功赋值一级缓存后,
* 删除锁,然后其他线程读取一级缓存
*/
String lock_key = key + '%' + region;
synchronized (_g_keyLocks.computeIfAbsent(lock_key, v -> new Object())) {
// 再一次获取
obj.setValue(holder.getLevel1Cache(region).get(key));
if(obj.rawValue() != null)
return obj;
// 尝试获取二级缓存
try {
obj.setLevel(CacheObject.LEVEL_2);
obj.setValue(holder.getLevel2Cache(region).get(key));
if (obj.rawValue() != null) {
// 获取到二级缓存,则赋值给一级缓存
holder.getLevel1Cache(region).put(key, obj.rawValue());
}else {
// 仍然没有获取到二级缓存,判断是否需要缓存空对象【防止缓存穿透】,根据传参决定
boolean cacheNull = (cacheNullObject.length > 0) ? cacheNullObject[0] : defaultCacheNullObject;
if (cacheNull)
set(region, key, newNullObject(), true);
}
} finally {
// 删除锁
_g_keyLocks.remove(lock_key);
}
}
// 返回缓存对象
return obj;
}
其他的操作根据上面的流程也是差不多的。
总结
这个框架主要是基于已经实现的缓存框架和缓存中间件进行封装整合形成二级缓存框架,大多数缓存操作还是应该看一下诸如ehcache等缓存框架,redis等缓存中间件,明确了学习目标,就继续学习吧。
推荐阅读