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();
    }
}