性能调优面试题集
大约 19 分钟约 5576 字
性能调优面试题集
简介
性能调优是高级工程师的核心能力之一,涵盖后端服务、数据库、前端渲染、内存管理等多个层面。本文整理性能调优相关的经典面试题,包括 ASP.NET Core 中间件管道优化、异步编程最佳实践、数据库索引与查询优化、连接池管理、前端 Core Web Vitals、GC 调优与内存泄漏排查、性能分析工具使用等,每题配有代码示例和分析思路。
特点
ASP.NET Core 性能
题目1:中间件管道优化
问题:你的 ASP.NET Core 应用响应时间变慢,如何定位和优化中间件管道?
/// <summary>
/// 中间件性能分析与优化
/// </summary>
public class MiddlewareOptimization
{
/// <summary>
/// 问题代码:每次请求都执行不必要的操作
/// </summary>
public void BadMiddleware(WebApplication app)
{
// 问题1:异常处理中间件位置不对
app.Use(async (context, next) =>
{
try { await next(); }
catch (Exception ex) { /* 处理异常 */ }
});
// 问题2:每个请求都做同步 IO
app.Use(async (context, next) =>
{
var config = File.ReadAllText("config.json"); // 同步 IO 阻塞线程
await next();
});
// 问题3:不必要的 JSON 序列化
app.Use(async (context, next) =>
{
await next();
var log = JsonSerializer.Serialize(context.Items); // 额外序列化
Console.WriteLine(log);
});
}
/// <summary>
/// 优化后的中间件配置
/// </summary>
public void OptimizedMiddleware(WebApplication app)
{
// 1. 异常处理放在最外层
app.UseExceptionHandler("/error");
// 2. HSTS 放在靠前位置(生产环境)
app.UseHsts();
// 3. HTTPS 重定向
app.UseHttpsRedirection();
// 4. 静态文件中间件(短路,不走后续管道)
app.UseStaticFiles(new StaticFileOptions
{
ServeUnknownFileTypes = true,
DefaultContentType = "application/octet-stream",
OnPrepareResponse = ctx =>
{
// 添加缓存头
ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=600";
}
});
// 5. 响应压缩
app.UseResponseCompression();
// 6. 响应缓存
app.UseResponseCaching();
// 7. 路由
app.UseRouting();
// 8. 认证和授权
app.UseAuthentication();
app.UseAuthorization();
// 9. 自定义中间件:异步、无阻塞
app.Use(async (context, next) =>
{
var stopwatch = Stopwatch.StartNew();
await next();
stopwatch.Stop();
// 只记录慢请求
if (stopwatch.ElapsedMilliseconds > 1000)
{
// 使用结构化日志(不序列化整个 context)
LogSlowRequest(context.Request.Path, stopwatch.ElapsedMilliseconds);
}
});
// 10. 端点映射
app.MapControllers();
}
/// <summary>
/// 性能诊断中间件
/// </summary>
public class PerformanceDiagnosticMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger _logger;
public PerformanceDiagnosticMiddleware(RequestDelegate next, ILogger<PerformanceDiagnosticMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
var beforeMemory = GC.GetTotalMemory(false);
// 记录请求开始
using var activity = Diagnostics.ActivitySource.StartActivity("http-request");
activity?.SetTag("http.method", context.Request.Method);
activity?.SetTag("http.path", context.Request.Path);
try
{
await _next(context);
}
finally
{
stopwatch.Stop();
var afterMemory = GC.GetTotalMemory(false);
var memoryDelta = afterMemory - beforeMemory;
// 记录指标
Diagnostics.RequestDuration.Record(stopwatch.ElapsedMilliseconds);
Diagnostics.RequestMemory.Record(memoryDelta);
// 慢请求告警
if (stopwatch.ElapsedMilliseconds > 500)
{
_logger.LogWarning(
"慢请求: {Path} 耗时 {Ms}ms, 内存增量 {Mem}bytes, 状态码 {Status}",
context.Request.Path,
stopwatch.ElapsedMilliseconds,
memoryDelta,
context.Response.StatusCode);
}
}
}
}
private void LogSlowRequest(PathString path, long ms) { }
}
/// <summary>
/// 诊断辅助类
/// </summary>
public static class Diagnostics
{
public static System.Diagnostics.ActivitySource ActivitySource { get; } = new("MyApp");
public static System.Diagnostics.Metrics.Meter Meter { get; } = new("MyApp");
public static System.Diagnostics.Metrics.Histogram<long> RequestDuration { get; }
= Meter.CreateHistogram<long>("http.request.duration", "ms");
public static System.Diagnostics.Metrics.Histogram<long> RequestMemory { get; }
= Meter.CreateHistogram<long>("http.request.memory", "bytes");
}题目2:异步编程性能陷阱
问题:你的 ASP.NET Core 接口在高并发下响应变慢,排查发现大量线程被阻塞。如何修复?
/// <summary>
/// 异步编程性能陷阱与修复
/// </summary>
public class AsyncPerformance
{
/// <summary>
/// 陷阱1:async void(无法 await,异常无法捕获)
/// </summary>
public class Trap_AsyncVoid
{
// 错误
public async void ProcessOrder(Order order)
{
await SaveOrderAsync(order); // 如果抛异常,进程崩溃
}
// 正确
public async Task ProcessOrderAsync(Order order)
{
await SaveOrderAsync(order);
}
}
/// <summary>
/// 陷阱2:.Result 或 .Wait()(可能导致死锁)
/// </summary>
public class Trap_BlockingAsync
{
// 错误:在 ASP.NET Core 中阻塞等待
public User GetUser(int id)
{
return _userService.GetUserAsync(id).Result; // 死锁风险
}
// 正确:一路异步到底
public async Task<User> GetUserAsync(int id)
{
return await _userService.GetUserAsync(id);
}
private IUserService _userService = null!;
}
/// <summary>
/// 陷阱3:不必要的 ConfigureAwait(false)
/// </summary>
public class Trap_ConfigureAwait
{
// ASP.NET Core 没有 SynchronizationContext,不需要 ConfigureAwait(false)
// 但在类库代码中仍然推荐使用(可能在非 ASP.NET Core 环境运行)
public async Task<string> GetDataAsync()
{
var response = await _httpClient.GetStringAsync("https://api.example.com")
.ConfigureAwait(false);
return response;
}
private HttpClient _httpClient = new();
}
/// <summary>
/// 陷阱4:并发不充分 — Task.WhenAll vs 顺序 await
/// </summary>
public class Trap_SequentialAwait
{
// 慢:顺序执行,总耗时 = 耗时1 + 耗时2 + 耗时3
public async Task<DashboardData> LoadDashboardSlowAsync()
{
var user = await GetUserAsync(); // 200ms
var orders = await GetOrdersAsync(); // 300ms
var notifications = await GetNotificationsAsync(); // 100ms
// 总耗时: 600ms
return new DashboardData(user, orders, notifications);
}
// 快:并行执行,总耗时 = max(耗时1, 耗时2, 耗时3)
public async Task<DashboardData> LoadDashboardFastAsync()
{
var userTask = GetUserAsync();
var ordersTask = GetOrdersAsync();
var notificationsTask = GetNotificationsAsync();
await Task.WhenAll(userTask, ordersTask, notificationsTask);
// 总耗时: 300ms
return new DashboardData(
await userTask,
await ordersTask,
await notificationsTask);
}
private Task<User> GetUserAsync() => Task.FromResult(new User());
private Task<List<Order>> GetOrdersAsync() => Task.FromResult(new List<Order>());
private Task<List<Notification>> GetNotificationsAsync() => Task.FromResult(new List<Notification>());
}
/// <summary>
/// 陷阱5:ValueTask 使用不当
/// </summary>
public class Trap_ValueTask
{
// ValueTask 适合同步完成的场景(避免 Task 对象分配)
// 但不能多次 await,不能作为字段存储
// 正确用法:缓存的热路径
private string? _cachedValue;
public ValueTask<string> GetValueAsync()
{
if (_cachedValue != null)
return new ValueTask<string>(_cachedValue); // 同步返回,零分配
return new ValueTask<string(LoadFromDbAsync());
}
private async Task<string> LoadFromDbAsync()
{
await Task.Delay(100);
_cachedValue = "cached";
return _cachedValue;
}
// 错误:多次 await ValueTask
public async Task BadUsageAsync()
{
var task = GetValueAsync();
var result1 = await task;
// var result2 = await task; // 未定义行为!
}
}
}
public record DashboardData(User User, List<Order> Orders, List<Notification> Notifications);
public record Order { public int Id { get; init; } }
public record Notification { public string Message { get; init; } = ""; }
public interface IUserService { Task<User> GetUserAsync(int id); }题目3:缓存策略优化
问题:接口 QPS 从 1000 上升到 10000 时,缓存命中率下降导致数据库压力骤增。如何优化?
/// <summary>
/// 多级缓存策略
/// </summary>
public class CacheStrategyOptimization
{
/// <summary>
/// 方案1:本地缓存 + 分布式缓存(二级缓存)
/// </summary>
public class TwoLevelCache
{
private readonly IMemoryCache _local;
private readonly IDistributedCache _distributed;
private readonly SemaphoreSlim _lock = new(1, 1);
public async Task<T?> GetAsync<T>(string key, Func<Task<T>> factory, TimeSpan ttl)
{
// L1: 本地缓存(亚毫秒级)
if (_local.TryGetValue(key, out T? value) && value != null)
return value;
// L2: 分布式缓存(毫秒级)
byte[]? bytes = await _distributed.GetAsync(key);
if (bytes != null)
{
value = JsonSerializer.Deserialize<T>(bytes);
if (value != null)
{
_local.Set(key, value, TimeSpan.FromSeconds(30)); // 本地缓存短 TTL
return value;
}
}
// L3: 数据源(加锁防击穿)
await _lock.WaitAsync();
try
{
// Double-check
bytes = await _distributed.GetAsync(key);
if (bytes != null)
return JsonSerializer.Deserialize<T>(bytes);
value = await factory();
var json = JsonSerializer.SerializeToUtf8Bytes(value);
await _distributed.SetAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = ttl
});
_local.Set(key, value, TimeSpan.FromSeconds(30));
return value;
}
finally
{
_lock.Release();
}
}
}
/// <summary>
/// 方案2:缓存预热
/// </summary>
public class CacheWarmup : IHostedService
{
private readonly IServiceProvider _services;
public CacheWarmup(IServiceProvider services) => _services = services;
public async Task StartAsync(CancellationToken ct)
{
using var scope = _services.CreateScope();
// 预加载热点数据
var productRepo = scope.ServiceProvider.GetRequiredService<IProductRepository>();
var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();
var hotProducts = await productRepo.GetHotProductsAsync(1000);
foreach (var product in hotProducts)
{
var key = $"product:{product.Id}";
var json = JsonSerializer.SerializeToUtf8Bytes(product);
await cache.SetAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
}, ct);
}
Console.WriteLine($"预热完成: {hotProducts.Count} 个热点商品");
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}
/// <summary>
/// 方案3:缓存互斥锁(防止缓存击穿)
/// </summary>
public class CacheWithMutex
{
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new();
public async Task<T> GetOrAddAsync<T>(
string key, Func<Task<T>> factory, TimeSpan ttl)
{
// 检查缓存
var cached = await GetFromCacheAsync<T>(key);
if (cached != null) return cached;
// 每个 key 一个锁
var keyLock = _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
await keyLock.WaitAsync();
try
{
// Double-check
cached = await GetFromCacheAsync<T>(key);
if (cached != null) return cached;
// 重建缓存
var value = await factory();
await SetCacheAsync(key, value, ttl);
return value;
}
finally
{
keyLock.Release();
_locks.TryRemove(key, out _);
}
}
private Task<T?> GetFromCacheAsync<T>(string key) => Task.FromResult<T?>(default);
private Task SetCacheAsync<T>(string key, T value, TimeSpan ttl) => Task.CompletedTask;
}
}
public interface IProductRepository { Task<List<Product>> GetHotProductsAsync(int count); }
public class Product { public int Id { get; set; } public string Name { get; set; } = ""; }
public interface IMemoryCache { bool TryGetValue<T>(object key, out T? value); void Set<T>(object key, T value, TimeSpan ttl); }
public interface IDistributedCache { Task<byte[]?> GetAsync(string key); Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken ct = default); }
public class DistributedCacheEntryOptions { public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; } }数据库性能
题目4:索引设计与查询优化
问题:一个查询从 10ms 恶化到 5s,如何排查和优化?
/// <summary>
/// 数据库查询优化实战
/// </summary>
public class DatabaseQueryOptimization
{
/// <summary>
/// 索引设计原则
/// </summary>
public static class IndexDesignPrinciples
{
/// 1. 最左前缀原则(复合索引)
/// 索引 (a, b, c) 可以覆盖:
/// WHERE a = 1
/// WHERE a = 1 AND b = 2
/// WHERE a = 1 AND b = 2 AND c = 3
/// 不能覆盖:
/// WHERE b = 2 (缺少 a)
/// WHERE c = 3 (缺少 a, b)
/// WHERE a = 1 AND c = 3 (跳过了 b)
/// 2. 覆盖索引(避免回表)
/// SELECT name FROM users WHERE email = 'x@y.com'
/// 如果索引包含 (email, name),则不需要回表查整行
/// 3. 避免索引失效
/// - WHERE YEAR(created_at) = 2024 → 索引失效(函数包装)
/// - WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01' → 索引有效
/// 4. 避免过度索引
/// - 每个索引增加写入开销
/// - 每个索引占用存储空间
/// - 一般建议单表索引不超过 5-6 个
}
/// <summary>
/// EF Core 查询优化示例
/// </summary>
public class EfCoreOptimization
{
private readonly AppDbContext _db;
/// 问题查询:N+1 问题
public async Task<List<OrderDto>> GetOrdersBadAsync()
{
// N+1:先查订单,然后循环查用户
var orders = await _db.Orders.ToListAsync();
var result = new List<OrderDto>();
foreach (var order in orders)
{
// 每个订单都触发一次查询!
var user = await _db.Users.FindAsync(order.UserId);
result.Add(new OrderDto
{
OrderId = order.Id,
UserName = user!.Name,
Amount = order.Amount
});
}
return result;
}
/// 优化1:使用 Include(Eager Loading)
public async Task<List<OrderDto>> GetOrdersWithIncludeAsync()
{
return await _db.Orders
.Include(o => o.User)
.Select(o => new OrderDto
{
OrderId = o.Id,
UserName = o.User.Name,
Amount = o.Amount
})
.ToListAsync();
}
/// 优化2:分页查询(避免加载全部数据)
public async Task<PagedResult<OrderDto>> GetOrdersPagedAsync(
int page, int pageSize)
{
var query = _db.Orders
.Include(o => o.User)
.OrderByDescending(o => o.CreatedAt);
int total = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(o => new OrderDto
{
OrderId = o.Id,
UserName = o.User.Name,
Amount = o.Amount
})
.ToListAsync();
return new PagedResult<OrderDto>(items, total, page, pageSize);
}
/// 优化3:编译查询(减少表达式树解析开销)
private static readonly Func<AppDbContext, int, Task<User?>> _getUserById =
EF.CompileAsyncQuery((AppDbContext db, int id) =>
db.Users.FirstOrDefault(u => u.Id == id));
public Task<User?> GetUserCompiledAsync(int id)
{
return _getUserById(_db, id);
}
/// 优化4:批量操作(减少数据库往返)
public async Task BulkUpdateStatusAsync(List<int> orderIds, string status)
{
// 不好的做法:循环单条更新
// foreach (var id in orderIds)
// await _db.Orders.Where(o => o.Id == id).ExecuteUpdateAsync(...)
// 好的做法:批量更新
await _db.Orders
.Where(o => orderIds.Contains(o.Id))
.ExecuteUpdateAsync(setter => setter
.SetProperty(o => o.Status, status)
.SetProperty(o => o.UpdatedAt, DateTime.UtcNow));
}
/// 优化5:使用 AsNoTracking(只读查询)
public async Task<List<Product>> GetProductsReadOnlyAsync()
{
return await _db.Products
.AsNoTracking() // 不跟踪变更,减少内存和 CPU 开销
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
/// 优化6:拆分查询(避免笛卡尔爆炸)
public async Task<List<User>> GetUsersWithAllDataAsync()
{
// 问题:Include 多个集合导航属性会导致笛卡尔积
// 优化:使用 AsSplitQuery 拆分为多个查询
return await _db.Users
.Include(u => u.Orders)
.Include(u => u.Addresses)
.AsSplitQuery() // 拆分为 3 个独立查询
.Where(u => u.IsActive)
.ToListAsync();
}
}
}
public record OrderDto { public int OrderId { get; init; } public string UserName { get; init; } = ""; public decimal Amount { get; init; } }
public record PagedResult<T>(List<T> Items, int Total, int Page, int PageSize);
public class AppDbContext { public DbSet<User> Users { get; } = null!; public DbSet<Order> Orders { get; } = null!; public DbSet<Product> Products { get; } = null!; }
public class User { public int Id { get; set; } public string Name { get; set; } = ""; public bool IsActive { get; set; } public List<Order> Orders { get; set; } = new(); public List<Address> Addresses { get; set; } = new(); }
public class Order { public int Id { get; set; } public int UserId { get; set; } public User User { get; set; } = null!; public decimal Amount { get; set; } public DateTime CreatedAt { get; set; } public string Status { get; set; } = ""; public DateTime UpdatedAt { get; set; } }
public class Address { }
public static class DbSetExtensions { public static Task<List<T>> ToListAsync<T>(this IQueryable<T> query) => Task.FromResult(new List<T>()); public static Task<int> CountAsync<T>(this IQueryable<T> query) => Task.FromResult(0); }题目5:连接池管理
问题:应用在高峰期出现 "Timeout expired" 异常,如何优化连接池配置?
/// <summary>
/// 数据库连接池优化
/// </summary>
public class ConnectionPoolOptimization
{
/// 连接字符串关键参数
/// Server=myserver;Database=mydb;User Id=user;Password=pass;
/// Max Pool Size=200; // 最大连接数(默认 100)
/// Min Pool Size=10; // 最小连接数(默认 0)
/// Connection Timeout=30; // 连接超时(默认 15s)
/// Connection Lifetime=300; // 连接最长存活时间(秒)
/// Pooling=true; // 启用连接池
/// <summary>
/// 连接池监控与诊断
/// </summary>
public class ConnectionPoolMonitor : BackgroundService
{
private readonly ILogger _logger;
public ConnectionPoolMonitor(ILogger<ConnectionPoolMonitor> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// 使用 ADO.NET 统计信息
using var connection = new SqlConnection("...");
connection.StatisticsEnabled = true;
await connection.OpenAsync(stoppingToken);
var stats = connection.RetrieveStatistics();
_logger.LogInformation(
"连接池统计 - 活跃: {Active}, 空闲: {Free}, 总计: {Total}",
stats["ActiveConnections"],
stats["FreeConnections"],
stats["TotalConnections"]);
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
}
/// <summary>
/// 优化策略
/// </summary>
public static class OptimizationStrategies
{
/// 1. 及时释放连接(使用 using)
public static async Task CorrectDisposalAsync()
{
await using var connection = new SqlConnection("...");
await connection.OpenAsync();
// 自动 Dispose
}
/// 2. 减少连接占用时间
public static async Task MinimizeConnectionTimeAsync(SqlConnection connection)
{
// 先在内存中准备数据,减少连接占用时间
var data = PrepareBatchData(); // 不需要连接
await connection.OpenAsync();
await BulkInsertAsync(connection, data); // 只在写入时占连接
// 连接立即释放
}
/// 3. 控制并发连接数
public static async Task ThrottledAccessAsync()
{
using var semaphore = new SemaphoreSlim(50); // 最多 50 并发连接
var tasks = Enumerable.Range(0, 200).Select(async i =>
{
await semaphore.WaitAsync();
try
{
await using var conn = new SqlConnection("...");
await conn.OpenAsync();
// 执行查询
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
private static List<object> PrepareBatchData() => new();
private static Task BulkInsertAsync(SqlConnection conn, List<object> data) => Task.CompletedTask;
}
}
public class SqlConnection
{
public SqlConnection(string connectionString) { }
public bool StatisticsEnabled { get; set; }
public Task OpenAsync(CancellationToken ct = default) => Task.CompletedTask;
public Dictionary<string, object> RetrieveStatistics() => new();
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}前端性能
题目6:Core Web Vitals 优化
问题:Web 应用的 LCP 从 2s 恶化到 5s,FID 超过 300ms,如何优化?
/// <summary>
/// 前端性能优化策略(后端开发者视角)
/// </summary>
public class FrontendPerformance
{
/// <summary>
/// Core Web Vitals 指标
/// LCP (Largest Contentful Paint) < 2.5s — 最大内容渲染时间
/// FID (First Input Delay) < 100ms — 首次输入延迟
/// CLS (Cumulative Layout Shift) < 0.1 — 累积布局偏移
/// INP (Interaction to Next Paint) < 200ms — 交互到下次绘制
/// </summary>
/// <summary>
/// 后端优化1:响应压缩
/// </summary>
public static WebApplicationBuilder ConfigureCompression(WebApplicationBuilder builder)
{
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
options.MimeTypes = new[]
{
"text/plain", "text/css", "text/html",
"application/json", "application/javascript",
"image/svg+xml"
};
});
builder.Services.Configure<BrotliCompressionProviderOptions>(
options => options.Level = System.IO.Compression.CompressionLevel.Optimal);
return builder;
}
/// <summary>
/// 后端优化2:静态资源缓存策略
/// </summary>
public static WebApplication ConfigureStaticFiles(WebApplication app)
{
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// 带哈希的文件:长期缓存
if (ctx.File.Name.Contains('.') && ctx.File.Name.Length > 20)
{
ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000, immutable";
}
else
{
// HTML 文件:短期缓存
ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=60";
}
}
});
return app;
}
/// <summary>
/// 后端优化3:API 响应优化
/// </summary>
public static class ApiResponseOptimization
{
// 只返回前端需要的字段(避免过度获取)
public static async Task<IResult> GetProductListOptimized(AppDbContext db, CancellationToken ct)
{
// 坏:返回所有字段
// var products = await db.Products.ToListAsync(ct);
// 好:只返回列表页需要的字段
var products = await db.Products
.AsNoTracking()
.Select(p => new ProductListItem
{
Id = p.Id,
Name = p.Name,
Price = p.Price,
ThumbnailUrl = p.ThumbnailUrl
})
.ToListAsync(ct);
return Results.Ok(products);
}
}
}
public class BrotliCompressionProvider { }
public class BrotliCompressionProviderOptions { public System.IO.Compression.CompressionLevel Level { get; set; } }
public record ProductListItem { public int Id { get; init; } public string Name { get; init; } = ""; public decimal Price { get; init; } public string ThumbnailUrl { get; init; } = ""; }内存管理
题目7:GC 调优与内存泄漏
问题:应用运行一段时间后内存持续增长,最终 OOM。如何排查?
/// <summary>
/// GC 调优与内存泄漏排查
/// </summary>
public class GcTuningAndMemoryLeaks
{
/// <summary>
/// 常见内存泄漏模式
/// </summary>
public class MemoryLeakPatterns
{
/// 模式1:事件订阅未取消
private readonly event EventHandler<string>? _event;
public void LeakySubscribe()
{
// 泄漏:短生命周期对象订阅长生命周期事件
var handler = new ShortLivedHandler();
_event += handler.OnEvent; // handler 永远不会被 GC
}
public void FixedSubscribe()
{
var handler = new ShortLivedHandler();
_event += handler.OnEvent;
// 使用完后取消订阅
_event -= handler.OnEvent;
}
/// 模式2:静态集合无限增长
private static readonly List<string> _cache = new();
public void LeakyCache()
{
_cache.Add(Guid.NewGuid().ToString()); // 永远不清理
}
public void FixedCache()
{
// 使用有大小限制的缓存
var cache = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10000 });
cache.Set("key", "value", new MemoryCacheEntryOptions { Size = 1 });
}
/// 模式3:大对象堆(LOH)碎片化
public void LargeObjectFragmentation()
{
// 问题:频繁分配 85KB+ 对象导致 LOH 碎片化
for (int i = 0; i < 1000; i++)
{
var buffer = new byte[100_000]; // > 85KB,进入 LOH
ProcessBuffer(buffer);
// buffer 回收后 LOH 产生空洞
}
}
public void FixedLargeObjectHandling()
{
// 方案1:使用 ArrayPool 复用大数组
for (int i = 0; i < 1000; i++)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(100_000);
try
{
ProcessBuffer(buffer);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
}
private void ProcessBuffer(byte[] buffer) { }
}
/// <summary>
/// GC 配置优化
/// </summary>
public static class GcConfiguration
{
/// ServerGC(默认用于 ASP.NET Core)
/// - 每个 CPU 核心一个 GC 堆
/// - 适合多核服务器
///
/// WorkstationGC
/// - 单个 GC 堆
/// - 适合单核或客户端应用
/// 配置方式:在项目文件中
/// <ServerGarbageCollection>true</ServerGarbageCollection>
/// 配置方式:在 runtimeconfig.json 中
/// "System.GC.Server": true
/// <summary>
/// GC 模式选择
/// </summary>
public static void SelectGcMode()
{
// NonConcurrent GC: 暂停所有线程做 GC
// Concurrent (Background) GC: Gen2 回收时不暂停 Gen0/Gen1
// 默认: Server GC + Background GC
// 查看当前 GC 配置
var gcInfo = GC.GetGCMemoryInfo();
Console.WriteLine($"GC 代数: {GC.MaxGeneration}");
Console.WriteLine($"堆大小: {gcInfo.HeapSizeBytes / 1024 / 1024}MB");
Console.WriteLine($"可用内存: {gcInfo.TotalAvailableMemoryBytes / 1024 / 1024}MB");
Console.WriteLine($"内存负载: {gcInfo.MemoryLoadBytes / 1024 / 1024}MB");
Console.WriteLine($"是否 ServerGC: {gcInfo.GenerationInfo.Length > 1}");
}
/// <summary>
/// 内存限制配置(适合容器环境)
/// </summary>
/// <runtimeconfig.json>
/// {
/// "configProperties": {
/// "System.GC.HeapHardLimit": 1073741824, // 1GB 硬限制
/// "System.GC.HeapCount": 4,
/// "System.GC.HeapAffinitizeRanges": "0-3"
/// }
/// }
}
/// <summary>
/// 内存诊断工具
/// </summary>
public class MemoryDiagnostics
{
/// <summary>
/// 使用 dotnet-diagnostics 工具
/// </summary>
public static class DiagnosticCommands
{
/// 1. 查看进程内存
/// dotnet-counters monitor -p <pid>
/// 关注指标:
/// - Working Set
/// - GC Heap Size
/// - Gen 0/1/2 GC Count
/// - LOH Size
/// - Allocation Rate
/// 2. 抓取内存 dump
/// dotnet-gcdump collect -p <pid>
/// 或
/// dotnet-dump collect -p <pid>
/// 3. 分析 dump
/// dotnet-dump analyze <dump-file>
/// > dumpheap -stat # 查看堆上对象统计
/// > dumpheap -mt <mt> # 查看特定类型的对象
/// > gcroot <address> # 查看对象的引用链
/// 4. 使用 dotnet-trace 记录 GC 事件
/// dotnet-trace collect -p <pid> --profile gc-verbose
}
/// <summary>
/// 代码级别的内存监控
/// </summary>
public static void MonitorMemory()
{
// 监控 GC 信息
GC.GetGCMemoryInfo();
// 获取各代大小
Console.WriteLine($"Gen0: {GC.GetGeneration(0)}");
Console.WriteLine($"总分配: {GC.GetTotalAllocatedBytes(true) / 1024 / 1024}MB");
// 强制 GC(诊断用,生产不建议)
GC.Collect(2, GCCollectionMode.Forced, blocking: true, compacting: true);
}
}
}
public class ShortLivedHandler { public void OnEvent(object? sender, string e) { } }
public class MemoryCacheOptions { public long? SizeLimit { get; set; } }
public class MemoryCache { public MemoryCache(MemoryCacheOptions options) { } public void Set(string key, string value, MemoryCacheEntryOptions options) { } }
public class MemoryCacheEntryOptions { public long Size { get; set; } }性能分析工具
题目8:如何选择和使用性能分析工具
问题:应用出现性能抖动,如何系统性排查?
性能分析工具矩阵:
1. dotnet-counters — 实时指标监控
适用场景: 日常监控,快速定位方向
命令: dotnet-counters monitor -p <pid>
关键指标: CPU Usage, GC Heap Size, Gen0/1/2 GC Count, Thread Count
2. dotnet-trace — 性能追踪
适用场景: 定位 CPU 热点,分析异步调用链
命令: dotnet-trace collect -p <pid> --profile cpu-sampling
分析: PerfView 或 Speedscope 打开 .nettrace 文件
3. dotnet-dump — 内存分析
适用场景: 内存泄漏排查,对象引用分析
命令:
dotnet-dump collect -p <pid> # 抓 dump
dotnet-dump analyze <dump> # 分析
> dumpheap -stat # 堆统计
> dumpheap -mt <MT> # 特定类型
> gcroot <addr> # 引用链
4. dotnet-gcdump — 轻量内存分析
适用场景: 生产环境内存分析(文件小)
命令: dotnet-gcdump collect -p <pid>
分析: Visual Studio 打开 .gcdump 文件
5. BenchmarkDotNet — 微基准测试
适用场景: 代码级性能对比
用法: [MemoryDiagnoser] 标注 Benchmark 类
6. Application Insights / OpenTelemetry — APM
适用场景: 分布式追踪,全链路性能分析
适合: 微服务架构的性能瓶颈定位
排查流程:
1. dotnet-counters 看整体指标 → 确认是 CPU 还是内存问题
2. CPU 问题 → dotnet-trace 定位热点方法
3. 内存问题 → dotnet-dump 分析对象分布和引用链
4. IO 问题 → 检查数据库慢查询、外部 API 延迟
5. 锁竞争 → dotnet-trace 的 contention profile/// <summary>
/// 结构化性能排查框架
/// </summary>
public class PerformanceTriageFramework
{
/// <summary>
/// 步骤1:确认性能基线
/// </summary>
public static class Step1_Baseline
{
public static async Task CollectBaselineAsync()
{
// 使用 BenchmarkDotNet 建立基线
// var summary = BenchmarkRunner.Run<MyBenchmark>();
// 在 CI 中运行基准测试,检测性能退化
// dotnet run -c Release --project Benchmarks -- --filter "*"
}
}
/// <summary>
/// 步骤2:监控与告警
/// </summary>
public static class Step2_Monitoring
{
public static WebApplicationBuilder ConfigureMonitoring(WebApplicationBuilder builder)
{
// OpenTelemetry 集成
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddMeter("MyApp"))
.WithTracing(tracing => tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("MyApp"));
// 健康检查
builder.Services.AddHealthChecks()
.AddCheck("memory", () =>
{
var info = GC.GetGCMemoryInfo();
long loadBytes = info.MemoryLoadBytes;
long totalBytes = info.TotalAvailableMemoryBytes;
double ratio = (double)loadBytes / totalBytes;
return ratio > 0.9
? HealthCheckResult.Unhealthy($"内存使用率 {ratio:P1}")
: ratio > 0.7
? HealthCheckResult.Degraded($"内存使用率 {ratio:P1}")
: HealthCheckResult.Healthy($"内存使用率 {ratio:P1}");
});
return builder;
}
}
/// <summary>
/// 步骤3:分析与优化
/// </summary>
public static class Step3_Analyze
{
public static async Task ProfileAsync()
{
// 1. CPU 热点分析
// dotnet-trace collect -p <pid> --profile cpu-sampling --duration 00:00:30
// 2. 内存分析
// dotnet-gcdump collect -p <pid>
// 3. 异步死锁排查
// dotnet-dump analyze <dump>
// > setthread <tid>
// > clrstack
// 4. 锁竞争分析
// dotnet-trace collect -p <pid> --profile gc-verbose
}
}
/// <summary>
/// 步骤4:验证与回归测试
/// </summary>
public static class Step4_Validate
{
public static void RunRegressionTest()
{
// 1. 运行 BenchmarkDotNet 对比优化前后
// 2. 使用 k6 / JMeter 进行负载测试
// 3. 对比 APM 指标(P99 延迟、吞吐量)
// 4. 检查内存占用趋势(是否仍然增长)
}
}
}
public static class HealthCheckResult
{
public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Healthy(string description) => default;
public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Degraded(string description) => default;
public static Microsoft.Extensions.Diagnostics.HealthChecks.HealthCheckResult Unhealthy(string description) => default;
}
namespace Microsoft.Extensions.Diagnostics.HealthChecks
{
public struct HealthCheckResult { }
}关键知识点
- ASP.NET Core 中间件顺序影响性能,短路中间件应前置
- async/await 陷阱(阻塞、async void)是性能问题常见原因
- 数据库查询优化的核心是减少 IO:索引、覆盖查询、批量操作
- 连接池配置需要根据并发量调优 Max/Min Pool Size
- GC 调优的关键是理解代际回收和 LOH 碎片化
- 性能排查应从监控开始,先定位瓶颈再深入分析
常见误区
| 误区 | 正确理解 |
|---|---|
| 性能优化就是加缓存 | 缓存是手段之一,先定位瓶颈 |
| GC 调优能解决所有内存问题 | 应先修复泄漏,再调优 GC |
| 异步一定比同步快 | 异步解决的是可扩展性,不是单次延迟 |
| 加索引就能解决所有查询慢 | 不当索引反而降低写入性能 |
| CPU 100% 就是 CPU 密集型 | 可能是 GC 开销、锁竞争等间接原因 |
进阶路线
- 入门阶段:掌握 dotnet-counters、日志分析
- 进阶阶段:使用 dotnet-trace、dotnet-dump 排查问题
- 高级阶段:GC 调优、连接池优化、缓存策略设计
- 专家阶段:构建性能基线体系、自动化回归检测
适用场景
- 性能调优面试
- 生产环境性能问题排查
- 系统性能优化方案评审
- 性能监控体系设计
落地建议
- 在 CI 中集成 BenchmarkDotNet,检测性能退化
- 建立核心接口的 P99 延迟基线和告警
- 定期做性能演练,模拟高并发场景
- 建立 runbook 记录常见性能问题的排查步骤
排错清单
复盘问题
- 你的应用中最大的性能瓶颈在哪里?
- 如何在不影响生产的情况下做性能分析?
- 性能优化的投入产出比如何衡量?
