zl程序教程

您现在的位置是:首页 >  .Net

当前栏目

《ASP.NET Core技术内幕与项目实战》精简集-高级组件4.1:缓存Cache

2023-03-20 15:33:41 时间

本节内容,涉及到7.4(P198-P221),以WebApi说明为主。主要NuGet包:

  • Microsoft.Extensions.Caching.StackExchangeRedis(Redis缓存) 

 

一、图解缓存机制

 

1、如图所示,首次请求数据时,先从缓存中获取,如果没有,则继续向数据库中获取。获取到数据后,将数据保存到缓存中。再次请求数据,一样先从缓存中获取,成功获取,“缓存命中”。多次请求中,命中次数占全部请求次数的比例,叫“命中率”。如果数据源的数据发生变化,而缓存中的数据没有更新,就会导致“缓存数据不一致”。

2、Web请求过程:客户端浏览器>网关服务器>Web服务器>数据库服务器,这些节点都可以设置缓存,缓存数据以键值对的方式储存在节点的内存中。

3、从图中可以看出,如果命中缓存,可以节省查询读取数据库的过程,可以明显提升请求效率。

4、常用的缓存包括:客户端响应缓存、服务端响应缓存、内存缓存、分布式缓存

 

 

二、客户端响应缓存和服务端响应缓存

1、客户端响应缓存

//GetNow1未设置客户端缓存
//GetNow2通过特性标注设置了客户端缓存
//特性[ResponseCache(Duration = 60)],告诉浏览器可以缓存数据60秒
//实质上是设置响应报文头的chche-control值为max-age=60,遵守HTTP的RFC7234规范
[ApiController]
[Route("api/[controller]/[action]")]
public class MyController: ControllerBase
{
    [HttpGet]
    public DateTime GetNow1()
    {
        return DateTime.Now;
    }

    [HttpGet]
    [ResponseCache(Duration = 60)]
    public DateTime GetNow2()
    {
        return DateTime.Now;
    }
}

 

2、服务端响应缓存

//配置中间件UseResponseCaching,其它与客户端响应缓存一致
//注意中间件位置,在UseCors之后,在MapControllers之前

var app = builder.Build();
......
app.UseCors();

app.UseResponseCaching();

app.MapControllers();

app.Run();

 

3、客户端和服务端响应缓存的注意事项:
1)如果客户端禁用浏览器缓存,"cache-control:no-chche",不仅客户端响应缓存失效,服务端响应缓存也会失效。

2)服务端响应缓存有一些限制,如响应码为200、GET/HEAD响应、不能有Authorization、不能有Set-Cookie。

3)建议只使用客户端响应缓存,简单设置ResponseCacheAttribute即可

 

 

三、内存缓存

1、在(Program.cs)容器中注册内存缓存服务,builder.Services.AddMemoryCache

2、在控制器中注入IMemoryCache服务

 1 [ApiController]
 2 [Route("api/[controller]/[action]")]
 3 public class MyController: ControllerBase
 4 {
 5     private readonly MyDbContext ctx;
 6     private readonly ILogger<MyController> logger;
 7     private readonly IMemoryCache memoryCache;
 8 
 9     public MyController(MyDbContext ctx, ILogger<MyController> logger, IMemoryCache memoryCache)
10     {
11         this.ctx = ctx;
12         this.logger = logger;
13         this.memoryCache = memoryCache;
14     }
15 
16     [HttpGet]
17     public async Task<Book[]> GetBooks()
18     {
19         logger.LogInformation("开始执行GetBooks");
20         var books = await memoryCache.GetOrCreateAsync("AllBooks", async (e) =>
21         {
22             //绝对过期时间
23             e.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
24             //滑动过期时间
25             e.SlidingExpiration = TimeSpan.FromSeconds(10); 
26 
27             logger.LogInformation("从数据库中读取数据");
28 
29             return await ctx.Books.ToArrayAsync();
30         });
31         logger.LogInformation("把数据返回给调用者");
32         return books;
33     }
34 }

代码解读:

7行,9-14行:依赖注入IMemoryCache内存缓存服务

20-30行:调用方法GetOrCreateAsync,尝试获取缓存键“AllBooks”的值,如果有,则返回缓存值。如果没有,则从数据库中读取数据,并将数据缓存到内存中(键为“AllBooks”),同时,设置缓存的绝对过期时间为30秒,滑动过程期间为10秒

