.net core WebApi使用Redis教程(五)Redis缓存应用缓存击穿概念及解决办法
本文讲解Redis在应用缓存领域的使用方法,实例代码基于 .net core WebApi进行实现。实例代码下载链接:http://www.80cxy.com/Blog/ResourceView?arId=202401110950583321kEqrCT
一、缓存击穿定义
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了,无数请求访问会在瞬间给数据库带来巨大的冲击。
常见的两种解决方案:
(一)互斥锁:
缓存构建由特定线程创建,构建时获取互斥锁,其他线程再获取资源重建缓存时无法获取到互斥锁,只能等待重试,直到缓存重建成功。
优点:
没有额外的内存消耗,保证一致性,实现简单。
缺点:线程需要等待,性能受影响,可能有死锁风险。
(二)逻辑过期:
不在设置缓存TTL过期时间,在缓存数据中添加过期时间。缓存构建时获取互斥锁,构建由特定线程开启一个新线程构建,并将过期的数据返回,其他线程再获取资源重建缓存时无法获取到互斥锁,直接将过期数据返回,直到缓存构建完才能获取到新数据。
优点:线程无需等待,性能较好。
缺点:不保证一致性,有额外的内容消耗,实现复杂。
二、互斥锁实现代码
(一)代码逻辑流程
修改根据Id获取商铺的业务,基于互斥锁方式来解决缓存击穿问题。程序流程逻辑流程如下:
(二)实现代码
using Common.CacheManager; using Common.Commons; using Common.Const; using Common.Enum; using Common.Extensions; using Domain.Entities; using Domain.Repository; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Domain.Service { public class RedisDomainService : IRedisDomainService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IServiceProvider serviceProvider; private readonly IRedisDomainRepostory redisRepostory; private readonly ICacheService cacheService; private ISession _session => _httpContextAccessor.HttpContext.Session; public RedisDomainService(IHttpContextAccessor httpContextAccessor, IRedisDomainRepostory redisRepostory, ICacheService cacheService, IServiceProvider serviceProvider = null) { _httpContextAccessor = httpContextAccessor; this.redisRepostory = redisRepostory; this.cacheService = cacheService; this.serviceProvider = serviceProvider; } #region Redis实现缓存 public async Task<ResponseContent> GetShopById(string id) { ResponseContent response = new ResponseContent(); //用互斥锁解决缓存击穿 //Shop shop = await QueryWithMutex(id); if (shop == null) return response.Error(ResponseType.OperError); else return response.Ok(shop); } #region 根据ID获取商铺信息(用互斥锁解决缓存击穿代码) /// <summary> /// 根据ID获取商铺信息(用互斥锁解决缓存击穿代码) /// </summary> /// <param name="id"></param> /// <returns></returns> public async Task<Shop> QueryWithMutex(string id) { Shop shop = null; //1.从Redis缓存查询商品 string shopJson = cacheService.Get(RedisConstants.CACHE_SHOP_KEY + id); //2.判断缓存是否存在 if (!string.IsNullOrEmpty(shopJson)) { //3.存在且不为空值直接返回 return JsonConvert.DeserializeObject<Shop>(shopJson); } //判断命中的是否是空值(缓存穿透存入的空值) if (shopJson != null) { //返回错误信息 return null; } //开始实现缓存重建 //4.1获取互斥锁 bool isLock = TryLock(RedisConstants.LOCK_SHOP_KEY + id); //4.2判断是否获取成功 if (!isLock) { //4.3失败,则休眠并重试 Thread.Sleep(50); return await QueryWithMutex(id); } //4.不存在,根据Id查询数据库 shop = await redisRepostory.GetShopById(id); Thread.Sleep(200);//默认重建缓存延迟 //5.不存在,返回错误 if (shop == null) { //将空值写入Redis(解决缓存穿透) cacheService.Add(RedisConstants.CACHE_SHOP_KEY + id, "", RedisConstants.CACHE_NULL_TTL); //返回错误信息 return null; } //6.存在写入Redis,并设置缓存超时时间 cacheService.Add(RedisConstants.CACHE_SHOP_KEY + id, JsonConvert.SerializeObject(shop), RedisConstants.CACHE_SHOP_TTL); //7.释放互斥锁 UnLock(RedisConstants.LOCK_SHOP_KEY + id); //8.返回 return shop; } #endregion /// <summary> /// 获取互斥锁 /// </summary> /// <param name="key"></param> /// <returns></returns> private bool TryLock(string key) { bool flag = cacheService.AddNx(key, "1"); cacheService.Expire(key, 10); return flag; } /// <summary> /// 释放互斥锁 /// </summary> /// <param name="key"></param> private void UnLock(string key) { cacheService.Remove(key); } } }
三、逻辑过期实现代码
(一)逻辑过期代码实现逻辑流程
修改根据Id获取商铺的业务,基于逻辑过期方式来解决缓存击穿问题。程序流程逻辑流程如下:
(二)实现代码
using Common.CacheManager; using Common.Commons; using Common.Const; using Common.Enum; using Common.Extensions; using Domain.Entities; using Domain.Repository; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace Domain.Service { public class RedisDomainService : IRedisDomainService { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IServiceProvider serviceProvider; private readonly IRedisDomainRepostory redisRepostory; private readonly ICacheService cacheService; private ISession _session => _httpContextAccessor.HttpContext.Session; public RedisDomainService(IHttpContextAccessor httpContextAccessor, IRedisDomainRepostory redisRepostory, ICacheService cacheService, IServiceProvider serviceProvider = null) { _httpContextAccessor = httpContextAccessor; this.redisRepostory = redisRepostory; this.cacheService = cacheService; this.serviceProvider = serviceProvider; } #region Redis实现缓存 public async Task<ResponseContent> GetShopById(string id) { ResponseContent response = new ResponseContent(); //用逻辑过期解决缓存击穿 //生成Redis缓存 //await SaveShop2Redis(id, 60); Shop shop = await QueryWithLogicalExpire(id); if (shop == null) return response.Error(ResponseType.OperError); else return response.Ok(shop); } #region 根据ID获取商铺信息(用逻辑过期解决缓存击穿代码) /// <summary> /// 根据ID获取商铺信息(用逻辑过期解决缓存击穿代码) /// </summary> /// <param name="id"></param> /// <returns></returns> public async Task<Shop> QueryWithLogicalExpire(string id) { Shop shop = null; //1.从Redis缓存查询商品 string shopJson = cacheService.Get(RedisConstants.CACHE_SHOP_KEY + id); //2.判断缓存是否存在 if (string.IsNullOrEmpty(shopJson)) { //3.不存在直接返回 return null; } //4.命中,需要先把Json反序列化为对象 RedisData redisData = JsonConvert.DeserializeObject<RedisData>(shopJson); JObject jsonObject = (JObject)redisData.Data; shop = jsonObject.ToObject<Shop>(); DateTime expireTime = redisData.ExpireTime; //5.判断是否过期 int result = DateTime.Compare(DateTime.Now, expireTime); if (result < 0) { //5.1未过期直接返回店铺信息 return shop; } //5.2已过期,需要缓存重建 //6.缓存重建 //6.1获取互斥锁 bool isLock = TryLock(RedisConstants.LOCK_SHOP_KEY + id); //6.2判断是否获取成功 if (isLock) { //6.3成功,开启独立线程实现缓存重建 Thread t = new Thread(new ParameterizedThreadStart(async param => { //重建缓存 await SaveShop2Redis(id, 20); //释放锁 UnLock(RedisConstants.LOCK_SHOP_KEY + id); })); t.Start(""); } //6.3返回 return shop; } #endregion /// <summary> /// 获取互斥锁 /// </summary> /// <param name="key"></param> /// <returns></returns> private bool TryLock(string key) { bool flag = cacheService.AddNx(key, "1"); cacheService.Expire(key, 10); return flag; } /// <summary> /// 释放互斥锁 /// </summary> /// <param name="key"></param> private void UnLock(string key) { cacheService.Remove(key); } /// <summary> /// 获取商铺信息并添加逻辑过期时间,添加到Redis /// </summary> /// <param name="id"></param> /// <param name="expireSeconds"></param> /// <returns></returns> private async Task SaveShop2Redis(string id, int expireSeconds) { using (var scope = serviceProvider.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<IRedisDomainRepostory>(); //1.查询店铺数据 Shop shop = await context.GetShopById(id); Thread.Sleep(200); //2.封装逻辑过期时间 RedisData redisData = new RedisData(); redisData.Data = shop; redisData.ExpireTime = DateTime.Now.AddSeconds(expireSeconds); //3.写入Redis cacheService.Add(RedisConstants.CACHE_SHOP_KEY + id, JsonConvert.SerializeObject(redisData)); } } #endregion } }
猜您可能还喜欢
- .net core WebApi使用Redis教程(二)Redis缓存应用基础讲解(837)
- Redis教程一、Redis基础讲解及windows下安装、配置说明(612)
- .net core WebApi使用Redis教程(一)Redis代替Session实现登录(520)
- .net core WebApi使用Redis教程(五)Redis缓存应用缓存击穿概念及解决办法(472)
- .net core WebApi使用Redis教程(三)Redis缓存应用缓存穿透概念及解决办法(459)
- Redis教程二、Redis数据结构及常用命令详细介绍(379)
- .net core WebApi使用Redis教程(四)Redis缓存应用缓存雪崩讲解(374)
- Redis教程三、一图读懂Redis企业应用场景(349)
评论列表
发表评论
文章分类
文章归档
- 2025年3月 (1)
- 2024年6月 (2)
- 2024年5月 (2)
- 2024年4月 (4)
- 2024年3月 (30)
- 2024年1月 (4)
- 2023年12月 (2)
- 2023年11月 (4)
- 2023年10月 (4)
- 2023年9月 (6)
- 2023年3月 (2)
- 2023年2月 (1)
- 2023年1月 (1)
- 2022年12月 (1)
- 2022年9月 (21)
- 2022年8月 (10)
- 2022年7月 (3)
- 2022年4月 (1)
- 2022年3月 (13)
- 2021年8月 (1)
- 2021年3月 (1)
- 2020年12月 (42)
- 2020年11月 (7)
- 2020年10月 (5)
- 2020年8月 (1)
- 2020年6月 (1)
- 2020年3月 (2)
- 2019年12月 (8)
- 2019年11月 (3)
- 2019年9月 (1)
- 2019年4月 (1)
- 2019年3月 (6)
- 2019年2月 (1)
- 2018年7月 (7)
阅读排行
- 1.asp.net mvc内微信pc端、H5、JsApi支付方式总结(5702)
- 2.各大搜索网站网站收录提交入口地址(3201)
- 3.Windows 10休眠文件更改存储位置(3163)
- 4.ECharts仪表盘实例及参数使用详解(3095)
- 5.windows 10安装myeclipse 10破解补丁cracker.jar、run.bat闪退解决办法(2991)
- 6.HTML5 WebSocket与C#建立Socket连接实现代码(2865)
- 7.华为鸿蒙系统清除微信浏览器缓存方法(2779)
- 8.CERT_HAS_EXPIRED错误如何解决(2245)
- 9.Js异步async、await关键字详细介绍(lambda表达式中使用async和await关键字)(2187)
- 10.HBuilder编辑器格式化代码(2118)