你是我想和全世界炫耀,又舍不得和任何人分享的人。 --zhu
缓存定义
缓存(Caching)是系统优化中简单又有效的工具,投入小收效大。数据库中的索引等简单有效的优化功能本质上都是缓存。
多级缓存
浏览器-->网关服务器-->Web服务器-->数据库服务器。
客户端响应缓存
1、RFC73224是HTTP协议中对缓存进行控制的规范,其中重要的是cache-control这个响应报文头。服务器如果返回cache-control:max-age=60,表示服务器指示浏览器端“可以缓存这个响应内容60秒”。
2、我们只要给需要进行缓存控制的控制器的操作方法添加ResponseCacheAttribute这个Attribute,ASP.NET Core会自动添加cache-control报文头。
public class TestController:ControllerBase
{
[ResposeCache(Duration =20)]
[HttpGet]
public DateTime Now()
{
return DateTime.Now;
}
}
服务器端响应缓存
1、如果ASP.NET Core中安装了“响应式缓存中间件”,那么AP.NET Core不仅会继续根据[ResponseCache]设置来生成cache-control响应报文头来设置客户端缓存,而且服务器端也会按照[ResponseCache]的设置来响应进行服务器端缓存。和客户端缓存区别:来自多个不同客户端的相同请求。
2、“响应缓存中间件”的好处:对于来自不同客户端的相同请求或者不支持客户端缓存的客户端,能降低服务器端的压力。
3、用法:app.MapControllers()之前加上app.UseResponseCaching()。确保app.UseCors()写到app.UserResponseCaching()之前。
4、问题:
1)无法解决恶意请求给服务器带来的压力。
2)限制较多:响应码为200的GET或者HEAD响应才可能被缓存;报文头中不能含有Authorization、Set-Cookie等。
3)建议采用内存缓存、分布式缓存。
内存缓存(In-memory cache)
1、把缓存数据放到应用程序的内存。内存缓存中保存的是一系列的键值对,就像Dictionary类型一样。
2、内存缓存的数据保存在当前允许的网站程序中,是和进程相关。因为在Web服务器中,多个不同网站运行在不同的进程中,不同网站的内存缓存不会相互干扰,网站重启后,内存缓存中所有数据被清空。
内存缓存用法
1、启用:builder.Services.AddMemoryCache()
2、注入IMemoryCache接口,查看接口的方法:TryGetValue、Remove、Set、GetOrCreate、GetOrCreateAsync
3、用GetOrCreateAsync讲解
public async Task<Book[]> GetBooks()
{
logger.LogInformation("开始执行GetBooks");
var items = await memCache.GetOrCreateAsync("AllBooks",async(e)=>
{
logger.LogInformation("无缓存数据,从数据库获取");
return awwit dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("把数据返回给调用者");
return items;
}
缓存的过期时间
1、以上缓存不会过期,只能重启服务器。
2、解决方法:在数据改变的时候调用Remove或者Set来删除或者修改缓存(优点:及时);过期时间(只要过期时间比较短,缓存数据不一致的情况也不会持续很长时间)。
3、绝对过期时间、滑动过期时间。
绝对过期时间
1、GetOrCreateAsync()方法的回调方法中有一个ICacheEntry类型的参数,通过ICacheEntry对当前的缓存项做设置。
2、AbsoluteExpirationRelativeToNow用来设定缓存项的绝对过期时间。
public async Task<Book[]> GetBooks()
{
logger.LogInformation("开始执行GetBooks");
var items = await memCache.GetOrCreateAsync("AllBooks",async(e)=>
{
logger.LogInformation("无缓存数据,从数据库获取");
e.AbsoluteExpirationRelativeToNow =TimeSpan.FromSeconds(10);//缓存有效期10秒
return awwit dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("把数据返回给调用者");
return items;
}
滑动过期时间
1、缓存没过期时再请求一次,缓存续命一次。
2、ICacheEnty的SlidingExpiration属性用来设定缓存项的滑动过期时间。
public async Task<Book[]> GetBooks()
{
logger.LogInformation("开始执行GetBooks");
var items = await memCache.GetOrCreateAsync("AllBooks",async(e)=>
{
logger.LogInformation("无缓存数据,从数据库获取");
e.SlidingExpiration = TimeSpan.FromSeconds(10);//缓存有效期10秒
return awwit dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("把数据返回给调用者");
return items;
}
两种混用
使用滑动过期时间策略,如果一个缓存项一直被频繁访问,那么这个缓存项就会一直被续期不过期。可以对一个缓存项同时设定滑动过期时间和绝对过期时间,并且把绝对过期时间设定的比滑动过期时间长,这样缓存项的内容会在绝对过期时间内随着访问被滑动续期,但一旦超过绝对过期时间,缓存项就会被删除。
public async Task<Book[]> GetBooks()
{
logger.LogInformation("开始执行GetBooks");
var items = await memCache.GetOrCreateAsync("AllBooks",async(e)=>
{
logger.LogInformation("无缓存数据,从数据库获取");
e.SlidingExpiration = TimeSpan.FromSeconds(30);
e.SlidingExpiration = TimeSpan.FromSeconds(10);
return awwit dbCtx.Books.ToArrayAsync();
});
logger.LogInformation("把数据返回给调用者");
return items;
}
内存缓存总结
1、无论用哪种过期时间策略,程序中都会存在缓存数据不一致的情况。部分系统(博客)无所谓,部分系统不行(金融)。
2、可以通过其他机制获取数据源改变消息,哉通过代码调用IMemoryCache的Set方法更新缓存。
缓存穿透
string cacheKey="Book"+id;
Book? b=memCache.Get<Book?>(cacheKey);
if(b==null)如果缓存中没有数据
{
b = await dbCtx.Books.FindAsync(id);//查询数据库,写入缓存
memCache.Set(cacheKey,b);
}
上述代码,遇到不存在缓存键值时,会查询数据库,如果有人恶意频繁传入不存在的Id,每次都要查数据库,给数据库造成压力。
缓存穿透解决方法
1、把数据库查不到的当成一个指定数据放入缓存。
2、我们用GetOrCreateAsync方法即可,因为它会把null值当成合法的缓存值。
string cacheKey = "Book" + id;
var book =await memoCache.GetOrCreateAsync(cacheKey,async(e) =>{
var b=await dbAtx.Books.FindAsync(id);
logger.LogInformation("数据库查询:{0}",b==null?"为空":"不为空");
return b;
});
logger.LogInformation("Demo5执行结束:{0}",b==null?"为空":"不为空");
缓存雪崩
1、缓存项集中过期引起缓存雪崩。(例如所有都堆30秒后过期)
2、解决方法:在基础过期时间上,再加一个随机过期时间。
e.AbsoluteExpirationRelativeToNow =TimeSpan.FromSeconds(Random.Shared.Next(10,19));//过期时间随机,(Random.Shared全局随机数)
缓存数据混乱
public User GetUserInfo()
{
Guid userId=...;
return memCache.GetOrCreate("UserInfo",(e)=>
{
return ctx.User.Find()userId;//要不重复的键,UserInfo改成UserInfo+Id
})
}
分布式缓存
内存缓存效率高,但是如果集群节点数量非常多的话,重复查询时还是会把数据库整垮。
1、常用的分布式缓存服务器有Redis、Memcached等。
2、.NET Core中提供了统一的分布式缓存服务器操作接口IDistributedCache,用法和内存缓存类似。
3、分布式缓存和内存缓存的区别:缓存值的类型为byte[],需要我们进行类型转换,也提供了按照string类型存取缓存值的扩展方法。
缓存服务器选择
1、SQLServer做缓存性能不好。
2、Memcached是缓存专用,性能非常高,但是集群、高可用等方面比较弱,而且“缓存键的最大长度为250字节”限制。安装EnyimMemcachedCore包。
3、Redis不局限于缓存,Redis做缓存服务器比Memcached性能稍差,但是Redis的高可用、集群等方面强大,适合数据量大、高可用性等场景。
使用
1、NuGet安装Mircrosoft.Extensions.Caching.StackExchangeRedis
2、
builder.Services.AddStackExchangeRedisCache(options=>
{
options.Configuration="localhost";
options.InstanceName="yzk_";//避免混乱
});
public async Task<Book[]> GetBooks(long id)
{
string? s = await distCache.GetStringAsync("Book"+id);
if(s==null)
{
var book=await MyDbContext.GetByIdAsync(id);
await distCache.SetStringAsync("Book"+id,JsonSerializer.Serialize(book));
}
else
{
book = JsonSerializer.Deserialize<Book?>(s);
}
if(book==null)
{
return NotFound("不存在");
}
else
{
return book;
}
}
标签:缓存,return,过期,LogInformation,内存,logger
From: https://www.cnblogs.com/zhusichen/p/18325143