.net 溫故知新【14】:Asp.Net Core WebAPI 快取

2023-11-24 15:01:42

一、快取

快取指在中間層中儲存資料的行為,該行為可使後續資料檢索更快。 從概念上講,快取是一種效能優化策略和設計考慮因素。 快取可以顯著提高應用效能,方法是提高不常更改(或檢索成本高)的資料的就緒性。

二、RFC9111

在最新的快取控制規範檔案RFC9111中,詳細描述了瀏覽器快取和伺服器快取控制的規範,其中有一個最重要的響應報文頭Cache-Control

該報文頭的設定會影響我們的快取,包括瀏覽器端和伺服器端。

RFC911:https://www.rfc-editor.org/rfc/rfc9111

三、網頁端快取

Cache-Control中,如果設定max-age=10,則表示告訴瀏覽器快取10s,而為什麼瀏覽器要認這個表示呢,就是上面我們說的前後端都要根據RFC標準規範去實現,就是硬體的統一插口,不然其他生成出來的就用不了。

那麼在Asp.net Core 中只需要在介面上打上ResponseCacheAttribute並設定max-age的時間即可。

首先建一個Asp.Net Core WebAPI 專案,寫一個獲取學生的Get介面。

namespace WebAPI_Cache.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class CacheController : ControllerBase
    {

        public CacheController()
        {

        }

        [HttpGet]
        public ActionResult<Student> GetStudent()
        {

            return new Student()
            {
                Id = 1,
                Name = "Test",
                Age = Random.Shared.Next(0, 100),
            };
        }
    }
}
namespace WebAPI_Cache.Model
{
    public class Student
    {
        public int Id { get; set; }
        public string? Name { get; set; }
        public int Age { get; set; }
    }
}

在介面中我返回Studentage為1-100的亂數。啟動專案測試,短時間內兩次呼叫返回的age不一樣

第一次age:

第二次age:

當我在介面方法打上[ResponseCache(Duration = 10)],再次呼叫介面返回的資訊可以看到已經有了cache-control: public,max-age=10 的Header。

並且我在10秒內的請求,只有第一次請求過伺服器,其他都是從快取中取的,檢視edge瀏覽器網路存取如下:

四、伺服器快取

網頁端快取是放在瀏覽器端的,對於單點請求會有用,但是如果是多個不同前端請求呢。這個時候我們可以將快取放置在後端服務中,在ASP.NET Core 中設定響應快取中介軟體。

在 Program.cs中,將響應快取中介軟體服務 AddResponseCaching 新增到服務集合,並設定應用,如果使用 CORS 中介軟體時,必須在 UseResponseCaching 之前呼叫 UseCors。

如果header包含 Authorization,Set-Cookie 檔頭,也不會快取,因為這些使用者資訊快取會引起資料混亂。

然後對於我們需要伺服器快取的介面打上ResponseCache屬性,和設定瀏覽器快取一樣,還有其他引數可設定。我們通過兩個程序來測試,一個用瀏覽器swagger,一個用postman,可以看到兩個請求的age都是等於18的。所以可以確定伺服器端確實存在快取。

但是在用postman測試的時候記得在settings裡面把Send no-cache header勾掉,如果不去掉,傳送的時候就會在請求頭裡麵包含Cache-Control:no-cache,這樣伺服器端即便有快取也不會使用快取。

對於瀏覽器端相當於禁用快取,如果禁用了快取,傳送的請求頭也會帶上Cache-Control:no-cache,伺服器端看到no-cache 後便不會再使用快取進行響應。

而這個約定就是RFC9111的規範,所以這個後端快取策略比較雞肋,如果使用者禁用快取就沒用了,因此我們還可以使用記憶體快取。

五、記憶體快取

記憶體快取基於 IMemoryCache。 IMemoryCache 表示儲存在 Web 伺服器記憶體中的快取。

  • 首先Nuget安裝包
Install-Package Microsoft.Extensions.Caching.Memory
  • 在Program.cs中新增依賴
builder.Services.AddMemoryCache();
  • 快取資料
    我新增一個Post方法模擬id查詢Student

這樣我就將資料快取到了記憶體,可以設定快取的絕對過期時間,也可以設定滑動過期,稍後我們會看到過期策略的使用。

六、快取擊穿

快取擊穿是指熱點key在某個時間點過期的時候,而恰好在這個時間點對這個Key有大量的並行請求過來,或者是查詢了不存在的資料,快取裡面沒有,從而大量的請求打到資料庫上形成資料庫壓力。

上面記憶體快取中的寫法我們可以看到,如果查詢快取等於null就會再去查詢資料(我這裡只是模擬,沒有去寫真的資料庫查詢),如果這樣暴力請求攻擊就會有問題。

對於這個問題我們可以使用ImemoryCacheGetOrCreate方法,當然它還有非同步方式。通過該方法傳入快取的key和func 委託方法返回值來進行查詢並快取,如果沒查詢到返回的null也會儲存在快取中,防止惡意查詢不存在的資料。

        [HttpPost]
        public ActionResult<Student> GetStudent2(int id)
        {
            //查詢並建立快取
            var student = _memoryCache.GetOrCreate("student_" + id, t =>
            {
                t.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(20);
                //模擬只有id=1有資料
                if (id == 1)
                {
                    return new Student()
                    {
                        Id = 1,
                        Name = "Test",
                        Age = Random.Shared.Next(0, 100),
                    };
                }
                else
                {
                    //其他的返回空,但是空值也會快取,比如查詢 id=2,id=3 都會快取
                    return null;
                }

            });
            if (student == null)
            {
                return NotFound("未找到");
            }
            else
            {
                return student;
            }
        }

七、快取雪崩

快取雪崩是指快取中資料大批次到過期時間,導致所有請求都會去查資料庫,而查詢資料量巨大,引起資料庫壓力過大甚至down機。

對於雪崩情況我們對快取的策略主要是設定過期時間,部分不重要的站點,比如新聞網站我們將絕對過期時間AbsoluteExpiration設定的久一點。

對於要一定靈活性,能在請求不頻繁的時候進行失效以更新資料的,我們可以用滑動過期時間,就是如果頻繁請求就一值滑動過期時間。

當然為了避免滑動時間一直不過期,還可以兩種方式混合使用。上面的例子,我們設定絕對過期時間是20秒,我們將滑動過期設定5秒,在5秒內有持續存取就一直續命,直到20秒絕對過期。

那麼如果沒人存取,在5秒後就過期了,這樣資料下次存取也能及時查詢最新資料。

八、分散式快取

有了上面的快取方案,對付一些小的簡單業務系統完全夠用了,但是如果你是分散式部署服務,那麼像記憶體快取存取的資料就是單個伺服器的快取。

你可能需要多個伺服器的請求之間保持一致、在進行伺服器重啟和應用部署後仍然有效、不使用本地記憶體等情況。

這個時候我們可以使用第三方快取,比如memecache,Redis等。Asp.Net Core 使用 IDistributedCache 介面與快取進行互動。

  • NuGet安裝包
Install-Package Microsoft.Extensions.Caching.StackExchangeRedis
  • 在 Program.cs 中註冊 IDistributedCache 實現

Configuration: 為連線設定。
InstanceName: 為儲存鍵字首。

編寫測試方法GetStuden3

IDistributedCache 接受字串鍵並以 byte[] 陣列的形式新增或檢索快取項,所以資料是以byte[]形式存取,但是擴充套件了一個string型別的方法可以進行使用,我這裡用字串進行操作。

以上這些就是關於asp.net core 當中使用快取的重要點和基礎使用方法,詳細引數和檔案可參看官方檔案:ASP.NET Core 中的快取概述