[2core]WorkerService在Windows和Linux下部署与运行

一、概述

从.net framework迁移到.net core,除了要迁移基于asp.net的web程序,还有一个项目也是比较重要的,即服务程序或叫守护进程。在.net core中创建workerservice程序已经不同于.net framework,首先workerservice是按照DI方式管理,第二workerservice实现了真正的跨平台,第三部署安装需要结合操作系统工具,windows下使用sc.exe,linux下使用systemd。

特别说明,本文不会记录操作系统的安装和配置,也不会记录dotnet运行环境的安装和使用,所以如果你有这方面的需求,我表示很抱歉,请自行搜索,或去微软官网查看dotnet运行环境的安装和使用教程,本文更不会记录Docker环境的部署。

操作环境说明:

1.设计编程在Windows11 + VS2022 + dotnet6

2.测试环境在Windows Server 2022和Debian11

好了,继续下边的工作。

二、设计

此程序主要目的是将workerservice程序从.net framework迁移到.net core,设计的功能是服务启动和结束时输出日志,启动后服务持续输出日志(条/秒)。

此程序主要包含以下部分功能:

第一,日志log4net的配置:在asp.netcore中未曾见到的log4net抽疯病,在workerservice中见到了,主要是log4net.config配置文件引起的。

第二,appsettings.json配置文件的操作:同在asp.netcore中操作一样

第三,worker类的定义和功能实现:遇到了不少问题,不过都一一解决了。

三、编码

1.日志相关编码

日志编码主要设计三个文件:LogHelper、Log4Writer和log4net.config

1.1.LogHelper文件主要负责启动log4net。需要在Program文件中调用启动。

public class LogHelper
    {
        private static object _objLocker = new object();
        private static bool _isConfigure = false;
        public static void Configure()
        {
            if (!_isConfigure)
            {
                lock (_objLocker)
                {
                    if (!_isConfigure)
                    {
                        _isConfigure = true;
                        var logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config");
                        XmlConfigurator.Configure(new FileInfo(logPath));
                    }
                }
            }
        }
    }

1.2.Log4Writer文件主要提供程序输出日志到文件

public class Log4Writer
    {
        private static readonly log4net.ILog loginfo = log4net.LogManager.GetLogger("loginfo");
        private static readonly log4net.ILog logerror = log4net.LogManager.GetLogger("logerror");
        public static void WriteLog(string info)
        {
            if (loginfo.IsInfoEnabled)
            {
                loginfo.Info(info);
            }
        }

        public static void WriteLog(string info, Exception ex)
        {
            if (logerror.IsErrorEnabled)
            {
                logerror.Error(info, ex);
            }
        }
    }

