在 Linux 上部署 Python 服务通常用 systemd,但在 Windows Server 上有 几种不同的选择。本文对比了我实际用过的三种方案,帮助你在不同场景下做出选择。
方案对比
| 方案 | 开机自启 | 崩溃重启 | 日志 | 复杂度 |
|---|---|---|---|---|
| NSSM 服务 | ✅ | ✅ 可配置 | ✅ 自动 | 低 |
| Task Scheduler | ✅ | ❌ | 需手动重定向 | 低 |
| 手动启动 | ❌ | ❌ | 控制台 | 极低 |
方案一:NSSM 注册 Windows 服务(推荐)
NSSM(Non-Sucking Service Manager)是一个免费工具,把任意可执行程序 包装成 Windows 服务,支持崩溃后自动重启、日志自动收集。
安装步骤
# 下载 nssm.exe 放到 C:\tools\nssm\ # 然后注册服务(管理员 CMD) C:\tools\nssm\nssm.exe install cps_dashboard python # 弹出 GUI: # Path: C:\Python312\python.exe # Startup dir: C:\cps_dashboard # Arguments: -m uvicorn main:app --host 0.0.0.0 --port 8000
常用操作
sc start cps_dashboard sc stop cps_dashboard sc query cps_dashboard
优点
- 服务崩溃后可配置自动重启(Recovery 选项)
- 日志自动写到文件,无需手动重定向
- 在 services.msc 中可视化管理
方案二:Task Scheduler 开机自启
适合对崩溃重启要求不高的场景,配置更简单。 缺点是进程崩溃后不会自动重启,需要手动干预。
$action = New-ScheduledTaskAction `
-Execute "python" -Argument "main.py" `
-WorkingDirectory "C:\jiyun_tallybook\server"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0
Register-ScheduledTask `
-TaskName "MyService" -Action $action `
-Trigger $trigger -Settings $settings `
-RunLevel Highest -User "SYSTEM" -Force
-ExecutionTimeLimit 0 关闭超时限制,否则任务会被自动终止。
日志重定向
Task Scheduler 默认不捕获输出,需要显式重定向:
Start-Process -FilePath "python" ` -ArgumentList "main.py" ` -WorkingDirectory "C:\myapp" ` -RedirectStandardOutput "C:\myapp\logs\app.log" ` -RedirectStandardError "C:\myapp\logs\app.log" ` -WindowStyle Hidden
自动化部署脚本
用 Paramiko 从本地开发机直接 SSH 上传文件并重启服务, 避免每次都要手动 RDP 进去操作:
import paramiko, os
HOST = "106.52.93.127"; USER = "Administrator"
KEY = r"d:\workspace\deploy_key"
FILES = [
("main.py", "C:/myapp/main.py"),
("routers/api.py","C:/myapp/routers/api.py"),
]
key = paramiko.RSAKey.from_private_key_file(KEY)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(HOST, 22, USER, pkey=key)
sftp = client.open_sftp()
for local, remote in FILES:
sftp.put(local, remote)
print(f" ✓ {local}")
sftp.close()
client.exec_command("sc stop myservice")
import time; time.sleep(2)
client.exec_command("sc start myservice")
client.close()
print("Deploy done.")
端口冲突排查
启动失败时先查端口是否被占:
netstat -ano | findstr :8000 # 找到 PID 后查进程 tasklist | findstr