TangYuan之Cache的设计

服务器

浏览数:430

2020-6-21

#TangYuan之Cache的设计

1. 从Cache的定义开始

在本章节中,我将和大家一起来分析一下TangYuan中Cache的架构设计,我们还是从使用层面开始。在TangYuan中如果我们需要使用Cache功能,我们首先需要在主配置文件tangyuan-configuration.xml中定义Cache.

示例1:

<cache id="cache1" type="local">
	<property name="strategy" value="time"/>
	<property name="survivalTime" value="10"/>
	<property name="log" value="true"/>
</cache>

......

<cacheGroup id="cacheGroup">
	<cache ref="cache1" />
	<cache ref="cache2" />
</cacheGroup>

在上面的代码中,我们定义了一个cache和一个cacheGroup,在cache1中指定type="local",并定义了一些property;在cacheGroup中引用了cache1。那么在TangYuan是如何是如何来描述cache,并实现这些用户的定义呢?我们先来看类图:

图片1:

其中CacheVo对应的是<cache>标签的定义内容,CacheGroupVo对应的是<cacheGroup>标签的定义内容,而CacheRefVo则用来描述cacheGroupcache的引用关系。结合源码,我们可以更清楚的了解其内部实现:

CacheVo

public class CacheVo {

	public enum CacheType {
		LOCAL, EHCACHE, MEMCACHE, REDIS
	}

	public enum CacheStrategyType {
		LRU, FIFO, SOFT, WEAK, TIME
	}

	protected boolean			group;

	private ICache				cache;

	private CacheType			type;

	private String				resource;

	private Map<String, String>	properties;
	
	......
}

CacheGroupVo

public class CacheGroupVo extends CacheVo {

	private List<CacheRefVo>	cacheRefList;

	public CacheGroupVo(String id, boolean defaultCache, List<CacheRefVo> cacheRefList) {
		super(id, defaultCache);
		this.cacheRefList = cacheRefList;
		this.group = true;
	}
	......
}

CacheRefVo

public class CacheRefVo {

	private CacheVo		cacheVo;

	......
}

那么示例1中完整的内存描述应该是这样:

cache1 = new CacheVo{
	type = CacheType.LOCAL, 
	group = false, 
	properties = {
		strategy = "time", 
		survivalTime = "10" ,
		log = "true"
	}
}

cacheGroup = new CacheGroupVo(){
	group = true,
	cacheRefList = [
		cache1, 
		cache2
	]
}

了解了cache定义的实现,接下来我们继续分析。有些细心的朋友可能留意到类图中还一个CacheCreater类和一个ICache接口。那这二者又是作何用途呢?

2. cache的实现

ICache接口是所有Cache实现类的核心接口,他定义了Cache的基本操作,全类名是org.xson.tangyuan.cache.ICache, 其中重要的的方法如下:

// 启动
void start(String resource, Map<String, String> properties);

// 放置数据到缓存中
void putObject(Object key, Object value, Integer time);

// 从缓存中获取数据
Object getObject(Object key);	

// 清除缓存中的数据
Object removeObject(Object key);

那么在TangYuan中有多少cache的实现类呢?

图片2:

说明:

实现类 用途及说明 分类
LocalCache 本地Cache LocalCache
LRUCache LRU策略Cache包装类 LocalCache
FIFOCache FIFO策略Cache包装类 LocalCache
SoftCache Soft策略Cache包装类 LocalCache
WeakCache Weak策略Cache包装类 LocalCache
ScheduledCache 过期策略Cache包装类 LocalCache
SynchronizedCache 并发访问控制Cache包装类 LocalCache
LoggingCache Cache命中率统计Cache包装类 LocalCache
MemcachedCache Memcached实现类 MemcachedCache
EhCacheCache EhCache实现类 EhCacheCache
RedisCache Redis实现类 RedisCache

从上述列表可知,TangYuan中提供了LocalCache的实现和对Memcached、EhCache、Redis的扩展实现。 具体是如何实现的,我们可以从两个方面分析,一个是初始化,另一个是功能调用,下面具体看一下源码

LocalCache的实现:

CacheVo:

public void start() {
	if (null != cache) {
		cache.start(resource, properties);
	} else {
		cache = new CacheCreater().newInstance(this);
	}
	if (null != properties) {
		properties.clear();
		properties = null;
	}
}

