缓存雪崩和缓存穿透是C#后端服务在引入分布式缓存后最常遇到的两类问题。雪崩源于同一时刻大量键过期,穿透源于恶意或异常查询不存在的数据。下面从原理到代码说明如何用C#写出更稳健的缓存策略。

一、缓存雪崩的成因与解决
当很多缓存项设置了相同的过期时间,一旦到达时间点,请求全部落到数据库。常用做法是给过期时间加随机值,并使用锁或信号量合并回源。
1. 随机过期时间
在写入缓存时,基础过期时间上追加随机秒数,避免集体失效。
using System;
using StackExchange.Redis;
public class CacheHelper
{
private static IDatabase _db;
public static void SetWithRandomTtl(string key, string value, int baseSeconds)
{
var rnd = new Random();
int ttl = baseSeconds + rnd.Next(0, 300); // 随机0-300秒
_db.StringSet(key, value, TimeSpan.FromSeconds(ttl));
}
}
2. 互斥锁防止并发回源
缓存失效时,只让一个线程查库并写回,其余线程等待。
using System;
using System.Threading;
using StackExchange.Redis;
public class SafeCache
{
private static IDatabase _db;
public static string GetWithLock(string key)
{
string val = _db.StringGet(key);
if (!string.IsNullOrEmpty(val)) return val;
string lockKey = key + "_lock";
// 简单利用Redis锁,过期时间防止死锁
if (_db.StringSet(lockKey, "1", TimeSpan.FromSeconds(10), When.NotExists))
{
try
{
val = QueryFromDb(key); // 查数据库
_db.StringSet(key, val, TimeSpan.FromMinutes(5));
}
finally
{
_db.KeyDelete(lockKey);
}
}
else
{
Thread.Sleep(50);
return GetWithLock(key); // 重试读取
}
return val;
}
private static string QueryFromDb(string key)
{
return "db_value";
}
}
二、缓存穿透的应对
穿透是查询根本不存在的数据,缓存和数据库都没有,每次都打向数据库。
1. 缓存空对象
对查不到的结果也缓存短时间空值,减少重复穿透。
using StackExchange.Redis;
public class NullCache
{
private static IDatabase _db;
public static string GetOrNull(string key)
{
string val = _db.StringGet(key);
if (val != null) return val == "NULL" ? null : val;
val = QueryFromDb(key);
if (val == null)
{
_db.StringSet(key, "NULL", TimeSpan.FromMinutes(2)); // 短时间空缓存
}
else
{
_db.StringSet(key, val, TimeSpan.FromMinutes(5));
}
return val;
}
private static string QueryFromDb(string key)
{
return null;
}
}
2. 布隆过滤器拦截
在缓存前用布隆过滤器判断key是否可能存在,不存在直接拒绝。
using System;
using BloomFilter;
public class BloomGuard
{
private static Filter<string> _filter = new Filter<string>(1000000, 0.01);
public static bool MaybeExist(string key)
{
return _filter.Contains(key);
}
public static void Init(string[] allKeys)
{
foreach (var k in allKeys) _filter.Add(k);
}
}
三、小结
在C#里设计鲁棒缓存策略,核心是把过期打散、控制回源并发、拦截非法查询。结合上述代码思路,可有效降低雪崩与穿透风险,保障系统平稳。