重庆熊猫 Loading

ASP.NET Core教程-Configuration(配置)- Cache(缓存)

更新记录
转载请注明出处:
2022年11月7日 发布。
2022年11月5日 从笔记迁移到博客。

缓存

缓存的概念

缓存(Caching)是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存。

image

image

多级缓存

image

缓存命中

缓存命中率

缓存数据不一致

1、无论用那种过期时间策略,程序中都会存在缓存数据不一致的情况。部分系统(博客等)无所谓,部分系统不能忍受(比如金融)。
2、可以通过其他机制获取数据源改变的消息,再通过代码调用IMemoryCache的Set方法更新缓存。

缓存穿透问题

缓存穿透的解决方案:把“查不到”也当成一个数据放入缓存。我们用GetOrCreateAsync方法即可,因为它会把null值也当成合法的缓存值。

string cacheKey = "Book" + id;	//缓存键
Book? b = memCache.Get<Book?>(cacheKey);
if(b == null)  //如果缓存中没有数据
{
    //查询数据库,然后写入缓存
	b=await dbCtx.Books.FindAsync(id);
	memCache.Set(cacheKey, b);
}

缓存雪崩

缓存项集中过期引起缓存雪崩。
解决方法:在基础过期时间之上,再加一个随机的过期时间。

缓存数据混乱

解决方法:合理设置key

缓存数据(Cache Data)

Cache Data 缓存实现方式

ASP.NET Core中有三种预定义方式缓存,数据可以存放在不同地方,分别如下:

实现名称 描述
AddDistributedMemoryCache 基于内存的数据缓存(in-memory cache)
AddDistributedSqlServerCache 分布式缓存,基于 SQL Server 实现,需要 Microsoft.Extensions.Caching.SqlServer 包
AddStackExchangeRedisCache 分布式缓存,基于 Redis 实现,需要 Microsoft.Extensions.Caching.Redis 包

客户端响应缓存(Caching Responses)

1、RFC7324是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果返回cache-control:max-age=60,则表示服务器指示浏览器端“可以缓存这个响应内容60秒”。
2、我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute这个Attribute,ASP.NET Core会自动添加cache-control报文头。

[ResponseCache(Duration = 10)]
[HttpGet(Name = "Test")]
public string Get()
{
    return DateTime.Now.ToString();
}

服务器端响应缓存(Caching Responses)

服务器端响应缓存中间件(Response Caching Middleware)

服务器端缓存,需要使用的服务器端响应缓存中间件(Response Caching Middleware)。

如果ASP.NET Core中安装了“响应缓存中间件”,那么ASP.NETCore不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来对响应进行服务器端缓存。

客户端端缓存的区别

客户端端缓存的区别来自多个不同客户端的相同请求。
“响应缓存中间件”的好处:对于来自不同客户端的相同请求者不支持客户端缓存的客户端,能降低服务器端的压力。

使用方法

在app.MapControllers()之前加上app.UseResponseCaching()即可。

app.UseResponseCaching()

注意:请确保app.UseCors()写到app.UseResponseCaching()之前。

使用服务器端缓存实例

注册中间件

builder.Services.AddControllers();
//注册服务器端缓存中间件
builder.Services.AddResponseCaching();

启用中间件

app.UseAuthorization();
//启用服务器端缓存中间件
app.UseResponseCaching();
app.MapControllers();

在Action上进行标记

[ResponseCache(Duration = 10)]
[HttpGet(Name = "Test")]
public string Get()
{
    return DateTime.Now.ToString();
}

服务器端缓存存在问题

1、无法解决恶意请求给服务器带来的压力。
2、服务器端响应缓存还有很多限制,包括但不限于:响应状态码为200的GET或者HEAD响应才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。

解决办法:

采用内存缓存、分布式缓存等。

基于内存的缓存

1、把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionary类型一样。
2、内存缓存的数据保存在当前运行的网站程序的内存中,是和进程相关的。因为在Web服务器中,多个不同网站是运行在不同的进程中的,因此不同网站的内存缓存是不会互相干扰的,而且网站重启后,内存缓存中的所有数据也就都被清空了。

