為什么這個(gè)API慢得離譜?從400ms到40ms的.NET 9性能優(yōu)化實(shí)戰(zhàn)
和其他“簡單”的性能抱怨一樣,這個(gè)故事始于一個(gè)看似普通的性能問題。我們的.NET 9 Minimal API擁有所有時(shí)髦的特性——輕量級(jí)、快速啟動(dòng)、簡潔的端點(diǎn)。但在生產(chǎn)環(huán)境中?平均延遲高達(dá)400毫秒。
這還發(fā)生在熱路徑上。一個(gè)GET請(qǐng)求。甚至沒有數(shù)據(jù)庫調(diào)用。
作為長期使用C#的開發(fā)者,我能感覺到事情不對(duì)勁。于是我打開性能分析器,準(zhǔn)備深入調(diào)查。隨后便陷入了一系列“無害”中間件、不當(dāng)使用的HttpClient和出乎意料的異步開銷的迷宮。
經(jīng)過兩天殘酷的優(yōu)化,我們將這個(gè)API的中位延遲降低到了約40毫秒。本文記錄了每一個(gè)關(guān)鍵的修復(fù)步驟、基準(zhǔn)測(cè)試和代碼調(diào)整。
如果你在生產(chǎn)環(huán)境運(yùn)行.NET 9 API,這可能是你這周最有價(jià)值的10分鐘閱讀。
第一步:先分析,別猜測(cè)
在深入代碼之前,重要提醒:不要盲目優(yōu)化。我使用了以下工具:
? dotnet-trace:追蹤GC壓力和方法級(jí)性能
? dotnet-counters:監(jiān)控CPU、分配率和請(qǐng)求吞吐量
? JetBrains Rider Profiler:深入分析調(diào)用棧和慢端點(diǎn)
這是我立即發(fā)現(xiàn)的問題:
[400ms總延遲]└── 120ms: 中間件(自定義日志、CORS、指標(biāo)收集)└── 80ms: JSON序列化└── 60ms: HttpClient實(shí)例化(??)└── 40ms: GC暫停(分配密集型代碼)└── 100ms: 實(shí)際處理邏輯
現(xiàn)在我們來逐一解決。
第二步:無情削減中間件
我們喜歡可觀測(cè)性,但在Minimal API中,中間件的成本是真實(shí)存在的。
原來代碼:
app.Use(async (context, next) => {
var sw = Stopwatch.StartNew();
await next();
logger.LogInformation($"Request took {sw.ElapsedMilliseconds}ms");
});
app.UseCors(...);
app.Use(async (context, next) => {
metrics.Increment("api_requests");
await next();
});問題:每個(gè)Use都增加異步開銷,Stopwatch增加每次請(qǐng)求的分配
? 修復(fù):
? 使用Middleware類替代內(nèi)聯(lián)中間件(減少lambda捕獲)
? 通過OpenTelemetry將日志和指標(biāo)推送到ActivityListener
? 內(nèi)部API完全移除CORS
效果:節(jié)省約80ms
第三步:重用你的HttpClient
這個(gè)有點(diǎn)尷尬。在我們的處理程序中:
app.MapGet("/data", async () => {
using var client = new HttpClient();
var result = await client.GetStringAsync("https://internal-api/data");
return Results.Ok(result);
});經(jīng)典新手錯(cuò)誤:每次請(qǐng)求都銷毀HttpClient會(huì)殺死socket復(fù)用
? 修復(fù):
var httpClient = new HttpClient(new SocketsHttpHandler {
PooledConnectionLifetime = TimeSpan.FromMinutes(5)
});
app.MapGet("/data", async () => {
var result = await httpClient.GetStringAsync("https://internal-api/data");
return Results.Ok(result);
});或者更推薦使用IHttpClientFactory(如果需要策略)
效果:節(jié)省約60ms,負(fù)載下CPU降低12%
第四步:異步并不總是免費(fèi)的
有個(gè)誤區(qū):異步=快速。并非總是如此。
如果你的端點(diǎn)不需要等待I/O(比如從內(nèi)存讀?。?,異步只會(huì)增加上下文切換和額外分配。
我們的“健康檢查”端點(diǎn)原來是這樣的:
app.MapGet("/health", async () => {
return Results.Ok("Healthy");
});? 修復(fù):直接改為同步
app.MapGet("/health", () => Results.Ok("Healthy"));僅此一項(xiàng)就節(jié)省了約20ms(避免了異步狀態(tài)機(jī))
第五步:精簡JSONSystem.Text.Json很快——但需要正確配置。
我們使用了默認(rèn)設(shè)置,會(huì)序列化所有內(nèi)容:包括null值和不需要的巨大DTO屬性。
? 修復(fù):
? 使用[JsonIgnore]或創(chuàng)建精簡DTO
? 全局配置JSON:
builder.Services.Configure<JsonOptions>(options =>
{
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
});? 序列化大型已知結(jié)構(gòu)時(shí)使用源生成器
效果:節(jié)省約30ms,響應(yīng)大小減少18%
第六步:啟用響應(yīng)壓縮(但非必需)GZip有幫助——除非你大規(guī)模壓縮300字節(jié)的有效負(fù)載。
我們?nèi)謫⒂昧藟嚎s。這是個(gè)壞主意。對(duì)于小負(fù)載,這是CPU浪費(fèi)。
? 修復(fù):
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] {
"application/json"
});
});然后在邏輯中:
app.UseWhen(ctx => ctx.Request.Path.StartsWithSegments("/big"), builder =>
{
builder.UseResponseCompression();
});現(xiàn)在只壓縮大型JSON,跳過其他內(nèi)容
效果:平均節(jié)省15-20ms
額外技巧:預(yù)熱JIT,緩存一切這在負(fù)載測(cè)試前并不明顯
? 添加[PreJIT]路由或在啟動(dòng)時(shí)通過虛擬請(qǐng)求強(qiáng)制預(yù)熱
? 在單例或作用域服務(wù)中緩存查找和配置讀取
? 將靜態(tài)數(shù)據(jù)移入內(nèi)存——如枚舉或只讀參考數(shù)據(jù)
app.Lifetime.ApplicationStarted.Register(() =>
{
_ = httpClient.GetStringAsync("https://internal-api/warmup");
});這些小調(diào)整又額外節(jié)省了10-15ms
最終基準(zhǔn)測(cè)試:優(yōu)化前后[圖片:優(yōu)化前后性能對(duì)比圖表]
結(jié)語:性能不是偶然Minimal API很快——但前提是你要像對(duì)待F1賽車而不是家用轎車那樣對(duì)待它們。
每一層都很重要。每次分配都會(huì)累積。每個(gè)中間件、序列化器配置和異步調(diào)用都會(huì)引入摩擦。
事實(shí)上,這篇文章不是關(guān)于“巧妙技巧”,而是關(guān)于殘酷的專注和對(duì)抽象成本的尊重。
如果你正在大規(guī)模部署API,性能分析不是可選的。優(yōu)化不是過早的。延遲就是一個(gè)功能特性。
TL;DR 檢查清單
? 移除或合并中間件
? 使用連接池重用HttpClient
? 在不需要的地方避免異步
? 精簡JSON輸出并使用源生成器
? 選擇性添加壓縮
? 預(yù)熱JIT并積極緩存
如果這對(duì)你有幫助,請(qǐng)點(diǎn)贊并與你的團(tuán)隊(duì)分享。如果你在.NET Minimal API中遇到性能瓶頸,我很想知道你是如何解決的——或者你仍然卡在哪里。

































