這篇文章的主要內容來源於.NET文檔,此處翻譯前4條內容,其他內容會陸續貼出來
- 積極使用緩存
- 明確”熱代碼路徑”
- 避免使用阻塞調用
- 返回值使用IEnumerable<T> 還是 IAsyncEnumerable<T>?
積極使用緩存
詳情請查看:ASP.NET Core 中的響應緩存.
asp.net core 中的幾類緩存:
1.響應緩存(輸出緩存) ResponseCache
響應緩存本質上是控制響應數據Header頭中的Cache-Control,從而設置瀏覽器對Http請求的緩存。
有兩種實現方式:
1.使用響應緩存特性:ResponseCacheAttribute
下面的示例表示響應緩存60秒
[ResponseCache(Duration = 60)]
下面的示例表示不使用響應緩存,即響應Header頭為:Cache-Control:no-store,no-cache
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
2.使用響應緩存中間件 (可全局設定緩存策略)
// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddResponseCaching(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); }
app.UseResponseCaching(); app.Use(async (context, next) => { context.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue() { Public = true, MaxAge = TimeSpan.FromSeconds(10) }; context.Response.Headers[Microsoft.Net.Http.Headers.HeaderNames.Vary] = new string[] { "Accept-Encoding" }; await next(); }); }
注意:如果跨域中間件和響應緩存中間件共同使用的話,那么跨域中間件要在前面。
使用響應緩存設置緩存策略,不僅可以告知客戶端緩存響應數據,也可以明確告知客戶端不要緩存數據(Cache-Control:no-store,no-cache)。
2.內存緩存 IMemoryCache
在Startup中注入內存緩存依賴
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(option => { option.CompactionPercentage = 0.1; option.SizeLimit = 1000; }); } }
MemoryCacheOptions的解釋:
SizeLimit:緩存的最大大小。
CompactionPercentage:表示超出緩存最大大小時,需要壓縮的緩存比例。
ExpirationScanFrequence:表示掃描過期項的時間間隔。(注意:只有在訪問緩存之后才會觸發)
使用內存緩存
public class HomeController : Controller { private readonly IMemoryCache _cache; public HomeController(IMemoryCache cache) { _cache = cache; } public IActionResult Index() { _cache.TryGetValue("Test", out object value); if (value == null) { value = "Hello"; _cache.Set("Test", value); } this.ViewBag.Test = value; return View(); } }
注意事項:緩存到達失效時間,並不會被立即處理,因為並沒有對緩存失效進行判斷的定時器。
第一種情況:當緩存再次被訪問( (Get
, Set
, Remove
))時,會觸發失效掃描。
第二種情況:使用CancellationTokenSource,當CancellationTokenSource失效時,自動觸發緩存的失效。
3.分布式緩存 IDistributedCache
.NET Core中實現了IDistributedCache接口的四個分布式緩存:
1.分布式內存緩存
內存緩存,本質上並不是分布式緩存,主要用於在開發,測試階段使用。
services.AddDistributedMemoryCache();
2.分布式SQLServer緩存
使用SQLServer數據庫作為數據存儲,用以提供kv存儲的分布式服務
services.AddDistributedSqlServerCache(options => { options.ConnectionString = _config["DistCache_ConnectionString"]; options.SchemaName = "dbo"; options.TableName = "TestCache"; });
3.分布式緩存Redis
首先安裝程序包:Microsoft.Extensions.Caching.StackExchangeRedis
public void ConfigureServices(IServiceCollection services) { services.AddStackExchangeRedisCache(options => { options.Configuration = "localhost:6379"; }); }
4.分布式緩存NCache
NCache是一個為.NET應該程序開發的,非常快,非常穩定的,開放源代碼的分布式緩存。(GitHub上的解釋)
4.對象池 ObjectPool
明確應用的”熱代碼路徑”
”熱代碼路徑”的定義為訪問頻繁並且耗時較長的代碼。”熱代碼”會限制應用對水平擴展,並且對性能影響很明細,這個問題在文檔的后續部分也會多次提及。
我認為叫“關鍵路徑”更貼切,這一點的本質是找出系統中的性能瓶頸,確定對性能影響最大的代碼,然后加以解決。
避免使用阻塞調用
ASP.NET Core 程序應該被設計成同時處理多個請求。異步API使用一個小線程池可以處理上千個並發請求,而不會阻塞。這樣請求線程可以去處理其他請求,而不是等待一個長時同步任務完成。
ASP.NET Core 程序的一個常見的性能問題是,阻塞了本該異步執行的調用。很多同步調用會導致線程池飢餓,增大相應時間。
不要像下面這樣做:
- 通過 Task.Wait 或 Task.Result 阻塞異步執行。
- 在常用代碼上使用鎖(lock)。ASP.NET Core 程序被設計為並行架構時執行效率最高。
- 自己定義一個 Task.Run ,然后去await 。由於ASP.NET Core 程序已經是在通用線程池上運行了,調用 Task.Run 是沒有必要的,
應該這樣做:
- 異步調用“熱代碼”。
- 在訪問數據,I/O,長時操作的時候,如果有異步API就盡量使用異步API。不要使用Task.Run將同步API異步執行。
- 使Controller或Razor Page中的Action異步化。將整個調用棧異步化,以便使用 async/await。
性能分析器 PerfView,可以用來查找頻繁加入線程池的線程。
Microsoft-Windows-DotNETRuntime/ThreadPoolWorkerThread/Start 表明一個線程加入了線程池。
返回IEnumerable<T> 還是 IAsyncEnumerable<T>
如果Action Result返回IEnumerable<T>,那么序列化器以同步的方式處理集合迭代,這樣阻塞調用可能會導致線程池飢餓,為避免同步迭代,可在返回迭代之前調用 ToListAsync()。
從ASP.NET Core 3.0開始,IAsyncEnumerable<T>作為異步枚舉,可取代IEnumerable<T>。