首页 > 系统相关 >[2core]WorkerService在Windows和Linux下部署与运行

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

时间:2022-12-30 13:22:35浏览次数:62  
标签:string 2core instance Windows private AppSetting static Linux public

一、概述

从.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&quot;.txt&quot;" />
        <!--文件创建的方式,这里是以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&quot;.txt&quot;" />
        <!--文件创建的方式,这里是以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&quot;.txt&quot;" />
        <!--文件创建的方式,这里是以Date方式创建-->
        <param name="RollingStyle" value="Date" />
        <!--信息日志布局-->
        <layout type="log4net.Layout.PatternLayout">
            <param name="ConversionPattern" value="%m%n"  />
        </layout>
    </appender>
</log4net>
View Code

 

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

标签:string,2core,instance,Windows,private,AppSetting,static,Linux,public
From: https://www.cnblogs.com/Jkinbor/p/17014686.html

相关文章

  • Windows下PHP开发环境搭建
    介绍几种快速搭建PHP本地开发环境的方式1.xampp2.phpstudy3.Laragon集成开发环境: https://laragon.org/docs/4.Wamp5.在子系统中安装Linux版本的lnmp环境(1.使用宝......
  • linux 使用 nohup 运行 python 脚本,脚本中不要有input
    如题服务器系统由windows换为linux后阿里云的个人服务器报警性能受限但是服务器上运行的程序仅有数据采集,windows服务器上运行绰绰有余按理来说linux上会更轻松才对......
  • Linux Kernel 2.6.28 以上有BUG,系统运行第208.5天down机!
    简介: 业务服务器有一台服务器出现意外down机,服务器ping不通、无法登陆,本想通过公司KVM系统登陆系统重启解决,登陆KVM后发现系统屏幕打印大量的内核错误,KVM无法使用、无......
  • CRT + LRZSZ 进行远程linux系统服务器文件上传下载
       CRT+LRZSZ进行远程linux系统服务器文件上传下载\简单\方便\实用)安装这里就不做叙述了,因为太简单了,首先看下是否安装[root@localhost~]#rpm-qa|greprzszlrz......
  • 在windows上构建OpenCascade
    基于作者QuaoarsWorkshop的视频OpenCascadeLessons,讲的非常详细,观看需要魔法什么是OCCT?.首先,OpenCASCADETechnologySDK是一个几何建模库,或者更简单地说,它是一个几......
  • Linux 脚本设置开机自启chkconfig
    第一种方法:/etc/init.d#在init.d下创建脚本脚本格式:必须添加这2行否则无法添加#!/bin/bash#chkconfig:23458196#description:Starttomcat......添加权限:chmod777......
  • 虚拟机--部署欧拉系统--Linux
    一、首先,去欧拉系统官网下载相应ISO镜像​​https://www.openeuler.org/zh/download/​​​二、准备好VMware虚拟机---OracleVMvirtual我也用过,不过部署欧拉系统,感觉VMwar......
  • linux离线安装postgresql
    1.软件包说明软件包 说明postgresqllibrariesandclientbinariespostgresql-server coredatabaseserverpostgresql-contrib additionalsuppliedmodulespostgresql-......
  • Linux 系統移植时在 kernel 中开启 I210 网卡驱动
    I210网卡是一个笔记通用的网卡,现在的kernel里面都带有对应的驱动,不过大部分kernel源码是默认不开启的,我们系统移植时需要用到就把它开启即可。1.makemenuconfig......
  • Linux常用命令大全
    Linux常用命令大全(非常全!!!)系统信息 cat/etc/redhat-release显示安装的系统版本getconfLONG_BIT显示系统是多少位的(32/64bit)centos7的防火墙相关:firewall-cmd--state......