c# 如何在高并发服务中实现零停机部署(Zero-Downtime Deployment)

来源:这里教程网 时间:2026-02-21 17:38:32 作者:

为什么
IIS
默认滚动更新不等于零停机

很多团队以为只要用了

IIS
的“应用程序池回收 + 重叠回收”就实现了零停机,实际并非如此。关键问题在于:旧工作进程在收到
shutdown
信号后,仍会拒绝新请求,但已排队的请求(尤其是长连接、WebSocket、未完成的
async
方法)可能被强制中断。更严重的是,如果新版本启动失败,
IIS
不会自动回滚,而是让整个站点不可用。

Kestrel
+
nginx
反向代理下的平滑重启流程

真正可控的零停机部署,推荐绕过

IIS
,改用
Kestrel
自托管 +
nginx
做负载分发。核心是利用
nginx
upstream
health_check
,配合
dotnet
进程的优雅退出机制。

新版本发布前,先启动新
dotnet
进程,监听不同端口(如
5001
),并在
/healthz
返回
200
nginx
配置中启用
health_check
,仅将流量切到健康实例;旧实例返回
503
后,
nginx
自动摘除
Kestrel
进程需响应
SIGTERM
,调用
WebHostApplicationLifetime.StopApplication()
,等待正在处理的
HttpRequest
完成(建议设置
ShutdownTimeout
≤ 30s)
避免在
Startup.Configure
中做阻塞初始化——它发生在
nginx
开始转发前,会导致新实例迟迟无法进入健康状态

dotnet publish
后如何避免文件锁导致启动失败

Windows 上直接覆盖正在运行的

.dll
文件,常引发
System.IO.IOException: The process cannot access the file
。这不是并发问题,而是 .NET 运行时对已加载程序集的独占锁。

不要原地覆盖
publish
目录;改为每次发布到带时间戳的子目录(如
app-20240520-1423
用符号链接(
mklink /D current app-20240520-1423
)指向当前生效版本,
nginx
或服务脚本只读取
current
若用
Windows Service
托管,服务配置中的
WorkingDirectory
必须指向
current
,而非固定路径
dotnet
进程启动时读取的是运行时路径下的文件,符号链接切换后无需重启进程本身——但必须确保新进程启动后再切链接,否则旧进程仍读旧 DLL

数据库迁移与 API 版本兼容性怎么一起考虑

零停机不只是进程不挂,更是请求不报错。如果新版本代码依赖新增字段,而旧版本还在处理请求,就会出现

NullReferenceException
SqlException

所有数据库变更必须向前兼容:新增列加
DEFAULT
或允许
NULL
;删除列必须等两轮部署后(旧版下线后再删)
API 接口不直接删字段,而是用
[Obsolete]
标记 + 文档说明,并在响应体中保留字段(值为
null
使用
Entity Framework Core
时,避免在
OnModelCreating
中硬编码约束;改用
HasDefaultValueSql("GETDATE()")
等可迁移方式
灰度期间,可在中间件里按
User-Agent
或 Header 注入版本路由逻辑,把新老流量导向不同 Controller 分支(临时方案,勿长期依赖)
app.Use(async (context, next) =>
{
    var userAgent = context.Request.Headers["User-Agent"].ToString();
    if (userAgent.Contains("v2-client"))
        context.Items["ApiVersion"] = "v2";
    await next();
});
真正的难点不在工具链,而在部署窗口里那几十秒——你得同时控制进程生命周期、文件系统可见性、反向代理状态、数据库 schema 演进和客户端行为。少一个环节,用户就可能看到 502 或超时。

相关推荐