1.3.log4net.config文件主要定义日志的输出信息。如果不使用DI模式那么DiocAppender相关的配置删除即可。

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
    <root>
        <!--日志级别: NONE > FATAL > ERROR > WARN > INFO > DEBUG > ALL -->
        <priority value="ALL"/>
        <level value="ALL" />
        <appender-ref ref="DiocAppender" />
    </root>
    <!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
    <appender name="DiocAppender" type="log4net.Appender.RollingFileAppender" >
        <!--日志输出到exe程序这个相对目录下-->
        <param name="File" value="logs/diocs/" />
        <!--输出的日志不会覆盖以前的信息-->
        <param name="AppendToFile" value="true" />
        <!--备份文件的个数-->
        <param name="MaxSizeRollBackups" value="50" />
        <!--最小锁定模型以允许多个进程可以写入同一个文件-->
        <param name="MaxFileSize" value="10240" />
        <!--当前日志文件的最大大小-->
        <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
        <!--是否使用静态文件名-->
        <param name="StaticLogFileName" value="false" />
        <!--日志文件名-->
        <DatePattern value="yyyyMMdd".txt"" />
        <!--文件创建的方式,这里是以Date方式创建-->
        <param name="RollingStyle" value="Date" />
        <!--输出级别在INFO和ERROR之间的日志-->
        <filter type="log4net.Filter.LevelRangeFilter">
            <param name="LevelMin" value="ALL" />
            <param name="LevelMax" value="FATAL" />
        </filter>
        <!--信息日志布局-->
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="date [%thread] %-5level %logger - %message%newline" />
        </layout>
    </appender>

    <!--错误日志类-->
    <logger name="logerror">
        <!--日志类的名字-->
        <level value="ALL" />
        <!--定义记录的日志级别-->
        <appender-ref ref="ErrorAppender" />
    </logger>
    <!--信息日志类-->
    <logger name="loginfo">
        <!--日志类的名字-->
        <level value="ALL" />
        <!--定义记录的日志级别-->
        <appender-ref ref="InfoAppender" />
    </logger>
    <!--错误日志附加介质-->
    <!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
    <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender">
        <!--日志输出到exe程序这个相对目录下-->
        <param name="File" value="logs/errors/" />
        <!--输出的日志不会覆盖以前的信息-->
        <param name="AppendToFile" value="true" />
        <!--备份文件的个数-->
        <param name="MaxSizeRollBackups" value="50"/>
        <!--最小锁定模型以允许多个进程可以写入同一个文件-->
        <param name="MaxFileSize" value="10240" />
        <!--当前日志文件的最大大小-->
        <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
        <!--是否使用静态文件名-->
        <param name="StaticLogFileName" value="false" />
        <!--日志文件名-->
        <param name="DatePattern" value="yyyyMMdd".txt"" />
        <!--文件创建的方式,这里是以Date方式创建-->
        <param name="RollingStyle" value="Date" />
        <!--错误日志布局-->
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="[%t]异常类:%c [%x] 异常信息:%m%n"  />
        </layout>
    </appender>
    <!--信息日志附加介质-->
    <!-- name属性指定其名称,type则是log4net.Appender命名空间的一个类的名称,意思是,指定使用哪种介质-->
    <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
        <!--日志输出到exe程序这个相对目录下-->
        <param name="File" value="logs/infos/" />
        <!--输出的日志不会覆盖以前的信息-->
        <param name="AppendToFile" value="true" />
        <!--备份文件的个数-->
        <param name="MaxSizeRollBackups" value="50" />
        <!--最小锁定模型以允许多个进程可以写入同一个文件-->
        <param name="MaxFileSize" value="10240" />
        <!--当前日志文件的最大大小-->
        <param name="lockingModel"  type="log4net.Appender.FileAppender+MinimalLock" />
        <!--是否使用静态文件名-->
        <param name="StaticLogFileName" value="false" />
        <!--日志文件名-->
        <param name="DatePattern" value="yyyyMMdd".txt"" />
        <!--文件创建的方式,这里是以Date方式创建-->
        <param name="RollingStyle" value="Date" />
        <!--信息日志布局-->
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%m%n"  />
        </layout>
    </appender>
</log4net>

2.appsettings.json相关编码

它的相关编码主要涉及三个文件:ConfigFiles/AppSetting、ConfigFiles/ConfigFileManager和Consts/ConstFiles

2.1.ConfigFiles/AppSetting文件主要管理与appsettings.json相关的配置信息。

public class AppSetting
    {
        private static object _objLocker = new object();
        private static AppSetting _instance;
        private AppSetting() { }
        public static AppSetting Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objLocker)
                    {
                        if (_instance == null)
                        {
                            _instance = new AppSetting();
                            AppSettingConfig.Load(_instance);
                        }
                    }
                }
                return _instance;
            }
        }
        private class AppSettingConfig
        {
            private static AppSetting _appSetting;
            public static void Load(AppSetting appSetting)
            {
                _appSetting = appSetting;

                Compute();
                ChangeToken.OnChange(() => ConfigFileManager.Instance.AppSetting.GetReloadToken(), () => { Change(); });
            }
            private static void Compute()
            {
                _appSetting.ServiceName = ConfigFileManager.Instance.AppSetting.GetValue<string>(ConstFiles.AppSettings_ServiceName);
            }
            private static void Change()
            {

            }
        }

        #region Object Propertries
        public string ServiceName { get; private set; }
        #endregion
    }

2.2.ConfigFiles/ConfigFileManager文件主要负责管理配置文件信息。