这里涉及到之前类图中尚未介绍的CacheCreater类,TangYuan中是通过此类,对之前表格中提到的Cache实现类进行实例化的。

CacheCreater

这里会根据CacheType来进行不同分支跳转:

public ICache newInstance(CacheVo cacheVo) {
	CacheType type = cacheVo.getType();
	if (CacheType.LOCAL == type) {
		return newLocalCache(cacheVo);
	} else if (CacheType.EHCACHE == type) {
		return newEhcache(cacheVo);
	} else if (CacheType.MEMCACHE == type) {
		return newMemcache(cacheVo);
	} else if (CacheType.REDIS == type) {
		return newRedisCache(cacheVo);
	}
	return null;
}

这里真正的创建和初始化LocalCache:

private ICache newLocalCache(CacheVo cacheVo) {
	Map<String, String> properties = cacheVo.getProperties();

	ICache localCache = new LocalCache(cacheVo.getId());

	CacheStrategyType strategyType = CacheStrategyType.LRU;
	String strategy = properties.get("strategy");
	if (null != strategy) {
		if ("FIFO".equalsIgnoreCase(strategy)) {
			strategyType = CacheStrategyType.FIFO;
		} else if ("SOFT".equalsIgnoreCase(strategy)) {
			strategyType = CacheStrategyType.SOFT;
		} else if ("WEAK".equalsIgnoreCase(strategy)) {
			strategyType = CacheStrategyType.WEAK;
		} else if ("TIME".equalsIgnoreCase(strategy)) {
			strategyType = CacheStrategyType.TIME;
		}
	}

	int maxSize = 1024;
	String _maxSize = properties.get("maxSize");
	if (null != _maxSize) {
		maxSize = Integer.parseInt(_maxSize);
	}

	int survivalTime = 10; // 10秒
	String _survivalTime = properties.get("survivalTime");
	if (null != _survivalTime) {
		survivalTime = Integer.parseInt(_survivalTime);
	}

	// 根据设置
	if (CacheStrategyType.LRU == strategyType) {
		localCache = new LRUCache(localCache, maxSize);
	} else if (CacheStrategyType.FIFO == strategyType) {
		localCache = new FIFOCache(localCache, maxSize);
	} else if (CacheStrategyType.SOFT == strategyType) {
		localCache = new SoftCache(localCache, maxSize);
	} else if (CacheStrategyType.WEAK == strategyType) {
		localCache = new WeakCache(localCache, maxSize);
	} else if (CacheStrategyType.TIME == strategyType) {
		localCache = new ScheduledCache(localCache, survivalTime);
	}

	localCache = new SynchronizedCache(localCache);

	// log可选
	boolean log = false;
	String _log = properties.get("log");
	if (null != _log) {
		log = Boolean.parseBoolean(_log);
	}
	if (log) {
		localCache = new LoggingCache(localCache);
	}

	return localCache;
}

从上面可以看到LocalCache的创建会根据用户在<property>标签中的设置,进行层层的包装。而其最底层的 实现类为LocalCache,我们来看一下他的源码:

public class LocalCache extends AbstractCache {

	private Map<Object, Object>	cache	= new HashMap<Object, Object>(1024);

	@Override
	public void putObject(Object key, Object value, Integer time) {
		cache.put(key, value);
	}

	public Object getObject(Object key) {
		return cache.get(key);
	}

	public Object removeObject(Object key) {
		return cache.remove(key);
	}

}

看到这里,大家应该明白了,LocalCache的最底层就是使用了一个HashMap,在当前JVM内存中进行数据的缓存。

Memcached的实现:

MemcachedLocalCache不同,因为Memcached本身为第三方Cache,TangYuan中只是做了集成,所以Memcached初始化的关键在于下面这段代码:

@Override
public void start(String resource, Map<String, String> properties) {
	......
	SockIOPool pool = SockIOPool.getInstance();
	pool.setServers(serverlist);
	pool.setWeights(weights);
	pool.setInitConn(initialConnections);
	pool.setMinConn(minSpareConnections);
	pool.setMaxConn(maxSpareConnections);
	pool.setMaxIdle(maxIdleTime);
	pool.setMaxBusyTime(maxBusyTime);
	pool.setMaintSleep(maintThreadSleep);
	pool.setSocketTO(socketTimeOut);
	pool.setSocketConnectTO(socketConnectTO);
	pool.setFailover(failover);
	pool.setFailback(failback);
	pool.setNagle(nagleAlg);
	pool.setHashingAlg(SockIOPool.NEW_COMPAT_HASH);
	pool.setAliveCheck(aliveCheck);
	pool.initialize();

	cachedClient = new MemCachedClient();
	......
}

