WebAPI-缓存
WebAPI-缓存
浏览器缓存
使用[ResponseCache(Duration = 60)]特性允许浏览器缓存服务器响应的页面信息60s
[HttpGet]
[ResponseCache(Duration = 60)]
public IActionResult GetWeatherForecast()
{
return Ok();
}
除了允许在客户端缓存页面,我们可以通过使用响应缓存中间件
来配置服务端的缓存
需要在program.cs文件中配置响应缓存中间件
*这个中间件比较鸡肋,实际开发中也几乎用不上,更多的是采用内存缓存或分布式缓存等 *
app.UseAuthorization();
// 启动服务器端缓存,需要在MapControllers之前完成配置
app.UseResponseCaching();
app.MapControllers();
app.Run();
内存缓存
在program.cs文件中添加内存缓存服务
builder.Services.AddMemoryCache();
在控制器中注册服务,并使用缓存
通过调用GetOrCreate方法从缓存中获取数据,如果数据不存在则生成数据并缓存
当我们对数据库的数据进行操作时,我们可以选择调用Remove或者Set方法来操作或删除缓存中的数据
也可以配置缓存的过期时间,只要过期时间短,缓存数据不一致的情况不会持续很长时间
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IMemoryCache _memoryCache;
public WeatherForecastController(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
[HttpGet]
public async Task<IActionResult> Get(long id)
{
// 缓存键
string cacheKey = "WeatherForecast" + id;
// 使用 GetOrCreate 方法从缓存中获取数据,如果不存在则生成数据并缓存
var weatherForecasts = await _memoryCache.GetOrCreateAsync(cacheKey, async entry =>
{
// 设置缓存项的过期策略
entry.SlidingExpiration = TimeSpan.FromMinutes(5);
// 从数据源获取数据
return await FetchWeatherForecastsFromDataSource();
});
return Ok(weatherForecasts);
}
}
缓存穿透
对于恶意用户,访问一个不存在的缓存key,内存缓存会一直查询数据库,会给数据库造成很大的压力,这种现象被称之为缓存穿透
解决方案就是将数据查不到(null)也作为缓存结果存放在缓存中
调用GetOrCreateAsync方法,即可避免缓存穿透的问题
缓存雪崩
如果在请求频繁的时,缓存过期,会造成同一时间对数据库大量查询,造成缓存雪崩
解决方案就是在基础的过期时间之上,加入随机过期时间,让缓存不在统一时间内被清空,这样就不会一瞬间对数据库发送大量的查询请求
缓存数据混乱
当缓存中的Key冲突时,缓存中的value就会出现数据混乱
所以将Key设置为唯一值,即可解决这个问题,通常我们将key+唯一Id命名
分布式缓存
当Web服务被部署到多个服务器中,我们需要统一的调配同一个缓存,这时,继续使用内存缓存就不是最好的选择了
我们将缓存独立出来,放到一台单独的服务器中,所有的Web服务都来调配这台服务器的缓存,即可完成分布式缓存服务器
.NET Core提供了统一的分布式缓存服务器的操作接口IDistributedCache,用法和内存缓存类似
微软官方发布了针对于Redis数据库支持的包,在实际项目中我们也是使用Redis进行数据缓存
在实际开发中,公司已经将相关框架封装完成
Redis
安装依赖:Microsoft.Extensions.Caching.StackExchangeRedis
注册服务
在program.cs中注册服务,并使用系统环境变量的方式配置Redis链接字符串
builder.Services.AddStackExchangeRedisCache(option =>
{
option.Configuration = Environment.GetEnvironmentVariable("REDIS_CONNECTION_STRING");
// 设置Redis中Key的前缀
option.InstanceName = "cache1_";
});
注入依赖
在控制器或Service中注入依赖,直接使用即可
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly IDistributedCache _distributedCache;
public WeatherForecastController(IDistributedCache distributedCache)
{
_distributedCache = distributedCache;
}
[HttpGet]
public async Task<IActionResult> Get(long id)
{
Person? person;
// 在Redis中查询
string? s = await _distributedCache.GetStringAsync("Person" + id);
if (s == null)
{
// 查询数据库
person = new Person(); // 实际上这个person储存数据库查询的结果
// 将查到的结果缓存并放到数据库中,因为Redis存放的数据类型为bite[] 所以将对象序列化为Json串存放
_distributedCache.SetStringAsync("Person" + id, JsonSerializer.Serialize(person));
}
else
{
//如果缓存中查到,则将Json串反序列化为对象
person = JsonSerializer.Deserialize<Person?>(s);
}
if (person == null)
{
return NotFound("Person not found");
}
return Ok();
}
}