更新记录
转载请注明出处:
2022年11月7日 发布。
2022年11月5日 从笔记迁移到博客。
缓存
缓存的概念
缓存(Caching)是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存。
多级缓存
缓存命中
缓存命中率
缓存数据不一致
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包
多服务器中的内存缓存
如果集群节点数量非常多,很容易重复查询导致把数据库压垮。解决办法:分布式缓存。
基于内存的数据缓存(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
生成完成后,会多一个类似这种结构的数据表。
配置和注册服务
安装 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类型存缓存值的扩展方法。
用什么做缓存服务器
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);
}
}
标签:Core,ASP,string,过期,Cache,cache,缓存,缓存数据,public
From: https://www.cnblogs.com/cqpanda/p/16856710.html