public class ConfigFileManager
    {
        private static object _objLocker = new object();
        private static ConfigFileManager _instance;
        private ConfigFileManager() { }
        public static ConfigFileManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objLocker)
                    {
                        if (_instance == null)
                        {
                            _instance = new ConfigFileManager();
                            Create();
                        }
                    }
                }
                return _instance;
            }
        }
        private static void Create()
        {
            //appsettings.json
            _instance.AppSetting = new ConfigurationBuilder()
                        .AddJsonFile(ConstFiles.AppSettings, true, true)
                        .Build();
        }

        public IConfigurationRoot AppSetting { get; private set; }
    }

2.3.Consts/ConstFiles文件主要定义一些与配置文件相关的属性和常量。

public class ConstFiles
    {
        public static string AppPathBase
        {
            get
            {
                return AppDomain.CurrentDomain.BaseDirectory;
            }
        }
        public static string AppDataPathBase
        {
            get
            {

                return Path.Combine(AppPathBase, "Data");
            }
        }
        public static string AppFilePathBase
        {
            get
            {

                return Path.Combine(AppPathBase, "Files");
            }
        }
        public static string AppServicePathBase
        {
            get
            {
                if (SystemUtils.IsDebug)
                {
                    return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Sys_ServicePath);
                }
                else
                {
                    return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), Sys_ServicePath);
                }
            }
        }


        public const string Sys_ServicePath = "XXXXX\\Configuration\\Paas";
        public const string AppSettings = "appsettings.json";
        public const string AppSettings_ServiceName = "ServiceName";
    }

3.worker相关编码

public class Worker : BackgroundService
    {
        private readonly IHostApplicationLifetime _hostApplicationLifetime;

        public Worker(IHostApplicationLifetime hostApplicationLifetime)
        {
            _hostApplicationLifetime = hostApplicationLifetime;
        }
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            try
            {
                while (!stoppingToken.IsCancellationRequested)
                {
                    Log4Writer.WriteLog($"Worker running at: {DateTimeOffset.Now}");
                    await Task.Delay(1000, stoppingToken);
                }
            }
            catch (Exception ex)
            {
                Log4Writer.WriteLog(ex.Message);
            }
        }
        public override Task StartAsync(CancellationToken cancellationToken)
        {
            try
            {
                _hostApplicationLifetime.ApplicationStopping.Register(() => { StopWork(); });
                Log4Writer.WriteLog($"Worker Start at: {DateTimeOffset.Now}");
            }
            catch (Exception ex)
            {
                Log4Writer.WriteLog(ex.Message);
            }
            return base.StartAsync(cancellationToken);
        }
        private void StopWork()
        {
            Log4Writer.WriteLog($"程序将要退出,请不要再接受请求,以及马上处理完待处理请求。");
        }
        public override Task StopAsync(CancellationToken cancellationToken)
        {
            try
            {
                Log4Writer.WriteLog($"Worker Stop at: {DateTimeOffset.Now}");
            }
            catch (Exception ex)
            {
                Log4Writer.WriteLog(ex.Message);
            }
            return base.StopAsync(cancellationToken);
        }
    }

4.测试运行

完成上述设计编码,此时回到Program入口文件,进行此文件的编码,内容如下:

public static void Main(string[] args)
        {
            LogHelper.Configure();

            IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args);
            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
            {
                hostBuilder = hostBuilder.UseWindowsService(o =>
                {
                    o.ServiceName = AppSetting.Instance.ServiceName;
                });
            }
            else
            {
                hostBuilder = hostBuilder.UseSystemd();
            }

            hostBuilder = hostBuilder.ConfigureServices(services =>
            {
                services.AddHostedService<Worker>();
            });

            IHost host = hostBuilder.Build();
            host.Run();
        }

编码中已区分Windows和Linux环境,很显然上述编码依赖两个软件包Microsoft.Extensions.Hosting.WindowsServices和Microsoft.Extensions.Hosting.Systemd。完成上述编码后,就可以使用VS进行调试运行了。

四、部署

通过VS编码测试与实际将程序部署到生产环境还有很多事情要做,比如在Windows环境里,你需要会使用sc.exe工具或编写bat脚本,在Linux环境里,你需要会编码XXXX.service文件。

