一个关于基础库(Cacher)的BUG
最近在对现有系统调试过程中,发现基础库Cacher.GetCacherTable的一个很严重的BUG。
请各位检查自己的代码,如果有同样的问题,请及时修改。
public static DataTable GetCacherTable(string sql, string cacheKey, CacheTime ct)
{
HttpContext context = HttpContext.Current;
DataTable dt = (DataTable)HttpRuntime.Cache[cacheKey];
if (dt == null)// 这里,如果缓存失效时,会产生大量的并发数据库请求,导致连接风暴。
{
dt = SqlHelper.ExecuteDataset(CommandType.Text, sql).Tables[0];
Cacher.CacherCache(cacheKey, context, dt, ct);
}
return dt;
}
这个问题比较典型,就是传说中的Dog Pile效应。
Dog-Pile Effect
所谓Dog-Pile Effect是指当某个缓存失效的时候,同时有大量请求发现没有缓存,进而请求后端的应用服务要求建立缓存,从而导致服务器卡顿甚至系统宕机的现象。
正确的代码应该如下:
public static DataTable GetCacherTable(string sql, string cacheKey, CacheTime ct)
{
bool forceLoad = false;
HttpContext context = HttpContext.Current;
string mutex_key = string.Format(CACHEKEY_MUTEX_FORMAT, cacheKey);
DataTable dt = (DataTable)HttpRuntime.Cache[cacheKey];
//用一个Mutex值来标识是否过期
if (HttpRuntime.Cache[mutex_key] == null)
{
HttpRuntime.Cache.Insert(mutex_key, “1″, null, DateTime.Now.AddSeconds((int)ct),
TimeSpan.Zero, CacheItemPriority.Normal, null);
forceLoad = true;
}
//这里加上double null checking是防止初次读取风暴
if (forceLoad || dt == null)
{
lock (g_locker)
{
dt = (DataTable)HttpRuntime.Cache[cacheKey];
if (forceLoad || dt == null)
{
Interlocked.Increment(ref g_dbCallCount);
dt = SqlHelper.ExecuteDataset(CommandType.Text, sql).Tables[0];
if (dt != null)
{
//这里实际是缓存2倍的时间,用于脏读
HttpRuntime.Cache.Insert(cacheKey, dt, null, DateTime.Now.AddSeconds((int)ct * 2),
TimeSpan.Zero, CacheItemPriority.Normal, null);
}
}
}
}
return dt;
}
以下是100个线程,5分钟的测试结果
- 旧Cache统计
数据库调用次数:462
页面耗时:3120 ms
调用结果:Demo测试 - 新Cache统计
数据库调用次数:26
页面耗时:3002 ms
调用结果:Demo测试