oschina 上的一种双缓存思路

红薯 发布于 2011/08/26 11:33
阅读 12K+
收藏 142

举个例子: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 上使用。

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

加载中
0
dedenj
dedenj

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

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

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

0
老盖
老盖
这种双缓存方式很不错,以后要借鉴一下
0
彭龙
彭龙
本人菜鸟,完全没有看懂,思想蛮好!
0
mallon
mallon
不用五分钟一下子都失效,而是每时每刻都随机取一两个失效,应该要好一点吧
0
许恒彪
许恒彪

负载均衡,多个tomcat的时候有bug

 

个人还是比较喜欢用nginx帮忙做静态化

0
ShaoJiahao
ShaoJiahao
不错,学习了。
0
幽灵
幽灵
哈哈,我转发了,嘻嘻。这太经典了。
0
B
Bluenight

我的建议如下:

和你一样建立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两个分区,当然在数据实时性要求不高的情况下

0
dargoner
dargoner
更新缓存的时候,是要进行线程同步的啊
0
我是潮汐
我是潮汐
为什么不在2.5分钟的时候 更新region2
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部