共计 6645 个字符,预计需要花费 17 分钟才能阅读完成。
一:背景
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()); //3 | |
s.WhenStarted(tc => tc.Start()); //4 | |
s.WhenStopped(tc => tc.Stop()); //5 | |
}); | |
x.RunAsLocalSystem(); //6 | |
x.StartAutomatically(); | |
x.SetDescription("TestService2 Topshelf Host"); //7 | |
x.SetDisplayName("MyService2"); //8 | |
x.SetServiceName("MyService2"); //9 | |
}); //10 | |
var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode()); //11 | |
Environment.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.9 | |
Running 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\win64 | |
C:\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 灵便弱小的多,不晓得大家更喜爱哪一种部署形式呢?欢送留言补充!????????????
更多高质量干货:参见我的 GitHub: dotnetfly
更多高质量干货:参见我的 GitHub: dotnetfly**