并发环境下JavaWeb的缓存过期策略
最近公司的几个平台经常在高峰期挂掉,经检查是因为数据库有太多Slow Query导致的,当初也没细想为什么会出现这么多的Slow Query,而且大部分还是相同的查询,单独拿某个Sql查询消耗时间大都在毫秒级别,为了安全起见,对所有Sql又做了一次优化,并且写了监测脚本,定期杀掉太慢的查询,但这样的话还是会影响到有些用户的访问。
? ? ?网站采用了Spring+SpringJDBC+Servlet+Memcache的架构,数据库是一对Master-Slave的Mysql,有大概几十个接口和4个网站共用这个数据库,采用了proxool数据库连接池。因为数据的实时性,所以CDN和Memcache的缓存过期时间都是在5分钟左右,好了环境介绍完毕,开始着手解决这个问题。
? ? ?为了模拟高峰期并发环境,使用Apache的ab命令对网站进行压力测试,此时测试环境是没有Cache的,果不其然,log里出现了数据库连接已经占满的异常信息,猜测是在大并发环境下,缓存正好过期,所有的访问都去请求数据库导致连接占满,经过考虑,有了以下解决方案,不足之处请说明。
?
备注:以下过程中出现的client为Memcache的实例,省略了初始化的过程,所用到的Memcache库为xmemcache1.3.3
?
1、? 增加数据备份,防止缓存过期后同时请求数据库
2、? 增加同步机制,保证并发环境下只有一个用户在更新数据
3、? 增加数据更新回调接口,当缓存过期后,调用接口更新数据
4、? 验证数据正确性,防止在更新pojo类时出现的ClassCastException
?
定义数据更新回调接口:
12 + object.toString() + " Error: " + e.getMessage());13 }14 }操作线程池更新缓存
1 Map<String, Object> args = new HashMap<String, Object>(); 2 args.put("page", page); 3 args.put("sort", sort); 4 5 Map<String, Object> data = (Map<String, Object>) MemcachedMgr.get("your key", args, new MemcachedCallback() { 6 7 public Object update(Map<String, Object> args) { 8 Map<String, Object> map = new HashMap<String, Object>(); 9 //所有的参数都可以从args拿到10 //TODO 查询数据库,并将结果存入map11 return map;12 }13 14 public boolean validate(Object data) {15 Map<String, Object> map = (Map<String, Object>) data;16 try {17 //TODO 通过强制类型转换来判断是否有转换错误,或者自定义校验18 } catch (Exception e) {19 return false;20 }21 return true;22 }23 });24 //TODO request.setAttribute & 转发
算是告一段落,开始压力测试,模拟300个并发测试该接口,数据库只有一个process,而且QPS基本没什么变化。
据同事讲,缓存过期请求击穿数据库这种情况叫“Dogpile”,google了下dogpile,只发现hibernate里自带了DogpilePrevention,百度没有找到相关资料……
采用map存放页面所需所有数据感觉上还是不太好,暂时没想到更好的办法,先这么着吧,如果有好的解决方案,请大家不吝指教。