关键词:Java、单机缓存、事件驱动
大家好,我是入错行的bug猫。(http://blog.csdn.net/qq_41399429,谢绝转载)
有时候我们自己写一个小项目陶冶一下情操,在需要用到缓存的时候,就有些纠结了,基本缓存都是使用第三服务,或者一个很大的Jar包。也就是一个小单机项目,我们也得部署一套缓存应用,引用一些Jar包…
既然没有,那么我们自己动手撸一个,娱乐一下~
说到缓存,一般采用ConcurrentHashMap
,put
get
remove
操作,对应缓存的存入、获取、移除。
然后Map的值,使用一个类,将数据包裹起来,包裹类里面添加存活时间属性。
在执行get
操作时,判断存活时间是否超时:超时移除,返回空;否则返回数据;
最后用另外一个线程,定时遍历Map检查失效数据、并移除。
但是!
在多线程的情况下,非常容易出现问题!!!
ConcurrentHashMap
虽然是操作线程安全,但是在组合的情况下,不是线程安全的!!
比喻:检查缓存中是否存在计数器A,如果存在,就加1,再存入缓存;否则创建一个存入缓存;
在多线程情况下,判断存在和存入缓存,是分为2个操作;
在初始情况下,甲、乙、丙同时检查缓存,都没有计数器A;然后甲、乙、丙分别创建了计数器,最终计数器结果为1,而不是理论值3!
加锁可以避免这种情况,但是变成了串行执行,效率会降低很多。
这个时候就要祭出事件驱动模型
了~
Redis、NodeJs、Javascript都是单线程,但是它们执行效率非常高,原因就在于事件驱动
通俗来讲:有事@我,没事我就闲着;多个事一起来了,排好队,一个一个来,one by one;
这样始终只有一个人(线程)在工作,不存在多个人(线程)协同工作互相扯皮。
把对缓存的所有操作,抽象为一个个事件
。
线程需要执行缓存操作,就往队列放入一个事件
,然后线程在事件结果返回之前,一直阻塞等待;
多线程情况下,仍然是往队列放事件
。保证队列是线程安全就行;
再看缓存工作者(缓存线程):
当队列里面有东西(事件
)的时候,缓存线程从队列取出事件
,然后根据事件类型,执行对应操作。
当结果操作完缓存得到结果后,再唤醒对应的线程,把结果传给线程,线程获取结果放行。缓存线程再重复从队列里取事件
;
队列事件取完之后,再检查一下距离上次定时清理失效数据
的事件是否超过一个阀值:如果是,就执行遍历ConcurrentHashMap
,清理失效数据
。或者是继续等待队列出东西。
如果有组合操作,保证组合操作的事件
,存入队列时是连续的就行。(或者使用责任链模式:当前事件
中,包含下一个事件
,缓存线程在执行完该事件
后,再检查一下是否包含下一个事件
,形成一个事件链
)
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 单机缓存管理
* @author bugcat
* */
public class InnerCache {
/**
* 1 秒
*/
public final static int sec = 1000;
/**
* 1 分钟
*/
public final static int min = 60000;
/**
* 1 小时
*/
public final static int hour = 3600000;
/**
* 1 天
*/
public final static int day = 86400000;
//所有操作组成的阻塞队列
private BlockingQueue<ExecuteEntry> queue = new LinkedBlockingQueue();
//缓存
private ConcurrentHashMap<String, Wrapper> cacheMap = new ConcurrentHashMap<>(16000);
private boolean run = true;
//单例
private static InnerCache cache = new InnerCache();
public static InnerCache getManager() {
return cache;
}
private InnerCache() {
//缓存线程
Thread worker = new Worker(this);
worker.start();
}
/**
* 包裹器,把数据对象抽象成统一的缓存节点。从缓存中存入、获取的原始数据
* */
private static class Wrapper {
private Object data; //缓存数据
private long keepAlive; //保留截止时间
public Wrapper() {}
public Wrapper(long activeTime) {
this.setActiveTime(activeTime);
}
public Wrapper(long activeTime, Object data) {
this.data = data;
this.setActiveTime(activeTime);
}
//设置保留时限 毫秒
private void setActiveTime(long activeTime) {
this.keepAlive = System.currentTimeMillis() + activeTime;
}
}
/**
* 从缓存中获取
* */
public <T> T get(String key, Class<T> clazz) {
ExecuteEntry entry = ExecuteEntry.query(key); //查询事件
queue.offer(entry); //存入队列
Wrapper wrapper = entry.getResult(); //阻塞等待结果
return (T) wrapper.data;
}
/**
* 从缓存中获取,并且马上刷新缓存时间
* */
public <T> T getAndRefresh(String key, long keepAlive, Class<T> clazz) {
ExecuteEntry entry = ExecuteEntry.refresh(key, keepAlive); //刷新缓存事件
queue.offer(entry);
Wrapper wrapper = entry.getResult();
return (T) wrapper.data;
}
/**
* 设置缓存
* */
public void set(String key, long keepAlive, Object object) {
ExecuteEntry entry = ExecuteEntry.insert(key, keepAlive, object); //设置缓存事件
queue.offer(entry);
entry.getResult();
}
/**
* 如果不存在就设置缓存,否则忽略
* */
public void setIfAbsent(String key, long keepAlive, Object object){
ExecuteEntry query = ExecuteEntry.query(key); //这个是组合操作:查询 + 存入,如果有其他的组合操作,可以按照这个模式写
query.next = queryResult -> { //queryResult 查询事件返回的结果 = Wrapper
query.nextEntry = null; //下一个事件对象
if( queryResult.data == null ){ //如果缓存中数据为null,表示需要把object存入缓存
query.nextEntry = ExecuteEntry.insert(key, keepAlive, object);
// query.nextEntry.next = xxx -> {} 事件链
}
return query.nextEntry; //返回下一个事件对象
};
queue.offer(query);
query.getResult();
}
/**
* 删除缓存
* */
public void del(String key) {
ExecuteEntry entry = ExecuteEntry.remove(key); //移除缓存事件
queue.offer(entry);
entry.getResult();
}
/**
* 事件
* */
private static abstract class ExecuteEntry {
//接收缓存线程处理的结果
private BlockingQueue<Wrapper> callback = new ArrayBlockingQueue<>(1);
//缓存key
protected String key;
protected Wrapper wrapper;
//创建下一个事件对象
protected Function<Wrapper, ExecuteEntry> next;
//下一个事件对象,由next创建赋值
protected ExecuteEntry nextEntry;
public static ExecuteEntry query(String key){
ExecuteEntry entry = new QueryExecuteEntry();
entry.key = key;
return entry;
}
public static ExecuteEntry refresh(String key, long keepAlive){
ExecuteEntry entry = new RefreshExecuteEntry();
entry.key = key;
entry.wrapper = new Wrapper(keepAlive);
return entry;
}
public static ExecuteEntry insert(String key, long keepAlive, Object object){
ExecuteEntry entry = new InsertKeepExecuteEntry();
entry.key = key;
entry.wrapper = new Wrapper(keepAlive, object);
return entry;
}
public static ExecuteEntry remove(String key){
ExecuteEntry entry = new RemoveExecuteEntry();
entry.key = key;
return entry;
}
/**
* 阻塞等待结果,结果一定不为null
* */
public Wrapper getResult(){
Wrapper wrapper = null;
//如果有下一个事件对象,先取下一个事件对象
if( nextEntry != null ){
nextEntry.getResult();
}
//阻塞等待结果
try { wrapper = callback.take(); } catch ( InterruptedException e ) { }
if( wrapper == null ) {
wrapper = new Wrapper();
}
return wrapper;
}
/**
* 阻塞等待结果,结果可能为null
* */
public <T> T getData(Class clazz){
Wrapper wrapper = getResult();
return (T) wrapper.data;
}
/**
* 缓存线程回调方法
* */
public Wrapper callback(Map<String, Wrapper> cacheMap){
Wrapper result = execute(cacheMap); //执行指定事件的具体方法,此时仍然属于缓存线程作用范围,单线程
callback.offer(result); //将结果通知给线程,结束线程阻塞
return result;
}
/**
* 事件具体操作
* */
protected abstract Wrapper execute(Map<String, Wrapper> cacheMap);
}
private static class QueryExecuteEntry extends ExecuteEntry {
@Override
public Wrapper execute(Map<String, Wrapper> cacheMap) {
Wrapper wrapper = cacheMap.get(key);
if( wrapper == null ){
wrapper = new Wrapper();
}
if( wrapper.keepAlive < System.currentTimeMillis() ){ //存活时间小于当前时间
cacheMap.remove(key);
}
return wrapper;
}
}
private static class RefreshExecuteEntry extends ExecuteEntry {
@Override
public Wrapper execute(Map<String, Wrapper> cacheMap) {
Wrapper wrapper = cacheMap.get(key);
if( wrapper == null ){
wrapper = new Wrapper();
}
if( wrapper.keepAlive < System.currentTimeMillis() ){ //存活时间小于当前时间
cacheMap.remove(key);
}
wrapper.keepAlive = super.wrapper.keepAlive;
return wrapper;
}
}
private static class InsertKeepExecuteEntry extends ExecuteEntry {
@Override
public Wrapper execute(Map<String, Wrapper> cacheMap) {
cacheMap.put(key, wrapper);
return wrapper;
}
}
private static class RemoveExecuteEntry extends ExecuteEntry {
@Override
public Wrapper execute(Map<String, Wrapper> cacheMap) {
cacheMap.remove(key);
return new Wrapper();
}
}
/**
* 缓存线程
* */
private static class Worker extends Thread {
private InnerCache cache;
public Worker(InnerCache cache) {
this.cache = cache;
}
@Override
public void run() {
long now = 0L;
long rolling = 3 * min; //每隔rolling时间后,检查移除失效数据
long last = System.currentTimeMillis();
while ( cache.run ) {
try {
//从队列中取事件
ExecuteEntry entry = cache.queue.poll(5000L, TimeUnit.MILLISECONDS);
//如果取到事件,或者超过等待时间
while ( entry != null ){
//执行事件回调
Wrapper result = entry.callback(cache.cacheMap);
//检查是否包含下一个事件(事件链)
if( entry.next != null ){
//执行next,获取下一个事件对象,循环执行
entry = entry.next.apply(result);
} else {
//如果再没有事件,结束循环
entry = null;
}
}
//当前时间
now = System.currentTimeMillis();
//当前时间 - 上次检查时间,超过了rolling,执行检查移除失效数据
if( now - last > rolling){
final long curr = now;
Stream<Map.Entry<String, Wrapper>> stream = null;
//如果缓存中超过x个对象,使用并行Stream处理,否则使用串行Stream todo x值根据实际情况设置
if( cache.cacheMap.size() > 4000 ){
stream = cache.cacheMap.entrySet().parallelStream();
} else {
stream = cache.cacheMap.entrySet().stream();
}
cache.cacheMap = stream
.filter( mapEntry -> mapEntry.getValue().keepAlive > curr)
.collect(Collectors.toConcurrentMap(mapEntry -> mapEntry.getKey(),
mapEntry -> mapEntry.getValue(),
(mapEntry1, mapEntry2) -> mapEntry2,
() -> new ConcurrentHashMap<>(6400)
));
last = now; //更新检查时间
}
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
run = false;
}
}
~ the end ~