跳至主要內容

在 Linux 中守护 .net core 程序

MonoLogueChi大约 2 分钟

说起来很惭愧,作为一个经常写 C# 的程序员,工作了四五年才真的搞明白在 Linux 正确守护 .net core 程序的方式。

起源

事情是这样的,我最近在写一个程序 vuepress-plugin-wxshareopen in new window 后端部分在 Linux 上一直没有正确退出,我就打算花点时间深入研究一下。

Systemd

提到守护进程工具,就不得不提 systemd 了,虽然我以前一直在使用 Supervisor 但是从去年开始,我就转为使用 systemd 了,主要是用起来太简单了。

想要详细了解 systemd,推荐看一下这些文章open in new window

简单来说,用 systemd 守护 .net core 进程应该是使用 Type=exec

但是使用 Type=exec 时,应该正确获得被监控的服务主进程的 pid ,否则 systemd 就要去猜测,大多数情况下都能猜对了,但不排除一些情况下会猜错了。

简单使用脚本

这个时候,我就想到了,可以使用一个脚本,运行程序并获得 pid,但是 sh 脚本我不会写,就打算问一下 ChatGPT 应该怎么写。

脚本应该怎么写
脚本应该怎么写

主体思路有了,再结合 这篇文章open in new window 就可以编写脚本了。

到了这里,我并没有直接编写脚本,而是又想到了一些别的事情。

修改程序

如果我修改一下程序,做成类似 nginx 那样,启动程序时,由程序本身输出一个 pidfile 呢?那不应该是更好吗?

直接开搞,.net core 我还是比较熟悉的,我现在用的是 .NET 7.0 ,写法大概就是下面这样,详细可以看 完整代码open in new window

var builder = WebApplication.CreateBuilder(args);
var appSettings = builder.Configuration.Get<AppSettings>();  // 配置文件

// 省略一堆东西

var app = builder.Build();

// 省略一堆东西

var serviceScope = app.Services.CreateScope();
var s = serviceScope.ServiceProvider;
var life = s.GetRequiredService<IHostApplicationLifetime>();

// 注册启动时事件
life.ApplicationStarted.Register(() =>
{
    // 获取并写入pid文件
    var pid = Process.GetCurrentProcess().Id;
    TextWriter pidWriter = new StreamWriter(appSettings.PidFile);
    pidWriter.Write(pid);
    pidWriter.Flush();
    pidWriter.Close();

    // 省略其他事件
});

// 注册结束后事件
life.ApplicationStopped.Register(() =>
{
    // 删除文件
    if (File.Exists(appSettings.PidFile)) File.Delete(appSettings.PidFile);
    if (!string.IsNullOrWhiteSpace(appSettings.UnixSocket) & File.Exists(appSettings.UnixSocket))
        File.Delete(appSettings.UnixSocket);

    // 省略其他事件
});

app.Run();

这样写了以后,写 .service 脚本就容易多了。

[Unit]
Description=WxShare
After=network.target remote-fs.target nss-lookup.target

[Service]
Type=exec

User=your_username
Group=your_groupname

PIDFile=/path/to/Wx.Share.pid
WorkingDirectory=/path/to/directory
ExecStartPre=/usr/bin/rm -f /path/to/Wx.Share.pid
ExecStart=/path/to/Wx.Share
ExecStartPost=/usr/bin/sleep 1s
KillSignal=SIGTERM

Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

结束

当然,现在还是不完美的,有一些特性还未实现,比如配置热重载等等,这些我会在以后继续研究。