Arash Zand

API 延遲在效能與使用者體驗中扮演關鍵角色。高延遲會讓使用者感到挫折、降低可擴展性,並增加基礎設施成本。本指南深入探討 C# .NET API 的成因、量測與最佳化策略。


1. 了解 API 延遲

API 延遲是指從客戶端發送請求到接收回應的總時間,涵蓋網路傳輸、伺服器端處理以及資料庫互動。

延遲類型:

  • 網路延遲 — 距離、頻寬、壅塞。解決方式:CDN。
  • 處理延遲 — 程式碼效率低、阻塞操作。解決方式:非同步程式設計。
  • 資料庫延遲 — 查詢緩慢、缺少索引。解決方式:查詢最佳化、快取、連線池。
Client → (Network) → API Gateway → (Processing) → Database → (DB latency) → API Gateway → Client

Enter fullscreen mode Exit fullscreen mode


2. 量測 API 延遲

Stopwatch — 快速且精準:

var stopwatch = Stopwatch.StartNew();
await ProcessRequestAsync();
stopwatch.Stop();
Console.WriteLine($"Elapsed: {stopwatch.ElapsedMilliseconds} ms");

Enter fullscreen mode Exit fullscreen mode

中介軟體計時 — 涵蓋所有請求:

public class LatencyMiddleware
{
    private readonly RequestDelegate _next;

    public LatencyMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        await _next(context);
        sw.Stop();
        Console.WriteLine($"Request latency: {sw.ElapsedMilliseconds} ms");
    }
}

Enter fullscreen mode Exit fullscreen mode

Program.cs 註冊:

app.UseMiddleware<LatencyMiddleware>();

Enter fullscreen mode Exit fullscreen mode

Application Insights — 生產環境等級:

public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(
        Configuration["ApplicationInsights:InstrumentationKey"]);
}

Enter fullscreen mode Exit fullscreen mode

其他工具: Postman(端點測試)、JMeter(負載測試)、Grafana + Prometheus(儀表板)、Jaeger / OpenTelemetry(分散式追蹤)。


3. C# .NET 中的延遲 — 請求流程

Client → Web Server (Kestrel/IIS) → Middleware Pipeline → Controller → DB/Logic → Middleware → Response

Enter fullscreen mode Exit fullscreen mode

同步 vs 非同步 — 最大影響因素:

// ❌ Synchronous — blocks the thread
public IActionResult GetData()
{
    var data = _service.GetData();
    return Ok(data);
}

// ✅ Asynchronous — frees the thread for other requests
public async Task<IActionResult> GetDataAsync()
{
    var data = await _service.GetDataAsync();
    return Ok(data);
}

Enter fullscreen mode Exit fullscreen mode

常見瓶頸:

Bottleneck Fix
Blocking I/O async/await throughout
Slow DB queries Indexes, AsNoTracking() for reads
Heavy middleware Remove unnecessary steps, async logging
Large serialization System.Text.Json, smaller payloads

4. 最佳化 API 延遲 — 最佳實踐

Async/Await

public async Task<IActionResult> GetUserDataAsync()
{
    var data = await _databaseService.GetUserDataAsync();
    return Ok(data);
}

Enter fullscreen mode Exit fullscreen mode

記憶體內快取

public async Task<User> GetUserByIdAsync(int userId)
{
    if (!_memoryCache.TryGetValue(userId, out User user))
    {
        user = await _databaseService.GetUserByIdAsync(userId);
        _memoryCache.Set(userId, user, TimeSpan.FromMinutes(10));
    }
    return user;
}

Enter fullscreen mode Exit fullscreen mode

回應壓縮

public void Configure(IApplicationBuilder app)
{
    app.UseResponseCompression();
}

Enter fullscreen mode Exit fullscreen mode

高效序列化

private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

[HttpGet]
public IActionResult GetUser(int userId)
{
    var user = _databaseService.GetUserById(userId);
    return Content(JsonSerializer.Serialize(user, _jsonOptions), "application/json");
}

Enter fullscreen mode Exit fullscreen mode


5. 進階技巧

訊息佇列 — 卸載非緊急工作

public async Task<IActionResult> ProcessOrderAsync(Order order)
{
    await _messageQueue.SendAsync(order);
    return Accepted(); // responds immediately
}

Enter fullscreen mode Exit fullscreen mode

使用 Redis 進行分散式快取

public async Task<User> GetUserByIdAsync(int userId)
{
    var cache = _redis.GetDatabase();
    var cached = await cache.StringGetAsync(userId.ToString());

    if (!cached.IsNullOrEmpty)
        return JsonSerializer.Deserialize<User>(cached);

    var user = await _databaseService.GetUserByIdAsync(userId);
    await cache.StringSetAsync(
        userId.ToString(),
        JsonSerializer.Serialize(user),
        TimeSpan.FromMinutes(10));

    return user;
}

Enter fullscreen mode Exit fullscreen mode

資料庫分片與 HTTP/2

對於高流量系統,分片可將資料分散到多個資料庫實例。升級至 HTTP/2 或 HTTP/3 可啟用多路複用 — 單一連線上同時處理多個請求,減少握手開銷。


6. 案例研究:電商結帳 API

問題:結帳平均需要 2–3 秒。透過 Application Insights + SQL Profiler 發現:

  • 同步的庫存服務呼叫
  • orders 資料表缺少索引

採用的修正:

  1. 將庫存檢查重構為 async/await
  2. orders 資料表新增索引,最佳化庫存查詢
  3. 針對很少變動的庫存資料使用 Redis 快取

結果:平均結帳回應時間從 3 秒降至 500 毫秒以下


7. 生產環境監控

  • Azure Monitor — API 效能與資源使用率
  • Prometheus + Grafana — 即時指標與儀表板
  • New Relic — 每個端點的端到端延遲
  • Serilog — 結構化非同步記錄

設定延遲閾值警示,讓團隊能在使用者察覺問題前處理效能回歸。


結論

降低 API 延遲不是一次性的工作。核心策略包括:非同步程式設計、資料庫最佳化、快取(記憶體內與分散式)、酬載壓縮,以及中介軟體維護。搭配持續監控與負載測試,您的 .NET API 就能隨著流量成長保持快速。


Originally published on Medium.