把 Console 部署成 Windows 服务,四种方式总有一款适合你!
一:背景
1. 講故事
上周有一個項目交付,因為是醫院級項目需要在客戶的局域網獨立部署。程序:netcore 2.0,操作系統:windows server 2012,坑爹的事情就來了, netcore sdk 一直裝不上,網上找了資料說需要先安裝 Visual C++ Redistributable for Visual Studio 2015, 開開心心下載下來又是安裝失敗,再次找資料說要打一堆 系統補丁,搞了一天!!!????????????
環境總算是裝好了,因為是 Console 服務程序,還得給它做成 windows service,看公司以前的部署方式都是采用 vs 的 windows service 模板,如下圖:
怎么說呢,這種方式太老舊了,這篇就來聊聊除了這種還有其他三種很有意思的服務部署方式,干脆拿在一起比較比較吧!
2. 測試代碼
為了能更加正規化一些,我在 Console 中監聽 Ctrl + C 事件,代碼如下:
public?class?Program{public?static?void?Main(string[]?args){var?dir?=?AppDomain.CurrentDomain.BaseDirectory;var?cts?=?new?CancellationTokenSource();var?bgtask?=?Task.Factory.StartNew(()?=>?{?TestService.Run(cts.Token);?});Console.CancelKeyPress?+=?(s,?e)?=>{TestService.Log($"{DateTime.Now}?后臺測試服務,準備進行資源清理!");cts.Cancel();bgtask.Wait();TestService.Log($"{DateTime.Now}?恭喜,Test服務程序已正常退出!");};TestService.Log($"{DateTime.Now}?后端服務程序正常啟動!");bgtask.Wait();}}有了這個模板,再定義一個 TestService,用于不斷的執行后臺任務,代碼如下:
public?class?TestService{public?static?void?Run(CancellationToken?token){while?(!token.IsCancellationRequested){Console.WriteLine($"{DateTime.Now}:?1.?獲取mysql");System.Threading.Thread.Sleep(2000);Console.WriteLine($"{DateTime.Now}:?2.?獲取redis");System.Threading.Thread.Sleep(2000);Console.WriteLine($"{DateTime.Now}:?3.?更新monogdb");System.Threading.Thread.Sleep(2000);Console.WriteLine($"{DateTime.Now}:?4.?通知kafka");System.Threading.Thread.Sleep(2000);Console.WriteLine($"{DateTime.Now}:?5.?所有業務處理完畢");System.Threading.Thread.Sleep(2000);}}public?static?void?Log(string?msg){Console.WriteLine(msg);File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory?+?"//1.log",?$"{msg}\r\n");}}二:四種服務部署方式
1. 傳統的 Windows Service 模板
相信做過 windowsservice 部署的朋友都知道這種方式,需要在 vs 中新建模板,然后定義一個子類 MySerivce 繼承于 ServiceBase ,重寫父類的 OnStart 和 OnStop 方法,代碼如下:
partial?class?MyService?:?ServiceBase{CancellationTokenSource?cts?=?new?CancellationTokenSource();Task?bgtask;public?MyService(){InitializeComponent();}protected?override?void?OnStart(string[]?args){//?TODO:?Add?code?here?to?start?your?service.bgtask?=?Task.Factory.StartNew(()?=>?{?TestService.Run(cts.Token);?});}protected?override?void?OnStop(){//?TODO:?Add?code?here?to?perform?any?tear-down?necessary?to?stop?your?service.cts.Cancel();bgtask.Wait();}}再重構一下 Main 方法:
public?class?Program{public?static?void?Main(string[]?args){ServiceBase.Run(new?MyService());}}最后執行 publish 發布,用 windows ?自帶的 sc 安裝服務。
sc?create?MyService?BinPath=E:\net5\ConsoleApp1\ConsoleApp2\bin\Release\netcoreapp3.1\publish\ConsoleApp2.exe sc?start?MyService為了驗證程序是否運行正常,可以去服務面板以及安裝路徑查看啟動日志。
接下來說說優缺點吧:
缺點:需要修改代碼,而且一旦代碼改完后,就不能再雙擊 exe 執行,導致無法調試。
優點:不需要額外依賴,全部采用內建技術。
2. 使用開源的 Topshelf
大家有興趣可以看一下它的官網:http://topshelf-project.com ?比較輕便簡潔,使用 nuget Install-Package Topshelf 接入項目,按照官方demo我需要在 TestService 中實現 Start 和 Stop 方法,修改如下:
public?class?TestService{CancellationTokenSource?cts?=?new?CancellationTokenSource();CancellationToken?token;Task?bgtask;public?TestService(){token?=?cts.Token;}public?void?Start(){bgtask?=?Task.Run(()?=>{while?(!token.IsCancellationRequested){Log($"{DateTime.Now}:?1.?獲取mysql");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?2.?獲取redis");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?3.?更新monogdb");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?4.?通知kafka");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?5.?所有業務處理完畢");System.Threading.Thread.Sleep(2000);}});}public?void?Stop(){cts.Cancel();bgtask.Wait();}public?static?void?Log(string?msg){Console.WriteLine(msg);File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory?+?"1.log",?$"{msg}\r\n");}}接下來再改造一下 Main 方法,使用它的 HostFactory 類,代碼如下:
public?static?void?Main(string[]?args){var?rc?=?HostFactory.Run(x?=>???????????????????????????????????//1{x.Service<TestService>(s?=>???????????????????????????????????//2{s.ConstructUsing(name?=>?new?TestService());????????????//3s.WhenStarted(tc?=>?tc.Start());?????????????????????????//4s.WhenStopped(tc?=>?tc.Stop());??????????????????????????//5});x.RunAsLocalSystem();???????????????????????????????????????//6x.StartAutomatically();x.SetDescription("TestService2?Topshelf?Host");???????????????????//7x.SetDisplayName("MyService2");??????????????????????????????????//8x.SetServiceName("MyService2");??????????????????????????????????//9});?????????????????????????????????????????????????????????????//10var?exitCode?=?(int)Convert.ChangeType(rc,?rc.GetTypeCode());??//11Environment.ExitCode?=?exitCode;}從上面代碼可以看出,主要還是做一些服務的信息配置,然后就可以發布項目,使用 xxx.exe install 進行服務安裝,如下圖:
E:\net5\ConsoleApp1\ConsoleApp5\bin\Release\netcoreapp3.1\publish2>ConsoleApp5.exe?install Configuration?Result: [Success]?Name?MyService2 [Success]?Description?TestService2?Topshelf?Host [Success]?ServiceName?MyService2 Topshelf?v4.2.1.215,?.NET?Framework?v3.1.9Running?a?transacted?installation.Beginning?the?Install?phase?of?the?installation. Installing?MyService2?service Installing?service?MyService2... Service?MyService2?has?been?successfully?installed.The?Install?phase?completed?successfully,?and?the?Commit?phase?is?beginning.The?Commit?phase?completed?successfully.The?transacted?install?has?completed.從輸出信息來看已經安裝成功,大家感覺這種方式優缺點如何?
缺點:需要安裝第三方工具包,需要修改代碼,而且還挺大的。。。
優點:雙擊也可調試,實現了系統的一些內建監聽,比如 Ctrl + C
3. 使用微軟新內置的 Hosting
說到這個 Hosting 相信大家不會陌生,在 netcore 中不管是 Console, MVC,WebApi 都是 Console 模式,比如我新建一個如下 WebApi。
這里我就有想法了,能不能把 Main 中的 Hosting 扣出來給我的服務用,那真的是????????了,還別說,真的可以,安裝一個 hosting + for windowsservice 即可。
nuget?Install-Package?Microsoft.Extensions.Hosting nuget?Install-Package?Microsoft.Extensions.Hosting.WindowsServices值得慶幸的是,包的最小依賴是 .NETStandard 2.0 ,意味著 .NET Framework 4.6.1 + 和 .NetCore 2.0 + 都可以用的上,????????
接下來就是改造,讓 TestService 重寫的父類 BackgroundService 中的 ExecuteAsync 方法,如下代碼:
public?class?TestService?:?BackgroundService{protected?override?Task?ExecuteAsync(CancellationToken?stoppingToken){return?Task.Run(()?=>{while?(!stoppingToken.IsCancellationRequested){Log($"{DateTime.Now}:?1.?獲取mysql");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?2.?獲取redis");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?3.?更新monogdb");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?4.?通知kafka");System.Threading.Thread.Sleep(2000);Log($"{DateTime.Now}:?5.?所有業務處理完畢");System.Threading.Thread.Sleep(2000);}});}public?static?void?Log(string?msg){Console.WriteLine(msg);File.AppendAllText(AppDomain.CurrentDomain.BaseDirectory?+?"1.log",?$"{msg}\r\n");}}然后再改造 Main 方法。
public?class?Program{public?static?void?Main(string[]?args){CreateHostBuilder(args).Build().Run();}public?static?IHostBuilder?CreateHostBuilder(string[]?args)?=>Host.CreateDefaultBuilder(args).UseWindowsService().ConfigureServices((hostContext,?services)?=>{services.AddHostedService<TestService>();});}哇!是不是熟悉的代碼映入眼前,雙擊 Console 是不是更加熟悉了哈~~~
最后可以用 sc 命令做成服務。
缺點:有少量的代碼侵入性,引入的依賴稍多
優點:微軟正派血統,功能強大,內建日志支持
4. nssm 第三方工具
前面三種要么是內建模板,要么是安裝 dll 的方式,那有沒有一種真的可以對代碼 零侵入 呢?大千世界無奇不有,可以看一下這款工具:http://www.nssm.cc ?,你無需修改任何代碼, 直接發布代碼后用下面命令安裝即可:
C:\Windows\system32>cd?C:\xcode\soft\nssm-2.24\win64C:\xcode\soft\nssm-2.24\win64>nssm??install?TestService3?E:\net5\ConsoleApp1\ConsoleApp6\bin\Release\netcoreapp3.1\publish\ConsoleApp6.exe?&&?nssm?start?TestService3 Service?"TestService3"?installed?successfully! TestService3: START:?操作成功完成。看到沒有,我真的沒有動任何代碼,服務就安裝完成了。
缺點:需要安裝第三方工具
優點:對代碼零侵入
三:總結
如果讓我選擇的話,我喜歡 3+4 的組合,代碼層面我更愿意使用 微軟新的 Hosting 承載,服務部署上更喜歡 nssm,畢竟它比 sc 靈活強大的多,不知道大家更喜歡哪一種部署方式呢?歡迎留言補充!????????????
總結
以上是生活随笔為你收集整理的把 Console 部署成 Windows 服务,四种方式总有一款适合你!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: BeetleX之Websocket服务使
- 下一篇: TensorFlow 2学习和工业CV领