1.Windows部署

在windows下服务管理主要是sc.exe工具。主要使用以下几条命令:
1.1.注册服务
                    set cmdpath=%~dp0jks.core.test.workerService
                    set svcName=TestWorkerService
                    sc create %svcName% binpath= "%cmdpath%" type= share start= auto displayname= "%svcName%"
                    sc description %svcName% "xxxxxxxxxxxxxxxxxxx"
                    说明:
                            a)~dp0表示批处理脚本所在目录
                            b)svcName表示服务名称
                            c)binpath表示服务程序的位置目录
                            d}start=auto表示该服务在计算机每次重新启动时自动启动并运行(即使没有人登录到计算机)
                            e)displayname表示指定一个友好名称,用于标识用户界面程序中的服务
                            f)sc description %svcName% "xxxxxxxxxxxxxxxxxxx"表示给指定的服务添加描述信息
1.2.启动服务
                    sc start TestWorkerService或net start TestWorkerService
1.3.停止服务
                    sc stop TestWorkerService或net stop TestWorkerService
1.4.卸载服务
                    sc delete TestWorkerService
1.5.注意事项
1.5.1.在windows系统下运行服务,Program中一定要设置服务名称,且与sc.exe create 服务名称 保持一致。

2.Debian部署

在Linux下服务管理主要Systemd程序负责。通常需要为服务程序创建.service配置文件,如MyService.service,这个文件通常放置于etc/systemd/system/目录或usr/lib/systemd/system/目录,这俩目录也没啥却别,主要前者的优先级高一点而已。
2.1.配置文件

[Unit]
Description=Long running service/daemon created from .NET worker template

[Service]
# The systemd service file must be configured with Type=notify to enable notifications.
Type=notify
# will set the Current Working Directory (CWD). Worker service will have issues without this setting
WorkingDirectory=/srv/Worker
# systemd will run this executable to start the service
ExecStart=/srv/Worker/MyService
# to query logs using journalctl, set a logical name here  
SyslogIdentifier=MyService

# Use your username to keep things simple.
# If you pick a different user, make sure dotnet and all permissions are set correctly to run the app
# To update permissions, use 'chown yourusername -R /srv/Worker' to take ownership of the folder and files,
#       Use 'chmod +x /srv/Worker/MyService' to allow execution of the executable file
User=yourusername

# This environment variable is necessary when dotnet isn't loaded for the specified user.
# To figure out this value, run 'env | grep DOTNET_ROOT' when dotnet has been loaded into your shell.
Environment=DOTNET_ROOT=/usr/share/dotnet/dotnet

# This gives time to MyService to shutdown gracefully.
TimeoutStopSec=300

[Install]
WantedBy=multi-user.target

2.2.配置文件修改说明
         2.2.1.Description:服务程序的描述信息。
         2.2.2.WorkingDirectory:服务程序所在目录。
         2.2.3.ExecStart:服务程序的启动位置。
         2.2.4.User:使用时,应将 User=yourusername 项中的 yourusername 改为具体的 linux 系统的登录名。

2.3.查看服务状态
         systemctl status MyService
2.4.启动服务
         systemctl start MyService
2.5.停止服务
         systemctl stop MyService
2.6.开机自动启动服务
         systemctl enable MyService
2.7.禁止开机自启服务
         systemctl disable MyService
2.8.查看服务是否存在服务列表中
         systemctl list-unit-files --type=service

五、总结

经上述设计、编码、部署、测试等过程,发现.net6的worker service程序在windows和linux系统里均运行良好,.net跨平台已不是梦,切实可行。

