一个关于基础库(Cacher)的BUG

JerryXia 发表于 , 阅读 (1,295)

最近在对现有系统调试过程中,发现基础库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是指当某个缓存失效的时候,同时有大量请求发现没有缓存,进而请求后端的应用服务要求建立缓存,从而导致服务器卡顿甚至系统宕机的现象。

i2

正确的代码应该如下:

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测试

添加新评论