TangYuan会根据用户在<property>标签中的设置的参数,在start方法中对Memcached进行初始化,具体的参数名称和直接使用Memcached是一致的。

MemcachedCache的相关操作源码:

@Override
public void putObject(Object key, Object value, Integer time) {
	Date expiry = null;
	if (null == time) {
		cachedClient.set(parseKey(key), value);
	} else {
		expiry = new Date(time.intValue() * 1000L);
		cachedClient.set(parseKey(key), value, expiry);
	}
}

@Override
public Object getObject(Object key) {
	return cachedClient.get(parseKey(key));
}

@Override
public Object removeObject(Object key) {
	Object result = getObject(key);
	if (null != result) {
		cachedClient.delete(parseKey(key));
	}
	return result;
}

EhCache的实现:

EhCache的初始化则是通过另一种方式:

@Override
public void start(String resource, Map<String, String> properties) {
	......
	try {
		InputStream inputStream = Resources.getResourceAsStream(resource);
		this.cacheManager = CacheManager.create(inputStream);
		this.cache = cacheManager.getCache(cacheManager.getCacheNames()[0]);
	} catch (Throwable e) {
		throw new CacheException(e);
	}
}

EhCacheCache采用这种外部资源文件来进行初始化的方式,主要是考虑兼容大家对EhCache使用习惯。

Redis的实现:

最后来看一下RedisCache的初始化方式:

public void start(String resource, Map<String, String> propertyMap) {
	......
	try {
		client = JedisClient.getInstance();
		Properties properties = new Properties();
		InputStream inputStream = Resources.getResourceAsStream(resource);
		properties.load(inputStream);
		client.start(properties);
	} catch (Throwable e) {
		throw new CacheException(e);
	}
}

RedisCache的初始化也是通过引入外部资源文件,这里使用了一个Redis的扩展工具包,感兴趣的朋友可以通过 以下地址了解:https://github.com/xsonorg/redis

3. cache的使用

之前两小节,我们一起分析了cache的定义和cache的实现,现在我们再来分析一下TangYuan是如何实现cache的使用的。我们先来看一下开发者是如何使用cache的:

示例2:

<selectOne id="getUserById" cacheUse="id:cache1; key:${service}; time:1">
	SELECT * from user WHERE user_id = #{user_id}
</selectOne>

在上述示例中,cacheUse这个属性就标志了当前SQL服务将会使用cache,那我们就从cacheUse这个属性说起。

图3:

图中CacheUseVo就对应示例2中的cacheUse属性,我们可以看到CacheUseVo类中持有了一个CacheVo,相当于持有了ICache的实现类,因此可通过cacheUse的属性值,进行cache的访问。具体代码如下:

public void putObject(final Object arg, final Object value) {
	// 异步操作
	TangYuanContainer.getInstance().addAsyncTask(new AsyncTask() {
		@Override
		public void run() {
			String key = buildKey(arg);
			cacheVo.putObject(key, value, time, ignore, service);
		}
	});
}

public Object getObject(Object arg) {
	String key = buildKey(arg);
	return cacheVo.getObject(key);
}

4. SQL服务 + cache执行流程

最后我们来分析一下,当一个服务使用cache后,对其执行流程的影响,我们还以示例2为例:

流程图:

源码:

@Override
public boolean execute(ServiceContext serviceContext, Object arg) throws Throwable {
	// 1. 从cache获取
	if (null != cacheUse) {
		Object result = cacheUse.getObject(arg);
		if (null != result) {
			serviceContext.setResult(result);
			return true;
		}
	}
	
	// 2. 服务执行
	if (XCO.class == resultType) {
		result = sqlContext.executeSelectSetListXCO(this, this.resultMap, fetchSize);
	} else {
		result = sqlContext.executeSelectSetListMap(this, this.resultMap, fetchSize);
	}
	
	// 3. 更新cache
	if (null != cacheUse) {
		cacheUse.putObject(arg, result);
	}
	
}

到此,本章节的内容就结束了,感兴趣的朋友可以关注TangYuan项目。

作者:xson_org