使用方法

注入服务器端内存缓存中间件

//注入服务器端内存缓存中间件
builder.Services.AddMemoryCache();

在Action中使用

//引入命名空间
using Microsoft.Extensions.Caching.Memory;

[HttpGet(Name = "Test")]
public string Get(string id,[FromServices]IMemoryCache memoryCache)
{
    //到内存缓存中去读取数据
    string value = (string)memoryCache.GetOrCreate(id, (entry) => {
        //把当前的时间赋值给这个Key
        entry.Value = DateTime.Now.ToString();
        //返回当前时间
        return DateTime.Now.ToString();
    });

    return value;
}

缓存的过期时间策略

在数据改变的时候调用Remove或者Set来删除或者修改缓存,但如果数据不改变,那就需要时间过期策略了。
两种过期时间策略:绝对过期时间、滑动过期时间。

GetOrCreateAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过ICacheEntry对当前的缓存项做设置。
AbsoluteExpirationRelativeToNow用来设定缓存项的绝对过期时间。

实例:绝对过期时间

[HttpGet(Name = "Test")]
public string Get(string id,[FromServices]IMemoryCache memoryCache)
{
    //到内存缓存中去读取数据
    string value = (string)memoryCache.GetOrCreate(id, (entry) => {
        //把当前的时间赋值给这个Key
        entry.Value = DateTime.Now.ToString();

        //设置过期时间
        entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));

        //返回当前时间
        return DateTime.Now.ToString();
    });

    return value;
}

实例:滑动过期时间

[HttpGet(Name = "Test")]
public string Get(string id,[FromServices]IMemoryCache memoryCache)
{
    //到内存缓存中去读取数据
    string value = (string)memoryCache.GetOrCreate(id, (entry) => {
        //把当前的时间赋值给这个Key
        entry.Value = DateTime.Now.ToString();

        //设置过期时间
        entry.SetSlidingExpiration(TimeSpan.FromSeconds(2));

        //返回当前时间
        return DateTime.Now.ToString();
    });

    return value;
}

两种过期时间混用
使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期而不过期。可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定的比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但是一旦超过了绝对过期时间,缓存项就会被删除。

封装内存缓存操作的帮助类

IQueryable、IEnumerable等类型可能存在着延迟加载的问题,如果把这两种类型的变量指向的对象保存到缓存中,在我们把它们取出来再去执行的时候,如果它们延迟加载时候需要的对象已经被释放的话,就会执行失败。因此缓存禁止这两种类型。

实现随机缓存过期时间即可。参照使用Zack.ASPNETCore包

多服务器中的内存缓存

如果集群节点数量非常多,很容易重复查询导致把数据库压垮。解决办法:分布式缓存。

image

基于内存的数据缓存(In-memory Cache)

说明

缓存数据存储在本地的内存中。适合单体应用。不适合跨服务器、跨容器情况下的数据缓存。

注册服务

使用 AddDistributedMemoryCache 中间件。

builder.Services.AddDistributedMemoryCache(options =>
{
    //设置过期扫描的频率
    options.ExpirationScanFrequency = TimeSpan.FromHours(1);
    //设置缓存空间的大小
    options.SizeLimit = 10000;
    //设置超过最大大小时压缩缓存的数量。
    options.CompactionPercentage = 0.8;
});

基本使用

引入命名空间

using Microsoft.Extensions.Caching.Distributed;

注入到使用的控制器

public string Test([FromServices]IDistributedCache cache)
{

}

IDistributedCache 服务的基本操作,缓存数据的操作也就是增删改查罢了,基本操作如下:

方法名称 描述
GetString(key) This method returns the cached string associated with the specified key, or null if there is no such item.
GetStringAsync(key) This method returns a Task that produces the cached string associated with the key, or null if there is no such item.
SetString(key, value, options) This method stores a string in the cache using the specified key. The cache entry can be configured with an optional DistributedCacheEntryOptions object.
SetStringAsync(key, value, options) This method asynchronously stores a string in the cache using the specified key. The cache entry can be configured with an optional DistributedCacheEntryOptions object.
Refresh(key) This method resets the expiry interval for the value associated with the key, preventing it from being flushed from the cache.
RefreshAsync(key) This method asynchronously resets the expiry interval for the value associated with the key, preventing it from being flushed from the cache.
Remove(key) This method removes the cached item associated with the key.
RemoveAsync(key) This method asynchronously removes the cached item associated with the key.

