.NET Core 3.1 Windows服务低性能服务器重启时偶发启动超时
问题根因
- Windows服务控制管理器(SCM)默认设置30秒启动阈值:服务收到启动指令后,必须在30秒内通过系统接口向SCM返回
SERVICE_RUNNING状态,否则SCM直接判定启动失败、终止启动流程,后续不会自动重试,只能手动触发启动。 - 当前代码使用的.NET Core 3.1内置
UseWindowsService()实现逻辑,会等待Host构建、所有启动阶段初始化逻辑全部执行完成后,才会向SCM上报运行状态。服务器重启阶段,CPU、磁盘IO资源被大量开机自启进程抢占,配置加载、NLog初始化、Startup类中的依赖注入注册、数据库连接预热、本地缓存加载等操作耗时很容易超过30秒阈值。 - 问题仅出现在低性能服务器重启场景的原因:常规运行状态下服务器资源充足,全流程初始化耗时远低于30秒,无异常;只有开机阶段资源争抢高峰时才会触达超时线。
可行解决方案
按改造成本从低到高排序:
- 配置系统层面参数,降低资源争抢、延长超时阈值
以管理员身份执行以下命令,替换命令中{你的服务名}为实际注册的Windows服务名称::: 将服务设为延迟自动启动,等系统核心资源释放后再拉起服务,避开开机资源争抢高峰 sc config {你的服务名} start= delayed-auto :: 配置服务失败自动重试规则,三次失败分别间隔5s、10s、30s重启,24小时重置失败计数 sc failure {你的服务名} reset= 86400 actions= restart/5000/restart/10000/restart/30000 :: 修改全局SCM服务启动超时阈值为2分钟(单位毫秒),适配低性能服务器启动速度 reg add HKLM\SYSTEM\CurrentControlSet\Control /v ServicesPipeTimeout /t REG_DWORD /d 120000 /f注意:修改
ServicesPipeTimeout注册表后需要重启服务器生效。 - 代码层面优化启动流程,快速上报运行状态
- 将
Microsoft.Extensions.Hosting.WindowsServicesNuGet包升级到3.1系列最新补丁版本,修复旧版本中启动状态上报不及时的问题。 - 把Startup中阻塞启动的重逻辑(数据库预热、大缓存加载、第三方远程连接初始化等)从启动管线中剥离,放到自定义后台托管服务中延后执行,保证Host能在数秒内完成初始化、向SCM上报运行状态。核心改造代码如下:
其中public static void Main(string[] args) { var logger = LogHelper.GetLogger(); try { logger.Info($"Starting {Assembly.GetExecutingAssembly().FullName} Branch: {ThisAssembly.Git.Branch} Commit: {ThisAssembly.Git.Commit}"); var host = Host.CreateDefaultBuilder(args) .UseContentRoot(Directory.GetCurrentDirectory()) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }) .ConfigureLogging(logging => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Trace); }) .UseNLog() .UseWindowsService(options => { options.ServiceName = "{你的服务名}"; }) // 注册后台初始化服务,承载重启动逻辑 .ConfigureServices(services => { services.AddHostedService<DeferredInitService>(); }) .Build(); host.Run(); } catch (Exception exception) { logger.Error(exception, "Stopped program because of exception"); throw; } finally { NLog.LogManager.Shutdown(); } }DeferredInitService实现IHostedService接口,将原先阻塞启动的重逻辑放到StartAsync方法中执行,执行过程不阻塞Host启动流程即可。 - 将
- 启动依赖项配置
如果服务依赖数据库、其他基础服务才能正常运行,可以通过sc命令配置服务依赖,等依赖服务启动完成后再拉起当前服务,避免无效启动等待拉长耗时:sc config {你的服务名} depend= mssqlserver/其他依赖服务名
内容的提问来源于stack exchange,提问作者Marco Franceschini
相关产品推荐
相关产品推荐

