
这类主题最值得先看的不是概念列表而是它到底能解决什么实际问题。对于需要处理线上流量的服务来说高可用性意味着当服务器、网络或某个依赖组件出现问题时你的 API 服务依然能对外提供可接受的服务而不是直接挂掉或返回大量错误。ASP.NET Core 本身是一个健壮的框架但要构建真正高可用的 Web API你需要从架构、编码、部署到运维监控做出一系列具体的设计和配置。很多人一听到“高可用”就想到复杂的集群和昂贵的硬件其实第一步往往是从代码和配置的健壮性开始的。一个在单机上都无法稳定处理异常和负载的 API放到集群里只会把问题放大。所以我更建议把构建过程拆成几个层次先从单个实例的稳定性做起再扩展到多实例部署和外部依赖的容错最后才是全局的监控和自愈。下面我会按照从内到外、从开发到部署的顺序把构建高可用 ASP.NET Core Web API 的关键环节拆解一遍。这些经验来自于实际生产环境的踩坑和优化你可以对照自己的项目看看哪些环节已经做了哪些还可以加强。1. 从代码层面筑牢单实例的稳定性基础高可用大厦的地基是每个运行实例的稳定性。如果单个实例动不动就崩溃、内存泄漏或者无法处理异常请求那么部署十个实例也只是增加了十个不稳定的点。这一层的工作主要在开发阶段完成。1.1 实施全面的异常处理与友好响应异常处理不仅仅是加个try-catch。在 Web API 中未处理的异常会导致请求线程崩溃并可能向客户端返回暴露内部信息的 500 错误页面这既不友好也不安全。首先必须配置全局异常处理中间件。在Program.cs或启动类中使用UseExceptionHandler来捕获管道中未处理的异常。这里的关键是在生产环境中你应该记录详细的异常信息到日志系统如 Serilog 或 Application Insights但向客户端返回一个通用的、不包含堆栈跟踪的错误响应。// Program.cs if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(appBuilder { appBuilder.Run(async context { context.Response.StatusCode 500; context.Response.ContentType application/json; // 记录异常到日志 var exceptionHandlerPathFeature context.Features.GetIExceptionHandlerPathFeature(); var logger context.RequestServices.GetRequiredServiceILoggerProgram(); logger.LogError(exceptionHandlerPathFeature?.Error, An unhandled exception occurred.); // 返回友好错误 await context.Response.WriteAsJsonAsync(new { Code InternalServerError, Message An unexpected error occurred. Please try again later. }); }); }); }其次为已知的业务异常定义特定的 HTTP 状态码和响应体。例如资源未找到返回 404权限不足返回 403请求参数无效返回 400。使用ProblemDetails标准Microsoft.AspNetCore.Mvc内置支持可以让你的错误响应更规范。// 在控制器或 Minimal API 中 if (resource null) { return Results.NotFound(new ProblemDetails { Title Resource Not Found, Detail $The resource with ID {id} was not found., Status StatusCodes.Status404NotFound, Type https://tools.ietf.org/html/rfc7231#section-6.5.4 }); }1.2 实现请求限流与防抖即使代码没有 Bug突如其来的高并发请求也可能压垮单个实例。限流Rate Limiting是保护服务的重要手段。ASP.NET Core 7 开始内置了灵活的限流中间件。不要一上来就做全局限流而是根据 API 端点的重要性和资源消耗情况实施分层限流策略。例如登录接口、发送短信接口可以设置较严格的限制而查询类接口可以宽松一些。// Program.cs builder.Services.AddRateLimiter(options { // 全局策略宽松 options.GlobalLimiter PartitionedRateLimiter.CreateHttpContext, string(httpContext RateLimitPartition.GetFixedWindowLimiter( partitionKey: httpContext.User.Identity?.Name ?? httpContext.Request.Headers.Host.ToString(), factory: partition new FixedWindowRateLimiterOptions { AutoReplenishment true, PermitLimit 100, // 每窗口允许100个请求 Window TimeSpan.FromSeconds(10) // 窗口时长10秒 })); // 针对特定端点的严格策略 options.AddPolicy(StrictPolicy, httpContext RateLimitPartition.GetFixedWindowLimiter( partitionKey: httpContext.Connection.RemoteIpAddress?.ToString(), factory: partition new FixedWindowRateLimiterOptions { AutoReplenishment true, PermitLimit 5, Window TimeSpan.FromSeconds(30) })); }); app.UseRateLimiter(); // 在 Minimal API 或控制器上应用策略 app.MapPost(/api/auth/login, () { /* ... */ }) .RequireRateLimiting(StrictPolicy);防抖Debouncing更多用于客户端但对于某些高频的写入操作如记录用户活动日志服务端可以考虑使用队列进行异步缓冲避免直接冲击数据库。1.3 确保资源的正确释放与连接池管理内存泄漏和连接耗尽是导致服务实例缓慢死亡的主要原因。重点关注IDisposable 对象确保HttpClient、数据库连接、文件流等实现了IDisposable接口的对象被正确释放。优先使用using语句或在依赖注入中管理其生命周期。数据库连接池EF Core 默认管理连接池但要避免在单个请求中长时间持有连接例如不要在using块里执行耗时同步 I/O。确保连接字符串一致不同的连接字符串会创建不同的连接池。静态集合与缓存谨慎使用静态集合作为缓存如果没有合适的清理策略它们会一直增长。考虑使用IMemoryCache并设置合理的过期时间和大小限制。// 不好的做法静态集合无限增长 public static class BadCache { public static ConcurrentDictionarystring, object Cache { get; } new(); } // 更好的做法使用 IMemoryCache builder.Services.AddMemoryCache(options { options.SizeLimit 1024 * 1024 * 100; // 限制缓存大小约100MB }); public class MyService { private readonly IMemoryCache _cache; public async TaskMyData GetDataAsync(string key) { return await _cache.GetOrCreateAsync(key, entry { entry.SlidingExpiration TimeSpan.FromMinutes(5); entry.Size 1; // 设置条目大小用于 SizeLimit 计算 return FetchDataFromDbAsync(key); }); } }2. 设计应对故障与依赖的韧性策略单个实例稳定后就要考虑外部世界的不确定性数据库可能慢第三方 API 可能挂掉网络可能抖动。服务的韧性Resiliency体现在对这些故障的容忍和恢复能力上。2.1 为外部 HTTP 调用添加重试与熔断使用HttpClient直接调用外部服务是脆弱的。Polly库是处理瞬态故障如网络超时、服务暂时不可用的事实标准。结合IHttpClientFactory使用是 ASP.NET Core 的最佳实践。典型的策略是“重试熔断”组合重试Retry应对短暂的网络故障或服务端负载过高。但要对非幂等的 POST、PUT 请求慎用重试。熔断Circuit Breaker当失败次数达到阈值时快速失败打开熔断器避免持续调用拖垮自己。一段时间后尝试半开探测服务是否恢复。// Program.cs builder.Services.AddHttpClient(ExternalApi, client { client.BaseAddress new Uri(https://api.example.com/); }) .AddTransientHttpErrorPolicy(policyBuilder policyBuilder .WaitAndRetryAsync(3, retryAttempt TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))) // 指数退避重试 .AddTransientHttpErrorPolicy(policyBuilder policyBuilder .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); // 5次失败后熔断30秒 // 在服务中使用 public class MyService { private readonly IHttpClientFactory _httpClientFactory; public async Task CallExternalApiAsync() { var client _httpClientFactory.CreateClient(ExternalApi); // 此次调用将受到上面定义的 Polly 策略保护 var response await client.GetAsync(/some-endpoint); } }2.2 对数据库与缓存访问实施健康检查健康检查Health Checks能让部署平台如 Kubernetes或负载均衡器知道你的服务实例是否健康。ASP.NET Core 内置了健康检查中间件并可以轻松扩展。为关键依赖如数据库、Redis 缓存、外部关键 API创建特定的健康检查端点。这样负载均衡器可以在实例不健康时将其从服务池中摘除。// Program.cs builder.Services.AddHealthChecks() .AddSqlServer(connectionString: builder.Configuration.GetConnectionString(DefaultConnection), healthQuery: SELECT 1;, // 简单的探测查询 name: sqlserver, failureStatus: HealthStatus.Unhealthy, tags: new[] { db, sql }) .AddRedis(redisConnectionString: builder.Configuration.GetConnectionString(Redis), name: redis, failureStatus: HealthStatus.Degraded, // Redis 挂了可能只是功能降级 tags: new[] { cache }) .AddUrlGroup(new Uri(https://api.critical.com/health), name: criticalapi); app.MapHealthChecks(/health, new HealthCheckOptions { Predicate _ true, // 检查所有注册的检查项 ResponseWriter UIResponseWriter.WriteHealthCheckUIResponse // 可读的 JSON 响应 }); // 可以创建一个仅包含基础存活检查的端点用于负载均衡器 app.MapHealthChecks(/health/ready, new HealthCheckOptions { Predicate reg reg.Tags.Contains(db) || reg.Tags.Contains(cache) // 只检查关键依赖 });负载均衡器如 ELB、Nginx可以定期调用/health/ready端点。如果返回非 200 状态码则停止将新流量路由到该实例。2.3 采用异步编程与非阻塞 I/O这几乎是现代 ASP.NET Core 的默认要求但值得再次强调。使用async/await进行所有 I/O 操作数据库查询、文件读写、HTTP 调用可以极大地提高线程池的利用率使单个实例能够以更少的资源处理更多的并发请求。避免在控制器或中间件中执行长时间运行的同步操作这会阻塞线程池线程。3. 通过部署与基础设施实现多实例高可用代码和配置层面的工作确保了单个实例的质量而真正的“高可用”通常意味着没有单点故障。这需要部署多个实例并通过基础设施将它们组织起来。3.1 理解负载均衡器如 ELB、Nginx的角色负载均衡器Load Balancer是高可用架构的交通警察。它位于客户端和你的多个 API 实例之间负责将请求分发到健康的实例上。用户提到“ELB后面是2个nginx服务器,可以吗”这是一种常见架构云服务商的 ELB弹性负载均衡器作为第一层将流量分发给后端的 Nginx 反向代理服务器Nginx 再分发给真正的应用实例如 Kestrel。这样做的好处是ELB处理 SSL 终结、DDoS 防护、与云平台深度集成。Nginx提供更精细的流量控制、静态文件服务、缓存、日志聚合等。可以吗当然可以但需要考虑复杂度。对于大多数场景ELB 直接指向运行 Kestrel 的 ASP.NET Core 实例就足够了。增加 Nginx 层会引入新的配置管理和故障点。除非你需要 Nginx 的特定功能如复杂的 rewrite 规则、Lua 脚本否则从简为宜。3.2 配置无状态服务与分布式会话要使多个实例能平等地处理任何请求你的服务必须设计为无状态Stateless。这意味着任何实例都不应存储与特定用户或会话相关的内存状态。会话Session如果必须使用会话请使用分布式缓存如 Redis、SQL Server作为会话存储后端而不是内存中。builder.Services.AddStackExchangeRedisCache(options { options.Configuration builder.Configuration.GetConnectionString(Redis); }); builder.Services.AddSession(options { options.IdleTimeout TimeSpan.FromMinutes(20); options.Cookie.HttpOnly true; options.Cookie.IsEssential true; });缓存同上使用分布式缓存Redis替代IMemoryCache以确保所有实例看到相同的缓存数据。文件上传上传的文件不应存储在本地实例的磁盘上而应直接存储到对象存储如 AWS S3、Azure Blob Storage或共享文件系统中。3.3 实现蓝绿部署或滚动更新直接更新生产环境的所有实例是危险的。蓝绿部署或滚动更新可以最小化停机时间。蓝绿部署准备一套全新的环境绿部署新版本通过健康检查后将负载均衡器的流量从旧环境蓝切换到绿环境。如果出现问题可以快速切回蓝环境。滚动更新Kubernetes 等容器平台原生支持逐步用新版本的 Pod实例替换旧版本的 Pod每次替换都确保新实例通过健康检查后再继续。关键点是确保应用在关闭时能优雅处理正在进行的请求。ASP.NET Core 支持优雅关闭在收到终止信号如SIGTERM后会停止接收新请求并等待一段时间让正在处理的请求完成。// 在 Host 配置中设置优雅关闭超时时间 builder.Host.ConfigureHostOptions(options { options.ShutdownTimeout TimeSpan.FromSeconds(30); // 默认是5秒可根据需要延长 });4. 建立可观测性与自动化运维能力系统上线后高可用性需要通过监控来保障通过日志和指标来发现问题通过自动化来快速响应。4.1 集成集中式日志与分布式追踪当你有多个实例时登录到每台服务器查看日志是不现实的。必须将日志集中收集到如ELK Stack、Seq或云服务如Azure Application Insights中。结构化日志使用Serilog等库比纯文本日志更利于分析和过滤。确保每条日志都包含请求唯一标识如CorrelationId这样你才能追踪一个请求流经了哪些服务。// 安装 Serilog.AspNetCore, Serilog.Sinks.Seq 等包 Log.Logger new LoggerConfiguration() .WriteTo.Console() .WriteTo.Seq(http://localhost:5341) .Enrich.FromLogContext() // 重要允许在上下文中添加属性 .CreateLogger(); builder.Host.UseSerilog(); // 在中间件中为每个请求添加 CorrelationId app.Use(async (context, next) { var correlationId context.Request.Headers[X-Correlation-ID].FirstOrDefault() ?? Guid.NewGuid().ToString(); using (Serilog.Context.LogContext.PushProperty(CorrelationId, correlationId)) { context.Response.Headers[X-Correlation-ID] correlationId; await next(context); } });4.2 监控关键性能指标与设置警报监控不能只看 CPU 和内存。对于 API需要关注应用层指标请求率RPS、错误率4xx, 5xx、平均响应时间、P95/P99 延迟。资源层指标GC 频率、线程池队列长度、数据库连接池使用率。业务指标关键操作的成功率、耗时。将这些指标暴露给监控系统如Prometheus并设置合理的警报。例如当 5xx 错误率超过 1% 持续 2 分钟或 P99 延迟超过 1 秒时立即通知运维人员。// 使用 prometheus-net.AspNetCore 库 app.UseHttpMetrics(); // 暴露 HTTP 相关指标 app.MapMetrics(); // 暴露 /metrics 端点给 Prometheus 抓取4.3 制定并演练故障恢复预案高可用不是完全不出事而是出事能快速恢复。你需要为可能发生的故障场景制定预案Runbook数据库连接失败切换只读副本启用本地缓存Redis 缓存全挂应用能否降级直接访问数据库性能影响多大单个可用区AZ故障流量能否快速切换到其他可用区定期进行故障演练混沌工程例如随机终止一个实例模拟依赖服务超时验证你的重试、熔断、健康检查机制是否真的有效。构建高可用的 ASP.NET Core Web API 是一个系统工程它贯穿了开发、测试、部署和运维的全生命周期。没有一劳永逸的银弹核心思路是让每个实例尽可能健壮让实例之间能够协同和替代让整个系统透明可视并能自动应对常见故障。从今天列出的这些点开始逐一审视和加固你的项目你会发现系统的可靠性会得到实实在在的提升。