补充说明:

①内存缓存,保存在Web服务器的内存中,且在当前运行程序的内存中,和进程相关。所以,如果Web服务器上,运行多个AspNetCore程序,程序之间的缓存相互隔离

②通过设置缓存键值的过期时间,可以有效防止“缓存数据不一致”。绝对过期,则超过设定时间后,缓存过期,需要重新从数据库获取数据;相对过期,指在设定的时间内,如果没有访问,则缓存过期,如有访问,则设定时间重置。一般绝对和相对过期结合使用,且设置绝对时间大于滑动过期时间。

③IMemoryCache,除了GetOrCreateAsync方法之外,还有TryGetValue、Remove、Get、Set、GetOrCreate等方法,一般使用GetOrCreateAsync,此方法可以有效防止缓存穿透。

④缓存常见问题一:缓存数据不一致。通过设置过期时间来缓解,如果要彻底解决,可以在数据变更时,调用Set方法,重新设置缓存值

⑤缓存常见问题二:缓存穿透。这是由使用Get和Set方法引起的,Get/Set的使用逻辑是,如果Get不到缓存,则读取数据库,并Set缓存键值。如果客户端大量请求数据库中没有的数据时,Get会一直返回null,而Set并不会将null作为键值保存,所以这种请求每次都会查询数据库,给数据库带来很大压力。GetOrCreateAsync,会将null也作为键值缓存,所以不会读取数据库。

⑥缓存常见问题三:缓存雪崩。如双十一,会提前做数据“预热”,即提前把数据读取出来并加入缓存。如果这些缓存的过期时间一致,会同时过期,造成数据库服务器请求大量集中,可以通过将过期时间设置为随机时间来避免。

 

 

四、分布式缓存

1、如果采用分布式部署,Web应用部署在多台Web服务器中,则可以选择分布式缓存。将缓存数据都保存到专门的缓存服务器中,所有Web应用都通过缓存服务器进行缓存数据的写入和获取。

2、简单的理解,就是原来使用本机的内存,现在使用另外一台服务器的内存。因为涉及跨服务器访问,性能比内存缓存稍低,但能解决分布式的问题。缓存服务器,并不直接使用内存,而是通过内存数据库进行缓存数据的管理,Redis使用比较多。

3、类似于内存缓存,AspNetCore提供了IDistributedCache接口,屏蔽了数据库了差异,无论使用Redis,或者其它数据库,用法都一样。同时,它的用法也几乎和IMemoryCache一样。键的类型为string,值的类型有两种,一是byte[],二是string。如果是byte[],需要自己进行读取和写入时的转换。

4、IDistributedCache的方法包括:GetAsync、SetAsync、RefreshAsync、RemoveAsync、GetStringAsync、SetStringAsync。

//首先注册Redis缓存服务
builder.Services.AddStackExchangeRedisCache(opt => 
{
    opt.Configuration = "localhost"; //配置Redis数据库连接
    opt.InstanceName = "fun_"; //配置缓存键的前缀
});


//操作使用分布式缓存,和IMemoryCache相似
[ApiController]
[Route("api/[controller]/[action]")]
public class TestController : ControllerBase
{
    //注入IDistributedCache服务
    private readonly IDistributedCache disCache
    public TestController(IDistributedCache disCache)
    {
        this.disCache = disCache;
    }
    
    [HttpGet]
    public string Now()
    {
        //获取缓存键为“Now”的值
        string s = disCache.GetString("Now");
        //如果键值为空,则获取s值,并缓存数据,过期时间设置为30秒
        if(s == null)
        {
            s = DateTime.Now.ToString();
            //设置过期时间
            var opt = new DistributedCacheEntryOptions();
            opt.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
            //缓存数据
            disCache.SetString("Now", s, opt);
        }
        return s;
    }
}

 

 

特别说明:
1、本系列内容主要基于杨中科老师的书籍《ASP.NET Core技术内幕与项目实战》及配套的B站视频视频教程,同时会增加极少部分的小知识点
2、本系列教程主要目的是提炼知识点,追求快准狠,以求快速复习,如果说书籍学习的效率是视频的2倍,那么“简读系列”应该做到再快3-5倍