Redis缓存穿透、缓存击穿、缓存雪崩是常见的缓存问题,可以通过以下方式解决:
缓存穿透
缓存穿透是指查询一个不存在的数据,由于缓存中没有,导致每次请求都会查询数据库,增加数据库负载。解决方法如下:
布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
缓存空对象:当一个查询返回的数据为空时,应该将这个空对象也缓存起来,但是过期时间需要短一些,这样下一次请求访问同样的key时就可以从缓存中获取到空对象,而不会再次查询数据库。
接口层参数校验:对于一些恶意攻击的非法请求,应该在接口层对参数进行校验,过滤掉不合法请求。
缓存击穿
缓存击穿是指一个key非常热点,在不停的扛着大并发,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,导致数据库瞬间压力过大。解决方法如下:
设置热点数据永不过期:对于一些热点数据,可以将其设置为永不过期,这样即使缓存失效,也不会导致大量请求直接打到数据库。
加互斥锁:在缓存失效的瞬间,可以使用互斥锁来防止缓存穿透,当多个请求同时访问同一key时,只有一个请求可以访问数据库,其余请求等待其返回结果。
缓存雪崩
缓存雪崩是指缓存中大量数据在同一时间失效,导致大量请求直接打到数据库,导致数据库瞬间压力过大。解决方法如下:
设置不同的过期时间:对于相同的数据,可以设置不同的过期时间,让它们失效的时间点均匀分布,避免在同一时间失效。
使用缓存预热:在系统启动时,可以将一些热点数据提前加载到缓存中,这样可以避免在系统运行过程中大量数据同时失效。
使用限流降级:当缓存失效,请求打到数据库时,可以使用限流降级来控制流量,避免瞬间压垮数据库。
以下是C#中使用StackExchange.Redis解决缓存穿透、缓存击穿、缓存雪崩的示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | using System; using System.Threading.Tasks; using StackExchange.Redis; public class RedisHelper { private static readonly Lazy<ConnectionMultiplexer> Connection; private static readonly object LockObject = new object (); static RedisHelper() { Connection = new Lazy<ConnectionMultiplexer>(() => { var configurationOptions = new ConfigurationOptions() { EndPoints = { "localhost:6379" }, Password = "password" , AbortOnConnectFail = false }; return ConnectionMultiplexer.Connect(configurationOptions); }); } public static IDatabase GetDatabase() { return Connection.Value.GetDatabase(); } public static async Task<T> GetOrSetAsync<T>( string key, Func<Task<T>> func, TimeSpan? expiry = null ) { var value = await GetAsync<T>(key); if (value != null ) { return value; } lock (LockObject) { value = GetAsync<T>(key).Result; if (value != null ) { return value; } value = func().Result; if (value != null ) { SetAsync(key, value, expiry).Wait(); } } return value; } public static async Task<T> GetAsync<T>( string key) { var value = await GetDatabase().StringGetAsync(key); return value.HasValue ? Deserialize<T>(value) : default (T); } public static async Task SetAsync<T>( string key, T value, TimeSpan? expiry = null ) { await GetDatabase().StringSetAsync(key, Serialize(value), expiry); } private static byte [] Serialize<T>(T value) { var json = Newtonsoft.Json.JsonConvert.SerializeObject(value); return System.Text.Encoding.UTF8.GetBytes(json); } private static T Deserialize<T>( byte [] value) { var json = System.Text.Encoding.UTF8.GetString(value); return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json); } } |
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class UserService { public async Task<User> GetUserAsync( int userId) { var cacheKey = $ "user:{userId}" ; return await RedisHelper.GetOrSetAsync(cacheKey, async () => { // 从数据库中查询用户信息 var user = await DbContext.Users.FindAsync(userId); return user; }, TimeSpan.FromMinutes(30)); } } |
在上面的示例中,使用了RedisHelper类来封装了StackExchange.Redis库的常用操作,并提供了GetOrSetAsync方法来解决缓存穿透、缓存击穿、缓存雪崩的问题。在GetOrSetAsync方法中,先从缓存中获取数据,如果缓存中不存在,则使用互斥锁来保证只有一个请求可以访问数据库,其余请求等待其返回结果。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
· 提示词工程——AI应用必不可少的技术