.net core WebApi使用Redis教程(五)Redis缓存应用缓存击穿概念及解决办法
本文讲解Redis在应用缓存领域的使用方法,实例代码基于 .net core WebApi进行实现。实例代码下载链接:http://www.80cxy.com/Blog/ResourceView?arId=202401110950583321kEqrCT
一、缓存击穿定义
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的Key突然失效了,无数请求访问会在瞬间给数据库带来巨大的冲击。
常见的两种解决方案:
(一)互斥锁:
缓存构建由特定线程创建,构建时获取互斥锁,其他线程再获取资源重建缓存时无法获取到互斥锁,只能等待重试,直到缓存重建成功。

优点:
没有额外的内存消耗,保证一致性,实现简单。
缺点:线程需要等待,性能受影响,可能有死锁风险。
(二)逻辑过期:
不在设置缓存TTL过期时间,在缓存数据中添加过期时间。缓存构建时获取互斥锁,构建由特定线程开启一个新线程构建,并将过期的数据返回,其他线程再获取资源重建缓存时无法获取到互斥锁,直接将过期数据返回,直到缓存构建完才能获取到新数据。
.png)
优点:线程无需等待,性能较好。
缺点:不保证一致性,有额外的内容消耗,实现复杂。
二、互斥锁实现代码
(一)代码逻辑流程
修改根据Id获取商铺的业务,基于互斥锁方式来解决缓存击穿问题。程序流程逻辑流程如下:
.png)
(二)实现代码
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缓存应用基础讲解(976)
- Redis教程一、Redis基础讲解及windows下安装、配置说明(732)
- .net core WebApi使用Redis教程(一)Redis代替Session实现登录(673)
- .net core WebApi使用Redis教程(三)Redis缓存应用缓存穿透概念及解决办法(572)
- .net core WebApi使用Redis教程(五)Redis缓存应用缓存击穿概念及解决办法(562)
- .net core WebApi使用Redis教程(四)Redis缓存应用缓存雪崩讲解(492)
- Redis教程二、Redis数据结构及常用命令详细介绍(466)
- Redis教程三、一图读懂Redis企业应用场景(455)
评论列表
发表评论
文章分类
文章归档
- 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支付方式总结(5920)
- 2.Windows 10休眠文件更改存储位置(4025)
- 3.各大搜索网站网站收录提交入口地址(3507)
- 4.windows 10安装myeclipse 10破解补丁cracker.jar、run.bat闪退解决办法(3483)
- 5.ECharts仪表盘实例及参数使用详解(3466)
- 6.华为鸿蒙系统清除微信浏览器缓存方法(3262)
- 7.HTML5 WebSocket与C#建立Socket连接实现代码(3222)
- 8.CERT_HAS_EXPIRED错误如何解决(3023)
- 9.Js异步async、await关键字详细介绍(lambda表达式中使用async和await关键字)(2673)
- 10.HBuilder编辑器格式化代码(2433)
