44
回答
oschina 上的一种双缓存思路
利用AWS快速构建适用于生产的无服务器应用程序,免费试用12个月>>>   

举个例子:oschina 的首页,访问量很高,这个页面必须得缓存,oschina 使用的是 ehcache。

一般我们的缓存伪码如此:

List<T> objs = (List<T>)CacheManager.get(cache_region, key);
if(objs == null){
	objs = database_query(beanClass, sql, params);
	CacheManager.set(cache_region, key, (Serializable)objs);
}
return objs;

问题是一旦缓存失效需要从数据库重新加载数据的时候,大量的并发数据库访问会导致响应超级慢,也就是所谓的雪崩。

目前我们对这个问题的处理方法,我简单的说一下思路,具体的代码等有空再整理出来

假设我们前面举的例子中所使用的缓存region(region1),设置了自动失效时间为5分钟,相当于说每5分钟就会有一次发现访问首页很卡。因此引入了第二个缓存 region (region2) ,这个缓存的对象不会自动失效,也就说该区域的数据长期有效。

region2 的配置:

<cache name="icache-global"
        maxElementsInMemory="100"
        eternal="true"
        overflowToDisk="true"
        />

引入了第二个长效的region后,数据的读取流程是这样的:

1. 从 region1 读取数据,有则直接返回

2. region1 没数据则启动数据更新线程(下面介绍),然后从 region2 读数据,有则返回

3. region2 也没有数据
这种情况属于系统刚刚启动,缓存还没有填充数据的情况,没办法,这时候肯定会卡,或者你应该在系统启动的时候,自行填充一下数据,很简单,我一般在tomcat启动后,用命令访问下首页就有了缓存数据。

这样做的目的是为了正常的缓存失效后,无需等待重新从数据库中获取数据,而是直接在 region2 中获取数据并返回。因此对用户来讲,不会感觉请求被堵塞。

虽然请求顺畅了,但是数据还得更新,因此重要的还是启动数据更新线程是如何处理的。

线程本身所执行的方法很简单,无非就是到数据库中读取数据,然后将数据填充到 region1 ,但记得要同时填充到 region2,以确保下次缓存失效时,获取得到的是最新的数据。

就这么简单,其实也可以工作,但会有一个问题:假设缓存失效的时候,同时来了100个请求,那么这100个请求会同时启动100个数据更新线程,这100个数据更新线程会到数据库执行同样的SQL语句获得同样的结果,因此这种做法对数据库的压力并没有降低。

于是我需要在线程的执行方法里做一些调整,下面是伪码:

String data = CacheManager.get(String.class, region1, key);
if(data == null){
	ReentrantLock lock = g_locks.get(key);	

	if(!lock.tryLock())
		return null;

	try{
		//1. 执行SQL查询获取数据
		//2. 数据填充到 region1
		//3. 数据填充到 region2
	}finally{
		lock.unlock();
	}
}

首先还是要判断下缓存数据是否已存在,然后使用了一个 ReentrantLock 锁对象来控制不让多余的线程去执行数据更新过程。

这只是个大体的思路,仅供参考,目前已经在 oschina 上使用。

别提什么页面静态化,我对那个一点都不感冒。

举报
红薯
发帖于6年前 44回/11K+阅
共有44个评论 最后回答: 2年前

为虾米直接在启动tomcat的时候直接把缓存init下,这样就不要自己访问首页了,

还有就是到时失效的问题,如果缓存是30分钟失效,可否用监听控制在30分钟前系统刷新缓存。

不管你信不信,反正俺这么干了

我想了解下现在oschina.com有几台服务器在跑?
--- 共有 5 条评论 ---
zstacks@红薯 : 部署了多个实例吗? nginx + 1 tomcat? 要是有多机并发提升的经验学习下就好 6年前 回复
zstacks大哥很生气,:) 居然这个上下文都没看清楚 6年前 回复
红薯@arden : oschina.net !!!! 6年前 回复
arden.net! 是啥? 这么大量就一台服务器?数据库和应用都在一台服务器上? 6年前 回复
红薯.net! only one. 6年前 回复

我的建议如下:

和你一样建立regionA和regionB,其中A的失效为5分钟,B长期。当然第一次启动时都没数据的,可以在服务器设置启动后直接进行一次访问(以上为前提)

1.从A读取数据,有则直接返回;

2. A没数据则从B返回结果给用户,并启动一个线程将B克隆副本到A(复制完成前,用户访问的都是B);

3.当代码控制使得A数据失效时,从B返回结果给用户,并启动一个线程将B克隆到A(复制完成前,用户访问的都是B);

4.我的思路是只更新B,暴露给用户的主要是A。当A出现失效时再允许用户短暂的访问B数据。有点类似均衡。

不过似乎要改一下memcache的方法了

 

或者启动定时器,轮流更新A\B两个分区,当然在数据实时性要求不高的情况下

--- 共有 5 条评论 ---
mj4738你的思路和我想的一样 6年前 回复
忆童关于第3点,启动新线程,我觉得应该进进行数据更新,同时写A和B两个缓存数据,因为A数据实时性要求高.但在数据更新完毕前都先用B数据暂时替代,这样更合适些 6年前 回复
BluenightA的缓存失效是可以用代码控制的,也就是说,当B更新数据后,可触发事件,在后台启用一个线程将B克隆到A,在线程结束前,用户访问的是B数据,克隆线程结束后,用户访问的就是A区的数据了,当然这需要有个标识位来控制用户当前所访问的分区 6年前 回复
Bluenight好像评论缓存出问题了?? 6年前 回复
ShaoJiahao你这种方式是:更新了B之后,要等A失效了才会由B复制到A,这段时间内最新的数据在B,而用户访问的数据是A,也就是上一个版本的B。 6年前 回复
顶部