设置键值对时,可以带一个额外的可选参数用于设置缓存项的持续时间、过期时间配置。DistributedCacheEntryOptions 支持的的属性如下:

属性项 说明
AbsoluteExpiration This property is used to specify an absolute expiry date.
AbsoluteExpirationRelativeToNow This property is used to specify a relative expiry date.
SlidingExpiration This property is used to specify a period of inactivity, after which the item will be ejected from the cache if it hasn’t been read.

设置缓存数据

public async Task<string> SetCacheKeyValue([FromServices] IDistributedCache cache)
{
    await cache.SetStringAsync("CountResult", countResultCache, 
                                   new DistributedCacheEntryOptions(){
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                                   });
}

读取缓存数据

public async Task<string> SetCacheKeyValue([FromServices] IDistributedCache cache)
{
    //获得指定键的值
    string countResultCache = await cache.GetStringAsync("CountResult");
}

移除指定Key的缓存数据

public async Task<string> SetCacheKeyValue([FromServices] IDistributedCache cache)
{
    await cache.RemoveAsync("CountResult");
}

设置缓存数据过期时间

public async Task<string> SetCacheKeyValue([FromServices] IDistributedCache cache)
{
    await cache.SetStringAsync("CountResult", countResultCache, 
                                   new DistributedCacheEntryOptions(){
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                                   });
}

真实情形下缓存数据

public async Task<string> SetCacheKeyValue([FromServices] IDistributedCache cache)
{
    //先去获得指定键的值
    string countResultCache = await cache.GetStringAsync("CountResult");

    //检测是否存在该键值对
    if (string.IsNullOrWhiteSpace(countResultCache))
    {
        //不存在则进行计算,然后缓存结果
        //模拟一个耗时的操作
        int CountResult = 0;
        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(1000);
            CountResult += i;
        }

        countResultCache = CountResult.ToString();

        //缓存计算结果
        await cache.SetStringAsync("CountResult", countResultCache, 
                                   new DistributedCacheEntryOptions(){
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                                   });
    }

    return countResultCache;
}

基于 SQL Server 的数据缓存

准备数据库

由于数据缓存存储在 SQL Server 内,所以需要准备一个 SQL Server 数据库。

打开 appsettings.json 文件,配置 连接字符串。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
   //连接字符串
  "ConnectionStrings": {
    //连接字符串
    "PandaSqlServerConnection": "Server=127.0.0.1;User Id=sa;Password=189789782;Database=CacheDb"
  }
}

连接上数据库后,为数据缓存创建缓存数据库。

CREATE DATABASE CacheDb;

安装数据库缓存表生成工具

dotnet tool install --global dotnet-sql-cache

执行 DotNet 缓存表生成命令,生成缓存需要的表。

dotnet sql-cache create "Server=127.0.0.1;User Id=sa;Password=189789782;Database=CacheDb" dbo DataCache

生成完成后,会多一个类似这种结构的数据表。

image

配置和注册服务

安装 SQL Server 缓存 NuGet 包。

dotnet add package Microsoft.Extensions.Caching.SqlServer

配置和注册服务。

builder.Services.AddDistributedSqlServerCache(options =>
{
    //设置存储缓存数据使用的数据库表面
    options.TableName = "DataCache";
    //设置存储缓存数据使用的数据库架构
    options.SchemaName = "dbo";
    //设置存储缓存数据使用的数据库连接字符串
    options.ConnectionString = builder.Configuration.GetConnectionString("PandaSqlServerConnection");
    //设置存储缓存数据过期扫描周期,默认30分钟扫描一次
    options.ExpiredItemsDeletionInterval = TimeSpan.FromHours(1);
    //设置默认过期时间,默认时20分钟
    options.DefaultSlidingExpiration = TimeSpan.FromHours(1);   
});

