Net DB Web多級快取的實現

2023-03-21 06:01:39

1、使用者端快取(瀏覽器快取)

  HTTP有一套控制快取的協定-RFC7234,其中最重要的就是cache-control這個相應報文頭,伺服器返回時,如果Response帶上

cache-control:max-age=5  #表示允許瀏覽器快取5秒(僅是允許,瀏覽器是否快取還看瀏覽器本身機制是否要遵循這套快取協定)

  Net 封裝好了一個快取特性,如:

    public class HomeController : ControllerBase
    {
        [ResponseCache(Duration = 5)]//告訴瀏覽器可用快取5秒,Duration必須定義,不然請求會報錯
        [HttpGet("GetNowDateTime")]
        public string GetNowDateTime()
        {
            return DateTime.Now.ToString();
        }
    }

  執行多次請求

  

  第一行為首次請求,Response Headers:

cache-control: public,max-age=5   #告訴瀏覽器進行快取
content-encoding: gzip
content-type: text/plain; charset=utf-8
date: Sun, 19 Mar 2023 07:17:50 GMT
server: Microsoft-IIS/10.0
vary: Accept-Encoding
x-powered-by: ASP.NET

  後續5s內的請求,都會從快取中取,Size=disk cache即為快取,取到的返回值和首次請求一致,直到過了5s,再次向伺服器發起請求。

  PS:勾選 Disable cache,發出的請求頭 Request Headers會加上

cache-control: no-cache  #不從快取中取數

2、伺服器端快取

  如果有大量使用者端存取伺服器獲取資料,僅依靠使用者端快取,還是會讓伺服器多次執行介面程式

  伺服器端快取就是為了解決這個問題

  .Net 新增伺服器快取中介軟體

//app.UseCors(); //跨域,如果有
app.UseResponseCaching();//啟動伺服器快取,位置介於這兩中介軟體之間
app.MapControllers();

  UseResponseCaching中介軟體需要和ResponseCache特性配合使用

  Api程式碼仍為

    public class HomeController : ControllerBase
    {
        [ResponseCache(Duration = 5)]//告訴瀏覽器可用快取5秒
        [HttpGet("GetNowDateTime")]
        public string GetNowDateTime()
        {
            return DateTime.Now.ToString();
        }
    }

  開啟兩個瀏覽器存取嘗試,功能實現!!!

  不過,這種伺服器快取方式十分雞肋,存在的限制太多:

a)無法解決惡意請求給伺服器帶來的壓力(Request Header帶上了cache-control: no-cache,不僅瀏覽器不讀快取,伺服器也不讀)
b)響應碼=200的Get或者Head的響應才會被快取
c)報文頭帶有Authorization、Set-Cookie等響應,不會快取

 3、記憶體快取

  記憶體快取需要自身在程式碼定義,僅針對業務層面的快取,不受請求頭影響

  新增記憶體快取服務

builder.Services.AddScoped<IDBHelper, SqlServerHelper>(); //db注入
builder.Services.AddMemoryCache();//記憶體快取

  模擬SqlServerHelper類

namespace DIDemo.Services
{
    public record Staff(int Id, string acc);
    public class SqlServerHelper : IDBHelper
    {
        List<Staff> _staff_list = new List<Staff>();
        public SqlServerHelper() {
            _staff_list.Add(new Staff(1,"tom"));
            _staff_list.Add(new Staff(2,"jerry"));
        }public Staff? GetStaff(int Id)
        {
            return this._staff_list.Find(x => x.Id == Id);
        }
    }
}

  定義一個查詢員工的介面

using DIDemo.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;

namespace DIDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        private readonly IDBHelper _db;
        private readonly IMemoryCache _menCache;

        public HomeController(IDBHelper db, IMemoryCache menCache)
        {
            _db = db;
            _menCache = menCache;
        }

        [HttpGet("GetStaff")]
        public  ActionResult<Staff> GetStaff(int Id)
        {
            Console.WriteLine("begin");
            //1、從快取取資料   2、快取取不到,從資料庫取,新增快取
            var items =  _menCache.GetOrCreate<Staff>($"staff_{Id}",  (e) =>
            {
                Console.WriteLine("快取不存在,開始寫快取");
                e.AbsoluteExpirationRelativeToNow= TimeSpan.FromSeconds(15); //15s後過期
                e.SlidingExpiration = TimeSpan.FromSeconds(5); //5s滑動過期:5s記憶體取過快取,會重新開始計算5s
                return _db.GetStaff(Id);
            });
            if (items == null)
            {
                return NotFound($"員工ID={Id},不存在");
            }
            return items;
        }
    }
}

  GetOrCreate,如果獲取不到,就通過委託,查詢資料庫並寫入快取

  AbsoluteExpirationRelativeToNow:固定的過期時間

  SlidingExpiration:滑動過期時間

  兩個時間可用單獨定義,也可以一起定義,其中一個過期即為過期,通常不單獨使用SlidingExpiration,可能造成快取無限續命

4、分散式快取

  涉及微服務,負載均衡,需要一個集中管理的快取服務,也就是我們的分散式快取

  老生常談的Redis,看看以前寫的文章吧!

  c# 使用 Redis   快取Redis