性能调优实战指南
大约 11 分钟约 3326 字
性能调优实战指南
简介
ASP.NET Core 应用的性能调优涵盖服务器配置、内存管理、异步优化和诊断工具使用。理解 Kestrel 配置、GC 调优、Span/Memory 使用和性能分析工具,有助于构建高吞吐低延迟的 API 服务。
特点
Kestrel 服务器调优
连接与线程池配置
// Kestrel 配置
builder.WebHost.ConfigureKestrel(options =>
{
// 端点配置
options.ListenAnyIP(8080, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
// 限制配置
options.Limits.MaxConcurrentConnections = 1000;
options.Limits.MaxConcurrentUpgradedConnections = 1000;
options.Limits.MaxRequestBodySize = 10 * 1024 * 1024; // 10MB
// 请求超时
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(2);
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(30);
options.Limits.HeadersReadTimeout = TimeSpan.FromSeconds(30);
// HTTP/2 配置
options.Limits.Http2.MaxStreamsPerConnection = 100;
options.Limits.Http2.InitialConnectionWindowSize = 1024 * 1024;
options.Limits.Http2.InitialStreamWindowSize = 1024 * 1024;
// HTTP/3
options.Limits.Http3.MaxRequestHeaderFieldSize = 8192;
});
// 线程池配置
ThreadPool.SetMinThreads(200, 200); // 最小线程数
ThreadPool.GetMinThreads(out var minWorker, out var minIO);
ThreadPool.GetMaxThreads(out var maxWorker, out var maxIO);
// 运行时配置(runtimeconfig.json 或环境变量)
// DOTNET_ThreadPool_WorkerCount=200
// DOTNET_gcServer=1
// DOTNET_gcConcurrent=1
// DOTNET_gcHeapHardLimit=1073741824 // 1GB 硬限制
// DOTNET_gcHeapCount=4 // 4 个堆(Server GC)GC 调优
GC 模式选择
// GC 模式:
// Workstation GC — 单堆,适合客户端和低内存场景
// Server GC — 多堆(每核一个),适合服务端高吞吐
// Non-Concurrent GC — 暂停所有线程进行回收
// Background GC — 并发回收,减少暂停时间
// .csproj 配置
// <PropertyGroup>
// <ServerGarbageCollection>true</ServerGarbageCollection>
// <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
// <RetainVMGarbageCollection>true</RetainVMGarbageCollection>
// </PropertyGroup>
// 环境变量配置
// DOTNET_gcServer=1 // Server GC
// DOTNET_gcConcurrent=1 // 并发 GC
// DOTNET_gcRetainVM=1 // 保留 VM(不释放给 OS)
// DOTNET_GCHeapHardLimit=1C0000000 // 堆硬限制(十六进制字节数)
// DOTNET_GCHeapHardLimitPercent=80 // 堆硬限制(百分比)
// DOTNET_GCHeapCount=4 // 堆数量
// 运行时检查 GC 配置
var gcInfo = GC.GetGCMemoryInfo();
Console.WriteLine($"GC Generation: {GC.MaxGeneration}");
Console.WriteLine($"Heap Size: {gcInfo.HeapSizeBytes / 1024 / 1024}MB");
Console.WriteLine($"Committed: {gcInfo.CommittedBytes / 1024 / 1024}MB");
Console.WriteLine($"Available: {gcInfo.TotalAvailableMemoryBytes / 1024 / 1024}MB");
Console.WriteLine($"Pause Time: {gcInfo.PauseTimePercentage}%");
Console.WriteLine($"Pinned Objects: {gcInfo.PinnedObjectsCount}");
// GC 通知(监控 GC 行为)
GC.RegisterForFullGCNotification(10, 10);
var monitorTask = Task.Run(() =>
{
while (true)
{
var status = GC.WaitForFullGCApproach();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss.fff}] Full GC 即将发生");
}
status = GC.WaitForFullGCComplete();
if (status == GCNotificationStatus.Succeeded)
{
Console.WriteLine($"[{DateTime.UtcNow:HH:mm:ss.fff}] Full GC 完成");
}
}
});内存优化
减少分配
// 1. 使用 Span<T> 和 Memory<T> 避免复制
public class SpanOptimizations
{
// 避免 string.Split 分配
public void ParseCsv(ReadOnlySpan<char> line)
{
int start = 0;
for (int i = 0; i < line.Length; i++)
{
if (line[i] == ',')
{
var field = line[start..i];
ProcessField(field);
start = i + 1;
}
}
// 最后一个字段
ProcessField(line[start..]);
}
// 使用 stackalloc 避免堆分配
public void ProcessSmallBuffer()
{
Span<byte> buffer = stackalloc byte[256]; // 栈上分配
GenerateData(buffer);
// 方法返回时自动释放,无需 GC
}
// 使用 ArrayPool 复用大数组
private readonly ArrayPool<byte> _pool = ArrayPool<byte>.Shared;
public async Task ProcessLargeDataAsync(Stream stream, CancellationToken ct)
{
var buffer = _pool.Rent(81920); // 80KB
try
{
var bytesRead = await stream.ReadAsync(buffer.AsMemory(0, 81920), ct);
ProcessData(buffer.AsSpan(0, bytesRead));
}
finally
{
_pool.Return(buffer);
}
}
// 使用 string.Create 避免中间字符串
public string FormatName(ReadOnlySpan<char> firstName, ReadOnlySpan<char> lastName)
{
return string.Create(firstName.Length + 1 + lastName.Length,
(firstName, lastName), (span, state) =>
{
state.firstName.CopyTo(span);
span[state.firstName.Length] = ' ';
state.lastName.CopyTo(span[(state.firstName.Length + 1)..]);
});
}
}
// 2. 避免闭包分配
public class ClosureOptimization
{
// ❌ 闭包分配(每次创建委托+闭包对象)
public void BadExample(List<int> numbers, int threshold)
{
var filtered = numbers.Where(n => n > threshold).ToList();
}
// ✅ 使用静态委托或值捕获
public void GoodExample(List<int> numbers, int threshold)
{
// List 本身接受本地函数(无闭包)
var filtered = numbers.FindAll(n => n > threshold);
}
}
// 3. 对象池复用
public class ObjectPool<T> where T : class, new()
{
private readonly ConcurrentBag<T> _objects = new();
private readonly int _maxSize;
private int _currentCount;
public ObjectPool(int maxSize = 100) => _maxSize = maxSize;
public T Get() => _objects.TryTake(out var item) ? item : new T();
public void Return(T item)
{
if (Interlocked.Increment(ref _currentCount) <= _maxSize)
{
_objects.Add(item);
}
else
{
Interlocked.Decrement(ref _currentCount);
}
}
}
// 使用 ObjectPool<System.Text.Json.Utf8JsonWriter>
public class JsonWriterPool
{
private readonly ObjectPool<Utf8JsonWriter> _pool = new();
public byte[] Serialize<T>(T data, JsonSerializerOptions options)
{
var writer = _pool.Get();
try
{
using var ms = new MemoryStream();
writer.Reset(ms);
JsonSerializer.Serialize(writer, data, options);
writer.Flush();
return ms.ToArray();
}
finally
{
_pool.Return(writer);
}
}
}异步优化
ValueTask 与避免阻塞
// 1. ValueTask<T> — 避免同步场景的 Task 分配
public class AsyncOptimizations
{
private readonly ConcurrentDictionary<string, string> _cache = new();
// 如果缓存命中(同步),不分配 Task 对象
public ValueTask<string> GetValueAsync(string key)
{
if (_cache.TryGetValue(key, out var value))
{
return new ValueTask<string>(value); // 同步返回,零分配
}
return new ValueTask<string>(LoadFromDatabaseAsync(key)); // 异步
}
private async Task<string> LoadFromDatabaseAsync(string key)
{
await Task.Delay(100); // 模拟 IO
var value = $"value-{key}";
_cache[key] = value;
return value;
}
}
// 2. 避免异步中的阻塞
public class AvoidBlocking
{
// ❌ .Result 阻塞
public string BadMethod()
{
return httpClient.GetAsync("/api/data").Result; // 死锁风险
}
// ✅ 全链路异步
public async Task<string> GoodMethodAsync()
{
var response = await httpClient.GetAsync("/api/data");
return await response.Content.ReadAsStringAsync();
}
// ❌ Task.Run 包装同步方法(浪费线程)
public async Task<string> AntiPatternAsync()
{
return await Task.Run(() => ComputeHash("data")); // 不必要
}
// ✅ 直接返回同步结果
public Task<string> BetterPatternAsync()
{
return Task.FromResult(ComputeHash("data"));
}
// ✅ 或者 ValueTask
public ValueTask<string> BestPattern()
{
return new ValueTask<string>(ComputeHash("data"));
}
}
// 3. ConfigureAwait 的使用
// ASP.NET Core 不需要 ConfigureAwait(false)
// 因为 SynchronizationContext 为 null
// 但库代码建议添加(跨平台兼容)
// 4. 并行异步
public async Task<List<UserDto>> GetUsersParallelAsync(List<int> userIds)
{
// 并行发起所有请求
var tasks = userIds.Select(id => GetUserAsync(id));
var users = await Task.WhenAll(tasks);
return users.ToList();
}
// 限制并发数
public async Task<List<UserDto>> GetUsersThrottledAsync(List<int> userIds, int maxConcurrency = 10)
{
using var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = userIds.Select(async id =>
{
await semaphore.WaitAsync();
try { return await GetUserAsync(id); }
finally { semaphore.Release(); }
});
return (await Task.WhenAll(tasks)).ToList();
}诊断工具
dotnet-trace 与 dotnet-dump
# 1. dotnet-trace — 性能分析
# 安装
dotnet tool install --global dotnet-trace
# 收集 CPU 性能数据(30 秒)
dotnet-trace collect -p <PID> --profile cpu-sampling --duration 00:00:30
# 收集 GC 事件
dotnet-trace collect -p <PID> --profile gc-verbose --duration 00:00:60
# 自定义提供者
dotnet-trace collect -p <PID> \
--providers Microsoft-DotNETCore-SampleProfiler,Microsoft-Windows-DotNETRuntime:0x1:Verbose
# 用 Speedscope 或 PerfView 分析 .nettrace 文件
# 2. dotnet-dump — 内存分析
dotnet tool install --global dotnet-dump
# 收集 dump
dotnet-dump collect -p <PID>
# 分析 dump
dotnet-dump analyze core_20240115.dump
# 常用命令:
# dumpheap -stat — 查看堆上对象统计
# dumpheap -type String — 查看特定类型
# gcroot <address> — 查看 GC 根引用
# clrstack — 查看调用栈
# eeheap -gc — 查看 GC 堆信息
# 3. dotnet-counters — 实时监控
dotnet tool install --global dotnet-counters
# 实时监控
dotnet-counters monitor -p <PID> --counters System.Runtime
# 自定义计数器
dotnet-counters monitor -p <PID> \
--counters System.Runtime[cpu-usage,working-set,gc-heap-size,gen-0-gc-count,gen-1-gc-count,gen-2-gc-count,threadpool-thread-count]自定义性能计数器
// .NET 8 自定义指标
public class ApiMetrics
{
private readonly Counter<long> _requestCounter;
private readonly Histogram<double> _requestDuration;
private readonly UpDownCounter<int> _activeRequests;
public ApiMetrics(IMeterFactory meterFactory)
{
var meter = meterFactory.Create("MyApp.Api");
_requestCounter = meter.CreateCounter<long>("api.requests.total", "次");
_requestDuration = meter.CreateHistogram<double>("api.request.duration", "ms");
_activeRequests = meter.CreateUpDownCounter<int>("api.requests.active");
}
public void RequestStart(string method, string path)
{
_activeRequests.Add(1);
_requestCounter.Add(1,
new KeyValuePair<string, object?>("method", method),
new KeyValuePair<string, object?>("path", path));
}
public void RequestEnd(string method, string path, double durationMs, int statusCode)
{
_activeRequests.Add(-1);
_requestDuration.Record(durationMs,
new KeyValuePair<string, object?>("method", method),
new KeyValuePair<string, object?>("path", path),
new KeyValuePair<string, object?>("status", statusCode));
}
}
// 中间件集成
app.Use(async (context, next) =>
{
var metrics = context.RequestServices.GetRequiredService<ApiMetrics>();
var stopwatch = Stopwatch.StartNew();
metrics.RequestStart(context.Request.Method, context.Request.Path);
await next(context);
stopwatch.Stop();
metrics.RequestEnd(context.Request.Method, context.Request.Path,
stopwatch.Elapsed.TotalMilliseconds, context.Response.StatusCode);
});
// OpenTelemetry 导出
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMeter("MyApp.Api")
.AddPrometheusExporter());优点
缺点
总结
Kestrel 调优包括连接限制、超时配置和 HTTP/2 参数调整。GC 使用 Server GC + Background GC 获得最佳吞吐,通过环境变量配置堆大小限制。内存优化策略:使用 Span<T> 避免复制、ArrayPool<T> 复用数组、ObjectPool<T> 复用对象。异步优化使用 ValueTask<T> 减少同步场景分配,避免 .Result 阻塞和 Task.Run 滥用。诊断使用 dotnet-trace 收集性能数据,dotnet-dump 分析内存问题,dotnet-counters 实时监控。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
适用场景
- 当你准备把《性能调优实战指南》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《性能调优实战指南》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《性能调优实战指南》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《性能调优实战指南》最大的收益和代价分别是什么?
中间件管道优化
中间件顺序与性能
// 中间件顺序对性能影响巨大
// 正确的顺序:异常处理 -> HSTS -> HTTPS -> 静态文件 -> 路由
var app = builder.Build();
// 1. 异常处理(最外层)
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/error");
// 2. HSTS(生产环境)
if (!app.Environment.IsDevelopment())
app.UseHsts();
// 3. HTTPS 重定向
app.UseHttpsRedirection();
// 4. 静态文件(在路由之前,避免不必要的路由匹配)
app.UseStaticFiles(new StaticFileOptions
{
// 静态文件缓存
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Append(
"Cache-Control", "public,max-age=31536000");
}
});
// 5. 响应压缩(在路由之前)
app.UseResponseCompression();
// 6. 路由
app.UseRouting();
// 7. 认证和授权
app.UseAuthentication();
app.UseAuthorization();
// 8. 限流(在端点映射之前)
app.UseRateLimiter();
// 9. 端点映射
app.MapControllers();
// 响应压缩配置
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
{
"application/json",
"application/grpc",
});
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest;
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = System.IO.Compression.CompressionLevel.Fastest;
});输出缓存与分布式缓存
// 输出缓存(.NET 7+)
builder.Services.AddOutputCache(options =>
{
options.AddBasePolicy(builder => builder.Expire(TimeSpan.FromMinutes(10)));
options.AddPolicy("Products", builder =>
builder.Expire(TimeSpan.FromMinutes(5)).Tag("products"));
options.AddPolicy("NoCache", builder => builder.NoCache());
});
app.MapGet("/api/products", async (IProductService service) =>
await service.GetAllAsync())
.CacheOutput("Products");
// 手动清除缓存
app.MapPost("/api/products/cache/clear", (IOutputCacheStore cache) =>
{
cache_evict(cache, "products");
return Results.Ok();
});
// 分布式缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("Redis");
options.InstanceName = "myapp:";
});
// 使用 IDistributedCache
public class CachedProductService
{
private readonly IDistributedCache _cache;
private readonly IProductRepository _repository;
private readonly ILogger<CachedProductService> _logger;
public CachedProductService(
IDistributedCache cache,
IProductRepository repository,
ILogger<CachedProductService> logger)
{
_cache = cache;
_repository = repository;
_logger = logger;
}
public async Task<Product?> GetByIdAsync(int id)
{
var cacheKey = $"product:{id}";
var cached = await _cache.GetStringAsync(cacheKey);
if (cached != null)
{
_logger.LogDebug("缓存命中: {Key}", cacheKey);
return JsonSerializer.Deserialize<Product>(cached);
}
_logger.LogDebug("缓存未命中: {Key}", cacheKey);
var product = await _repository.GetByIdAsync(id);
if (product != null)
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
SlidingExpiration = TimeSpan.FromMinutes(2),
};
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(product), options);
}
return product;
}
public async Task InvalidateAsync(int id)
{
await _cache.RemoveAsync($"product:{id}");
}
}HttpClient 优化
// HttpClient 正确使用方式
// 错误:每次创建新实例
// using var client = new HttpClient(); // 可能导致端口耗尽
// 正确:使用 IHttpClientFactory
builder.Services.AddHttpClient("api", client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
client.Timeout = TimeSpan.FromSeconds(30);
client.DefaultRequestHeaders.Add("Accept", "application/json");
})
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
MaxConnectionsPerServer = 100,
EnableMultipleHttp2Connections = true,
})
.AddResilienceHandler("retry", builder =>
{
builder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(200),
BackoffType = DelayBackoffType.Exponential,
UseJitter = true,
});
builder.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
SamplingDuration = TimeSpan.FromSeconds(30),
FailureRatio = 0.5,
MinimumThroughput = 10,
});
builder.AddTimeout(TimeSpan.FromSeconds(30));
});
// 类型化 HttpClient
public class ApiService
{
private readonly HttpClient _client;
private readonly ILogger<ApiService> _logger;
public ApiService(HttpClient client, ILogger<ApiService> logger)
{
_client = client;
_logger = logger;
}
public async Task<T?> GetAsync<T>(string path, CancellationToken ct)
{
var response = await _client.GetAsync(path, ct);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>(cancellationToken: ct);
}
}
builder.Services.AddHttpClient<ApiService>(client =>
{
client.BaseAddress = new Uri("https://api.example.com/");
});批量操作与并行处理优化
/// <summary>
/// 批量操作优化 — 减少数据库往返
/// </summary>
public class BulkOperationService
{
private readonly AppDbContext _context;
public BulkOperationService(AppDbContext context) => _context = context;
// 批量插入(EF Core 7+)
public async Task BulkInsertAsync(List<Product> products, CancellationToken ct)
{
await _context.Products.AddRangeAsync(products, ct);
await _context.SaveChangesAsync(ct);
}
// 批量更新(EF Core 7+ ExecuteUpdate)
public async Task BulkUpdatePriceAsync(decimal increasePercent, CancellationToken ct)
{
await _context.Products
.Where(p => p.IsActive)
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.Price, p => p.Price * (1 + increasePercent / 100)),
ct);
}
// 批量删除(EF Core 7+ ExecuteDelete)
public async Task BulkDeleteInactiveAsync(CancellationToken ct)
{
await _context.Products
.Where(p => !p.IsActive && p.LastModified < DateTime.UtcNow.AddMonths(-6))
.ExecuteDeleteAsync(ct);
}
// 分批处理大数据集
public async Task ProcessInBatchesAsync<T>(
IQueryable<T> query,
int batchSize,
Func<List<T>, Task> processBatch,
CancellationToken ct)
{
var skip = 0;
while (true)
{
var batch = await query.Skip(skip).Take(batchSize).ToListAsync(ct);
if (batch.Count == 0) break;
await processBatch(batch);
skip += batchSize;
}
}
}
// 并行处理 — 控制并发数
public async Task<List<Result>> ProcessParallelAsync(
List<Input> inputs,
int maxConcurrency,
CancellationToken ct)
{
using var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = inputs.Select(async input =>
{
await semaphore.WaitAsync(ct);
try
{
return await ProcessSingleAsync(input, ct);
}
finally
{
semaphore.Release();
}
});
return (await Task.WhenAll(tasks)).ToList();
}