基本使用

引入命名空间。

using Microsoft.Extensions.Caching.Distributed;

在控制器中注入服务使用即可。

public async Task<string> Test([FromServices] IDistributedCache cache)
{
    
}

设置缓存数据

public async Task<string> Test([FromServices] IDistributedCache cache)
{
     //设置缓存数据
     await cache.SetStringAsync("PandaKey", "PandaValue", new DistributedCacheEntryOptions(){
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(6)
                                   });
}

读取缓存数据

public async Task<string> Test([FromServices] IDistributedCache cache)
{
    //读取缓存的数据
    string cachedValue = await cache.GetStringAsync("PandaKey");
}

移除指定Key的缓存数据

public async Task<string> Test([FromServices] IDistributedCache cache)
{
    await cache.RemoveAsync("PandaKey");
}

设置缓存数据过期时间

public async Task<string> Test([FromServices] IDistributedCache cache)
{
    //设置缓存数据
        await cache.SetStringAsync("PandaKey", "PandaValue", new DistributedCacheEntryOptions()
                                   {
                                       //设置缓存数据过期时间
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(6)
                                   });
}

真实情形下缓存数据

public async Task<string> Test([FromServices] IDistributedCache cache)
{
    //读取缓存的数据
    string cachedValue = await cache.GetStringAsync("PandaKey");
    //检测缓存的Key对应的值是否为空
    if(string.IsNullOrEmpty(cachedValue))
    {
        //模拟很耗时的任务
        Thread.Sleep(2000);

        //设置缓存数据
        await cache.SetStringAsync("PandaKey", "PandaValue", new DistributedCacheEntryOptions()
                                   {
                                       AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(6)
                                   });

        cachedValue = "PandaValue";
    }

    return cachedValue;
}

分布式缓存(基于Redis)

使用单独的缓存服务器来存储缓存数据。

常用的分布式缓存服务器有Redis、Memcached等。
.NET Core中提供了统一的分布式缓存服务器的操作接口IDistributedCache,用法和内存缓存类似。
分布式缓存和内存缓存的区别:缓存值的类型为bytel],需要我们进行类型转换,也提供了一些按照string类型存缓存值的扩展方法。

image

用什么做缓存服务器

1、用SQLServer做缓存性能并不好。
2、Memcached是缓存专用,性能非常高,但是集群、高可用等方面比较弱,而且有“缓存键的最大长度为250字节”等限制。可以安装EnyimMemcachedCore这个第三方NuGet包。
3、Redis不局限于缓存,Redis做缓存服务器比Memcached性能稍差,但是Redis的高可用、集群等方便非常强大,适合在数据量大、高可用性等场合使用。使用Microsoft.Extensions.Caching.StackExchangeRedis包

Redis分布式缓存使用

安装Microsoft.Extensions.Caching.StackExchangeRedis包

dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis

注册分布式缓存服务

builder.Services.AddControllers();
//注入Redis缓存中间件
builder.Services.AddStackExchangeRedisCache(options => {
    options.Configuration = "localhost";
    options.InstanceName = "Panda_";
});

在控制器中使用

using Microsoft.AspNetCore.Mvc;
//引入分布式缓存命名空间
using Microsoft.Extensions.Caching.Distributed;

namespace Test2.Controllers;

[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    private readonly IDistributedCache _DistributedCache;

    private readonly ILogger<TestController> _logger;

    public TestController(ILogger<TestController> logger, IDistributedCache distributedCache)
    {
        _logger = logger;
        _DistributedCache = distributedCache;
    }

    [HttpGet("SetCache")]
    public void SetCache(string id)
    {
        //设置缓存值
        this._DistributedCache.SetString(id, "Panda");
    }

    [HttpGet("GetCache")]
    public string GetCache(string id)
    {
        //读取缓存值
        return this._DistributedCache.GetString(id);
    }
}
posted @ 2022-11-07 09:01  重庆熊猫  阅读(600)  评论(0编辑  收藏  举报