一、缓存失效的"案发现场"

某电商平台大促期间,商品详情页突然出现价格显示错误。技术团队紧急排查发现:使用了OutputCache的页面在商品调价后仍显示旧价格,数据库查询日志显示更新请求已正常执行,但用户端缓存未及时更新。这种典型的缓存失效场景,暴露了我们在缓存配置和数据更新逻辑中的潜在漏洞。

二、缓存系统的"体检清单"

1. 基础配置核查

// 错误示例:缺少关键参数的OutputCache配置
[OutputCache(Duration = 3600)] // 缺少VaryByParam导致不同商品ID共享缓存
public ActionResult ProductDetail(int id)
{
    // 业务逻辑
}

// 正确配置:通过VaryByParam区分不同商品
[OutputCache(Duration = 3600, VaryByParam = "id", Location = OutputCacheLocation.Server)]
public ActionResult ProductDetail(int id)
{
    // 使用MemoryCache做二级缓存
    var cacheKey = $"product_{id}";
    if (MemoryCache.Default.Contains(cacheKey))
    {
        return View(MemoryCache.Default[cacheKey]);
    }
    // 数据库查询与缓存写入逻辑
}

注释说明:

  • VaryByParam确保不同ID商品独立缓存
  • Location参数指定服务端缓存
  • MemoryCache作为辅助缓存层

2. 数据更新时的缓存驱逐

// 商品价格更新服务
public class ProductService
{
    // 错误示例:更新数据后未清除缓存
    public void UpdatePrice(int productId, decimal newPrice)
    {
        // 更新数据库
        using (var db = new AppDbContext())
        {
            var product = db.Products.Find(productId);
            product.Price = newPrice;
            db.SaveChanges();
        }
        // 缺少缓存清除操作
    }

    // 正确示例:主动清除相关缓存
    public void UpdatePrice(int productId, decimal newPrice)
    {
        // 更新数据库...
        
        // 清除OutputCache
        HttpResponse.RemoveOutputCacheItem(
            $"/Product/Detail/{productId}");
            
        // 清除MemoryCache
        var cacheKey = $"product_{productId}";
        if (MemoryCache.Default.Contains(cacheKey))
        {
            MemoryCache.Default.Remove(cacheKey);
        }
    }
}

注释说明:

  • RemoveOutputCacheItem清除指定路由缓存
  • MemoryCache需要显式移除条目
  • 建议封装统一的缓存清除方法

三、缓存依赖的"隐形锁链"

1. SQL缓存依赖配置

<!-- Web.config配置 -->
<caching>
  <sqlCacheDependency enabled="true" pollTime="60000">
    <databases>
      <add 
        name="ProductDB" 
        connectionStringName="AppDbContext" 
        pollTime="30000"/>
    </databases>
  </sqlCacheDependency>
</caching>
// 使用SQL缓存依赖
var sqlDependency = new SqlCacheDependency("ProductDB", "Products");
MemoryCache.Default.Add(
    cacheKey, 
    productData,
    sqlDependency,
    DateTime.MaxValue,
    Cache.NoSlidingExpiration,
    CacheItemPriority.Normal,
    null);

注意事项:

  • 需要数据库启用Broker服务
  • 表结构变更会导致依赖失效
  • 轮询间隔影响更新及时性

2. 文件依赖实战

// 价格配置文件依赖
var configPath = Server.MapPath("~/App_Data/price_config.xml");
var dependency = new CacheDependency(configPath);

MemoryCache.Default.Add(
    "price_config", 
    configData,
    dependency,
    Cache.NoAbsoluteExpiration,
    Cache.NoSlidingExpiration,
    CacheItemPriority.Normal,
    null);

优势说明:

  • 文件修改后自动刷新配置
  • 适用于频繁变更的静态数据
  • 配合FileSystemWatcher更高效

四、缓存策略的"黄金平衡"

应用场景分析

  • 高并发查询:商品列表/详情页
  • 计算密集型操作:报表统计
  • 配置类数据:系统参数设置

技术优缺点对比

缓存类型 响应速度 数据一致性 扩展性 适用场景
OutputCache 最快 最差 静态页面
MemoryCache 一般 一般 应用级共享数据
SQL Dependency 中等 较好 复杂 数据库驱动场景
Redis 较快 可调 优秀 分布式系统

必须警惕的"缓存陷阱"

  1. 雪崩效应:随机过期时间+熔断机制
  2. 穿透防御:空值缓存+布隆过滤器
  3. 击穿防护:互斥锁+自动续期
  4. 内存泄漏:定期清理+大小限制

五、实战演练:缓存监控系统

// 缓存健康检查中间件
public class CacheMonitorMiddleware
{
    public async Task Invoke(HttpContext context)
    {
        var cacheStatus = new
        {
            TotalMemory = GC.GetTotalMemory(false),
            MemoryCacheCount = MemoryCache.Default.GetCount(),
            OutputCacheItems = GetOutputCacheStats()
        };
        
        if (context.Request.Path == "/cache-status")
        {
            await context.Response.WriteAsync(
                JsonConvert.SerializeObject(cacheStatus));
            return;
        }
        
        await _next(context);
    }
    
    private int GetOutputCacheStats()
    {
        // 通过反射获取内部缓存计数(示例代码)
        var type = typeof(OutputCacheModule);
        var store = type.GetField("_cache", BindingFlags.NonPublic);
        return ((MemoryCache)store.GetValue(null)).Count;
    }
}

监控指标建议:

  • 缓存命中率
  • 内存使用率
  • 过期条目占比
  • 依赖项健康状态

六、缓存优化的常用方案

  1. 分级缓存策略:本地缓存+分布式缓存
  2. 智能淘汰算法:LRU与LFQ混合使用
  3. 压缩序列化:MessagePack替代JSON
  4. 热点数据预加载:基于访问模式预测
  5. 版本化缓存键:v1_product_123
  6. 熔断机制:缓存故障时降级查询