多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

nginx服务化五套方案原理解剖与选型

nginx服务化五套方案原理解剖与选型 把 nginx 做成 Windows 服务nssm、WinSW、srvany、ServiceBase、计划任务 —— 五套方案的原理解剖与选型摘要nginx 在 Windows 上没有官方的服务支持想让它开机自启就得套一层包装器。本文先把 Windows 服务的契约SCM / ServiceMain / SetServiceStatus讲清楚解释为什么sc create直挂nginx.exe注定失败再逐个解剖 nssm、WinSW、srvany、.NET ServiceBase 自制包装器、计划任务/启动文件夹五种方案的工作原理、停止机制、日志能力与实测数据含 PE 头实测最后给一张选型决策表和三条通用铁律。全程用真实文件尺寸和导入表说话不靠听说。一、先搞懂对手Windows 服务到底是个什么东西1.1 nginx.exe 不是服务程序Windows 服务的世界由SCMService Control Manager服务控制管理器统治。一个 exe 想成为服务必须满足契约自己调用StartServiceCtrlDispatcher把ServiceMain 函数指针注册给 SCMServiceMain 里先调RegisterServiceCtrlHandlerEx注册控制处理函数然后调SetServiceStatus报告状态START_PENDING → RUNNING停止时STOP_PENDING → STOPPED全程能响应 SCM 发来的控制码SERVICE_CONTROL_STOP / PAUSE / SHUTDOWN / INTERROGATE。nginx.exe一个都不满足。它是个普通的控制台程序main()起来就去监听端口了从不理会 SCM。所以sc create nginx binPath D:\nginx\nginx.exe这种直挂写法看着聪明实测必然失败SCM 拉起进程后等半天等不到SERVICE_RUNNING30 秒后判超时给你一个错误 1053「服务没有及时响应启动或控制请求」顺手在事件日志里留下 7000/7009。就算把ServiceStartToKill0之类的注册表技巧用上也还是没人转发停止信号、没人守进程 —— 服务管理器里点停止nginx 照跑不误。1.2 于是就有了包装器这个行业包装器的思路统一而朴素包装器自己是个合法的服务程序满足 1.1 的契约它再把真正的业务程序当子进程拉起来并替 SCM 看着它。┌─────────────────────────────────────────┐ 服务管理器 / net start │ SCM (services.exe) │ ─────────────────▶│ 读注册表 → 拉起服务进程( 包装器) │ └───────────────┬─────────────────────────┘ │ StartServiceCtrlDispatcher ▼ ┌─────────────────────────────────────────┐ │ 包装器进程 (nssm.exe / nginx-service.exe)│ │ ① SetServiceStatus(START_PENDING) │ │ ② CreateProcess → nginx.exe │ │ ③ SetServiceStatus(RUNNING) │ │ ④ 循环监视子进程退出码 / 重定向输出 │ └───────────────┬─────────────────────────┘ │ 被托管进程 ▼ nginx.exe (master) ── worker 进程 停止时SCM ──ControlService(STOP)──▶ 包装器 ──┬─ 发停止命令 / CtrlC / 杀进程树 └─ SetServiceStatus(STOPPED) 回报所有方案的差别就藏在这张图的几个空格里谁来实现这个包装器原生 C / .NET / 老古董工具、配置怎么写命令行 / XML / 注册表 / 代码、停止时怎么通知子进程CtrlC / 自定义停止命令 / 直接 TerminateProcess、输出往哪落重定向到文件 / 丢弃以及目标机需要什么运行时。二、五套方案逐个解剖2.1 nssm —— 零依赖的原生包装器Windows 上的标准答案原理单个 C 原生 exe自己实现服务契约用CreateProcess拉起被托管程序并周期性WaitForSingleObject监视被托管程序退出就按配置策略处理重启 / 忽略 / 退出。实测数据自写 Python 解析 PEnssm 2.24 官方包检查项win32/nssm.exewin64/nssm.exe大小294,912 字节331,264 字节架构x86 (0x014c)x64 (0x8664)PE SubsystemVersion5.05.2Win7 6.1加载器不会拒绝导入 DLLSHLWAPI / KERNEL32 / USER32 / COMDLG32 / ADVAPI32 / SHELL32同左导入表里没有MSVCR*.dll、没有VCRUNTIME140.dll、没有api-ms-win-crt-*—— 它是静态链接 CRT工程RuntimeLibrary0/MT的零依赖单文件程序不需要 VC 运行库、不需要 UCRT、不需要 .NET。这是 nssm 最大的价值也是它在老系统Windows 2000 起上一直吃香的原因。配置方式命令行/图形界面写进服务注册表。nssm install nginx D:\nginx\nginx.exe nssm set nginx AppDirectory D:\nginx ← 工作目录最高频的坑 nssm set nginx AppStdout D:\nginx\logs\out.log ← 输出重定向远程排障的生命线 nssm set nginx AppStderr D:\nginx\logs\err.log nssm set nginx AppExit Default Restart nssm set nginx Start SERVICE_AUTO_START nssm dump nginx ← 打印全部配置排障第一条命令停止机制按AppStopMethod*依次尝试默认顺序ConsoleCtrl控制台 CtrlC 事件 →WindowMessageWM_CLOSE →Threads→Terminate强杀逐级升级。对 nginx 这种控制台程序CtrlC那一步通常就能优雅退出。它独有的坑 —— “服务卡在 Paused”官方 README 原文NSSM will pause an increasingly longer time between subsequent restart attempts if the service fails to start in a timely manner, up to a maximum of four minutes. … you can use the Windows service console (where the service will be shown in Paused state) to send a continue signal也就是说被托管程序反复起来就秒退时nssm 会逐步延长重试间隔最长 4 分钟服务在服务管理器里显示为暂停。很多人包括一些技术文章把这个现象误判成nssm 和系统不兼容“系统缺组件”实际性质是nginx 自己起不来触发了 nssm 的保护机制。适用任何 Windows含精简/老系统只要不想在目标机上装运行时。不适合需要复杂依赖编排、需要把配置纳入版本管理的场景配置在注册表里不便 diff。2.2 WinSW —— 用 XML 描述服务的包装器原理.NET 写的包装器继承ServiceBase实现服务契约被托管程序的路径、参数、日志、失败恢复等全部写在同名 XML 里。改名成myapp.exe后它会找同目录的myapp.xml。配置方式XML可进 Git、可代码生成这是它比 nssm 更工程化的地方。serviceidnginx/idnamenginx/namedescriptionnginx web server/descriptionexecutableD:\nginx\nginx.exe/executableworkingdirectoryD:\nginx/workingdirectorylogpathD:\nginx/logpathlogmoderoll/logmodestartmodeAutomatic/startmodedependMySQL/dependstopexecutableD:\nginx\nginx.exe/stopexecutablestoparguments-s stop/stopargumentsonfailureactionrestartdelay10 sec/resetfailure1 hour/resetfailure/service服务管理用自带子命令install / uninstall / start / stop / stopwait / restart / statusstatus会打印NonExistent/Started/Stopped。关键语义官方 v2.12.0 文档务必记牢startmode默认Automatic停止流程默认先发CtrlC等stoptimeout默认 15 秒仍不退才TerminateProcess一旦配了stoparguments就改为先运行停止命令并等它退出联动规则用了stoparguments启动参数就要用startarguments而不是arguments失败恢复用onfailure actionrestart|reboot|none delay.../计数重置用resetfailure还支持serviceaccountLocalSystem / LocalService / NetworkService / 域账号 / gMSA、delayedAutoStart/、priority、download启动前拉取文件可做自更新。它最大的坑 —— 目标机必须有 .NET 运行时而且构建要挑对。WinSW 的 .NET Framework 构建是纯托管程序实测WinSW.NET4.exe的导入表只有mscoree.dll内嵌 CLR 版本串v4.0.30319没有运行时就是一堆 IL 字节码谁也跑不起来。v2.12.0 的构建矩阵尺寸实测GitHub Releases构建大小运行时前提备注WinSW.NET2.exe860,672 B (841 KB).NET Framework 2.0/3.5老系统友好WinSW.NET4.exe852,480 B (833 KB).NET Framework 4.0存量现场最常见的那个WinSW.NET461.exe655,872 B (641 KB).NET Framework 4.6.1Win7 需 SP1 SHA-2 补丁WinSW-x86.exe/WinSW-x64.exe≈16.5 MB / ≈17.4 MB自包含v2 .NET Core 3.1v3 .NET 7v3 的 .NET 7 自包含仅支持 Win10 1607为什么这点特别重要GitHub Releases 页面展示 2.12.0 稳定版和 3.x 预发布版两组资产默认容易点到最新3.x那个 17 MB 的 exe而它在 Win7/Server 2008 R2 上根本起不来。选错构建 → WinSW 不支持 Win7的传言就是这么来的。2.3 srvany.exe instsrv —— 该进博物馆的老方案原理微软 Windows Server 2003 Resource Kit 里的两个小工具。instsrv.exe负责注册服务srvany.exe负责当那个壳启动时读注册表HKLM\SYSTEM\CurrentControlSet\Services\服务名\Parameters Application D:\nginx\nginx.exe AppParameters 启动参数 AppDirectory D:\nginx然后CreateProcess拉起目标程序。为什么不该再用不看不管srvany 拉起进程后就基本撒手进程崩了它不知道也不会重启服务状态和实际进程状态可以对不上不转发停止点停止服务被托管进程不一定会跟着干净退出往往只剩强杀一条路没有日志被托管程序的 stdout/stderr 没有落盘机制出问题只能靠猜依赖第三方包需要下载 Resource Kit很多现场是某台机器上翻出来的 srvany.exe来源与版本不可控。后来的社区替代品srvany-ng、Rust 写的shawl补了监控和日志但选型逻辑上你已经不该在 2020 年代新项目里用 srvany 了。2.4 自制 .NET ServiceBase 包装器“installutil 那一套”原理自己写 C#class MyService : ServiceBaseServiceBase.Run(new MyService())在OnStart里Process.Start拉起 nginx在OnStop里杀掉。安装用 .NET SDK 自带工具C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe nginx-service.exe installutil.exe /u nginx-service.exe ← 卸载它的标志性报错—— 直接双击或命令行运行这个 exe 时会弹无法从命令行或调试程序启动服务。必须首先安装 Windows 服务使用 installutil.exe然后用 ServerExplorer、Windows 服务管理工具或 NET START 命令启动它。这是ServiceBase.Run的标准提示它检测到父进程不是 SCM于是拒绝执行。看到这句话就等于看到这是裸的 .NET ServiceBase 程序不是 WinSWWinSW 双击时只会打印用法帮助。这句话本身不代表 exe 坏了但如果这台机器上服务从未被正确安装过那开机不启动就有解释了。什么时候它是对的你需要极其定制化的行为启动前跑一段业务逻辑、和自家系统深度集成。为什么大多数场景不该选它监控进程、重定向日志、优雅停止、失败恢复、依赖顺序……这些 WinSW/nssm 都白送了自写得写一遍还有 bug换人维护时没有文档可查只有一个 exe。2.5 计划任务 / 启动文件夹 —— 不是服务不要混用原理任务计划程序taskschd.msc在计算机启动时或用户登录时运行一个程序启动文件夹shell:startup则是登录时执行。为什么在交付场景是雷区不在 SCM 里sc query、net start、服务面板全都看不到它运维交接时等于隐形进程依赖会话启动文件夹里的东西必须有人登录才跑任务计划虽然能配不管用户是否登录但需要保存密码密码改了任务就静默失效权限错位nginx 以某个登录用户身份跑读写logs/、绑定 80 端口的权限都跟着这个用户走无失败恢复进程崩了没人管开机顺序也无法和其他服务编排双开冲突如果服务方式也配了就会两个 nginx 抢 80 端口。一句话只有给登录用户用的桌面程序才用启动项服务器上的常驻服务一律做服务。三、横向对比大表维度nssmWinSWsrvany自制 ServiceBase计划任务/启动项实现语言C 原生.NETC 原生老.NET / 自选系统组件目标机依赖无.NET Framework 4.0 或自包含构建无但要带 srvany.exe对应 .NET 版本无体积≈ 300 KB833 KBNET4/ 17 MB自包含≈ 30 KB instsrv视写法—配置形式服务注册表GUI/命令行XML可进 Git注册表 Parameters代码里硬编码图形界面进程监控/自动重启✅ AppExit throttle✅ onfailure/resetfailure❌需自己写❌优雅停止✅ CtrlC→WM_CLOSE→强杀✅ stoparguments / stoptimeout❌ 基本靠强杀需自己写❌输出落盘✅ AppStdout/AppStderr✅ .out/.err/.wrapper 日志 轮转❌需自己写❌依赖顺序✅depend✅depend❌需自己写⚠️ 触发器凑最低系统Windows 2000Win7 SP1Framework 构建Win2000/2003 时代Win7Win7许可证Public domainMIT微软旧工具包自己定—最好的场景老系统 / 零依赖要求需要 XML 化、要进版本管理已有历史包袱深度定制桌面程序四、选型决策树目标机能不能装运行时 ├─ 不能老旧/精简系统、不允许装 .NET │ └─ ▶ nssm零依赖一个 300 KB 的 exe 搞定 └─ 能 ├─ 系统是 Win10 1607 / Server 2016 │ └─ ▶ WinSWXML 配置、功能最全选 2.12.0 的 WinSW.NET4/NET461或 3.x 的自包含构建 └─ 系统是 Win7 SP1 / Server 2008 R2 ├─ 已装 .NET 4.x ▶ WinSW.NET4.exe833 KB 或 nssm └─ 干净系统只有 3.5.1 ├─ 可以装 .NET 4.x ▶ 装完再用 WinSW.NET4.exe └─ 不想装 ▶ nssm 存量现场已有包装器正在跑 └─ ▶ 优先修通现有方案改配置、改启动类型、补日志而不是换组件 动一次配置要停机、要审批换组件的收益往往覆盖不了成本关于WinSW vs nssm再多说一句避免站队两者没有绝对优劣只有适配与否。nssm 胜在原生、零依赖、单文件WinSW 胜在配置可读可版本化、功能项更细服务账户、gMSA、下载扩展。新建环境按上面决策树挑接手存量现场先修通。五、三条通用铁律无论用哪套方案铁律一工作目录必须显式设置这是 nginx 服务化最高频的翻车点。包装器由 SCM 拉起时工作目录默认是C:\Windows\System32而 nginx 的conf/nginx.conf、logs/、pid都是相对路径 —— 手动双击测试一切正常做成服务就秒退。nssmnssm set nginx AppDirectory D:\nginxWinSWworkingdirectoryD:\nginx/workingdirectory保险做法把nginx.conf里的error_log、pid、access_log、root全写成绝对路径Windows 下用正斜杠/。铁律二把输出落盘别让服务死了没遗言判断服务为什么起不来靠猜是猜不出来的。务必配好nssmAppStdout/AppStderrWinSWlogpathlogmoderoll/logmode现场看xxx.wrapper.log包装器自己的动作、xxx.out.log/xxx.err.log被托管程序的输出。补一个反直觉的点nginx 正常工作时几乎不往 stdout/stderr 写东西所以.out/.err是 0 KB 很正常真正的线索在 wrapper 日志、logs\error.log和logs\nginx.pid。铁律三优雅停止且必须验证Windows 上停 nginx 的正确姿势是nginx.exe -s stop重载是-s reload直接TerminateProcess强杀会留下 pid 残留、worker 进程孤儿等隐患验证方法看日志里停止时到底执行了什么。如果只有一行ProcessKill说明你的优雅停止配置根本没生效WinSW 的stopexecutable写成了带参数的一行路径就是个经典陷阱。六、收尾Windows 上把 nginx 做成服务本质是补上 nginx 缺失的那部分服务契约实现。理解了 SCM 与包装器之间的关系五种方案的差异就退化成了四个具体问题目标机有没有运行时、配置写在哪、停止怎么通知、日志往哪落。把这四个问题问清楚选型基本不会错把这四条落实到位服务开机不启动“起来就退”停了留残留这三类问题也就少了一大半。本文的二进制尺寸、PE 头 SubsystemVersion、导入表数据均为实测WinSW v2.12.0 取自 GitHub Releases API 与官方包nssm 2.24 取自 Chocolatey 官方包WinSW 的 XML 语义与 nssm 的 Paused 机制均引用官方文档原文。
返回列表