源码地址:https://gitee.com/kinbor/jks.core.test.workerService

  • 8
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: To publish a .NET Worker Service from a Windows system to a Linux machine, you can follow these general steps: 1. Build your .NET Worker Service project for Linux platform: - Open your project in Visual Studio. - From the Solution Explorer, right-click your project and select "Properties". - In the "Properties" window, select the "Build" tab. - Change the "Target Framework" to ".NET Core" or ".NET 5" (depending on your project). - Change the "Target Runtime" to "linux-x64". - Save the changes and build your project. 2. Create a publish profile for Linux: - Right-click your project in the Solution Explorer and select "Publish". - In the "Publish" window, click on "Create new profile". - Select "Folder" as the publish target. - Choose a folder where you want to publish your application. - Click "Create". 3. Publish your application to the Linux machine: - In the "Publish" window, select the publish profile you just created. - Click on "Settings". - In the "Settings" window, select the "Connection" tab. - Enter the connection details for your Linux machine (e.g. hostname, username, password). - Click "Save". - Click "Publish". 4. Run your Worker Service on Linux: - Connect to your Linux machine using SSH or another remote access method. - Navigate to the folder where you published your application. - Run the application using the command "./YourApplicationName". Note that these steps are general and may need to be adjusted depending on your specific project and environment. Additionally, you may need to install the .NET runtime on the Linux machine if it is not already installed. ### 回答2: 在将Windows系统中的WorkerService发布到Linux上之前,我们需要考虑以下几点。 首先,要确认WorkerService能够在Linux操作系统上运行。由于WindowsLinux之间存在很多区别,所以我们需要确保WorkerService所依赖的框架、库和组件在Linux环境中是可用的。这样才能确保WorkerServiceLinux上的正常运行。 其次,我们需要将WorkerService的代码进行适当的修改,以便与Linux系统相适配。这可能包括更改文件路径、文件夹结构,甚至是代码中的一些特定的Windows相关函数调用。这样才能确保WorkerService不会因为操作系统的不同而出现错误或不支持的操作。 另外,我们还需要根据Linux中的具体要求进行一些额外的配置和安装操作。这可能包括安装特定的组件和服务,配置访问权限以及对网络和端口进行相应的设置。这些操作可以保证WorkerService能够与Linux操作系统和其他部署的服务正常交互。 最后,我们还需要选择适合在Linux部署WorkerService的工具和方法。常用的方式有使用Docker容器化技术,或者使用配置管理工具如Chef、Puppet等进行自动化部署和管理。选择合适的工具和方法可以简化部署过程,提高效率和可维护性。 总结起来,将Windows系统中的WorkerService发布到Linux上需要进行一系列的准备工作,包括确认运行环境的兼容性、修改代码以适应Linux系统、配置和安装相关组件,最后选择合适的工具和部署方法进行发布。这样才能确保WorkerServiceLinux上的顺利运行和正常工作。 ### 回答3: 将Windows系统上的WorkerService发布到Linux系统上时,需要进行一些调整和转换,以确保可靠的运行和良好的适应性。 首先,需要考虑WorkerService的依赖项和库文件。由于WindowsLinux系统的差异,一些依赖项可能无法直接在Linux上使用。因此,需要查找适合Linux系统的替代方案或兼容解决方案,并将其集成到WorkerService中。 其次,需要进行代码调整和适配。由于WindowsLinux系统的文件系统结构和命名规则存在差异,需要对WorkerService中的文件操作、路径处理等进行相应地更改。此外,还需注意处理系统间的特定行为差异,例如文件锁定、线程调度等。 另外,WorkerService的系统服务注册也需要特别处理。Windows上的服务注册和管理过程与Linux不同,因此需要使用适用于Linux的工具来注册WorkerService,并确保它能够正确运行和被系统管理。 然后,进行系统环境的准备和配置。Linux系统通常需要确保安装了适当的依赖项和库文件,以及提供所需的权限和访问控制。同时,还需根据WorkerService的要求进行系统配置,如网络设置、进程限制等。 最后,在发布到Linux系统之前,需要进行充分的测试和验证。这些测试可以包括对WorkerService的各种功能和性能的测试,以及对系统适应性和稳定性的测试。只有通过了这些测试,才能确保WorkerServiceLinux环境下的正常运行。 综上所述,将Windows系统上的WorkerService发布到Linux系统上需要进行各方面的转换和适配工作,包括依赖项处理、代码调整、系统服务注册、环境准备和测试验证等。只有做好这些步骤,才能确保WorkerServiceLinux系统上的顺利运行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值