前言

以前总结过一篇基于Quartz+Topshelf+.netcore实现定时任务Windows服务 https://www.cnblogs.com/gt1987/p/11806053.html。回顾起来发现有点野路子的感觉,没有使用.netcore推荐的基于 HostedService 的方式,也没有体现.net core跨平台的风格。于是重新写了一个Sample。

Work Service

首先搭建项目框架。

  • 版本 .netcore3.1
  • 建立一个Console程序项目模板,修改 project 属性为 <Project Sdk="Microsoft.NET.Sdk.Worker">
  • Nuget引入 Microsoft.Extensions.Hosting,支持配置+Logging+注入等基本框架内容。
  • Nuget引入 Quartz.Jobs 组件。

后来发现vs2019实际有一个 Worker Service 项目模板,直接选择建立即可,不用上面这么麻烦~~

QuartzJob、HostedService集成

集成的主要思路为以 HostedService 作为服务承载,启动的时候 加载 QuartzJob 定时任务配置并启动。而 HostedService 则自动接入.netcore服务程序体系。

由于 QuartzJob 暂没有专门的.netcore版本,这里我们首先要作下特别处理,实现一个JobFactory用于集成.net core依赖注入框架。

public class MyJobFactory : IJobFactory
{
private readonly IServiceProvider _serviceProvider; public MyJobFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
{
return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
} public void ReturnJob(IJob job)
{
//IJob已经在.net core容器体系下,应该考虑通过DI的方式 dispose
//IJob对象的销毁 if implement IDisposable
//var dispose = job as IDisposable;
//dispose?.Dispose();
}
}

定义一个SampleJob:

[DisallowConcurrentExecution]
public class SampleJob : IJob
{
private readonly ILogger<SampleJob> _logger; public SampleJob(ILogger<SampleJob> logger)
{
_logger = logger;
} public void Dispose()
{
_logger.LogInformation($"{nameof(SampleJob)} disposed.");
} public async Task Execute(IJobExecutionContext context)
{
_logger.LogInformation($"{nameof(SampleJob)} executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
await Task.CompletedTask;
}
}

定义自己的HostedService,在Start方法里面配置了定时任务并启动

public class QuartzJobHostedService : IHostedService
{
private readonly IScheduler _scheduler;
private readonly ILogger<QuartzJobHostedService> _logger; public QuartzJobHostedService(IScheduler scheduler,
ILogger<QuartzJobHostedService> logger)
{
_scheduler = scheduler;
_logger = logger;
} public async Task StartAsync(CancellationToken cancellationToken)
{
//just for sample test,should configuration in config file when dev
//sample job
var job = CreateJob(typeof(SampleJob));
var trigger = CreateTrigger("SampleJob", "0/5 * * * * ?");
await _scheduler.ScheduleJob(job, trigger, cancellationToken); //disposed job
var job2 = CreateJob(typeof(DisposedSampleJob));
var trigger2 = CreateTrigger("DisposeSampleJob", "0/10 * * * * ?");
await _scheduler.ScheduleJob(job2, trigger2, cancellationToken); await _scheduler.Start(cancellationToken); _logger.LogInformation("jobScheduler started.");
} public async Task StopAsync(CancellationToken cancellationToken)
{
await _scheduler?.Shutdown(cancellationToken); _logger.LogInformation("jobScheduler stoped.");
} private ITrigger CreateTrigger(string name, string cronExpression)
{
return TriggerBuilder
.Create()
.WithIdentity($"{name}.trigger")
.WithCronSchedule(cronExpression)
.Build();
} private IJobDetail CreateJob(Type jobType)
{
return JobBuilder
.Create(jobType)
.WithIdentity(jobType.FullName)
.WithDescription(jobType.Name)
.Build();
}
}

最后我们看一下服务的启动配置,注意IScheduler和IJobFactory的生命周期,这里由于 StdSchedulerFactory.GetDefaultScheduler() 的原因,必须是Singleton来兼容。

class Program
{
static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
host.Run();
} public static IHostBuilder CreateHostBuilder(string[] args) =>
Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureLogging(loggingBuilder =>
{
loggingBuilder.AddConsole();
loggingBuilder.SetMinimumLevel(LogLevel.Information);
})
.ConfigureServices((context, services) =>
{
services.AddTransient<SampleJob>()
.AddTransient<DisposedSampleJob>()
.AddTransient<IDisposableService, DisposableService>()
.AddSingleton<IJobFactory, MyJobFactory>()
.AddSingleton<IScheduler>(sp =>
{
var scheduler = StdSchedulerFactory.GetDefaultScheduler().ConfigureAwait(false).GetAwaiter().GetResult();
scheduler.JobFactory = sp.GetRequiredService<IJobFactory>();
return scheduler;
})
.AddHostedService<QuartzJobHostedService>();
})
//if install by topshelf,don't need this
.UseWindowsService(); }

这样一个简单的定时任务服务就搭建完成了,可以本地启动运行。但是如果要部署到服务器上作为windows服务或者linux服务,还需要作一点额外的工作。

IDisposable 问题

这里插入另外一个话题,我们看到 IJobFactory 有一个 ReturnJob 方法用于处理Job对象的资源释放问题。但是我们知道在.netcore依赖注入体系下,任何通过注入获取的对象一定不能通过自己手动方式来处理资源释放问题。参考官方文档 https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#design-services-for-dependency-injection。那么如何处理需要手动释放例如 IDisposable 的问题呢?

这里我们建立一个继承 IDisposable 接口服务

//通常情况下 不应该将IDisposebale接口 注册为 Transient or Scope。改用工厂模式创建
public interface IDisposableService : IDisposable
{
} public class DisposableService : IDisposableService
{
private ILogger<DisposableService> _logger; public DisposableService(ILogger<DisposableService> logger)
{
_logger = logger;
} public void Dispose()
{
_logger.LogInformation($"{nameof(DisposableService)} has disposed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}.");
}
}

依赖IDisposableService的SampleJob:

/// <summary>
/// 需要dispose
/// 1.IDisposableService可以注册为Singleton,会自动dispose
/// 2.如果不能注册单例,则如本例方式通过IServiceProvider.CreateScope方式处理
/// </summary>
[DisallowConcurrentExecution]
public class DisposedSampleJob : IJob
{
private readonly ILogger<DisposedSampleJob> _logger;
private readonly IServiceProvider _serviceProvider; public DisposedSampleJob(ILogger<DisposedSampleJob> logger,
IServiceProvider serviceProvider)
{
_logger = logger;
_serviceProvider = serviceProvider;
} public async Task Execute(IJobExecutionContext context)
{
//if IDisposableService register Transient,use CreateScope to dispose IDisposableService
//if IDisposableService register singleton,it can be inject directly and dispose automatically
using (var scope = _serviceProvider.CreateScope())
{
var service = scope.ServiceProvider.GetRequiredService<IDisposableService>();
_logger.LogInformation($"{nameof(DisposedSampleJob)} executed at {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}.");
await Task.CompletedTask;
}
}
}

这里如注释,要么将 IDisposableService 注册为单例,直接注入引用,.net core DI框架会自动处理资源释放。如果某些原因不能注册单例,则需要采取不太推荐的 定位器模式,如上面代码中实现方式来处理。

部署windows服务

如果要部署到windows服务,则还需要引入 Microsoft.Extensions.Hosting.WindowsServices组件,在构建 IHostBuilder的时候加入 UseWindowsService()即可。它主要的功能是将整个系统的生命周期接入windows服务的生命周期。(默认的是console控制台程序生命周期)。

然后就是烦人的部署到windows服务。这里提供了install和uninstall2个脚本,使用的是window sc工具。

install.bat

set serviceName=QuartzJob.Sample.JobService
set serviceFilePath=F:\gt_work\MyProject\git_project\gt.SomeSamples\QuartzJob.Sample\bin\Release\netcoreapp3.1\QuartzJob.Sample.exe
set serviceDescription=sample job sc create %serviceName% BinPath=%serviceFilePath%
sc config %serviceName% start=auto
sc description %serviceName% %serviceDescription%
sc start %serviceName%
pause

uninstall.bat

set serviceName=QuartzJob.Sample.JobService

sc stop   %serviceName%
sc delete %serviceName% pause

通过管理员权限启动即可正常部署和卸载windows服务

部署Linux服务

如果要部署到Linux服务的话,我查到的有两种方式,一种是Systemd方式,需要引入 Microsoft.Extensions.Hosting.Systemd组件,加入 UseSystemd。另一种方式使用SuperVisor来创建服务。这里我尝试使用了SuperVisor来实现。

我的Linux版本是Centos7,这里刚开始我找了一台Centos6的机器,在安装.netcore sdk这步就走不下去了,这里注意下。

  • 注册microsoft密钥 sudo rpm -Uvh https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm

  • 安装.netcore sudo yum install dotnet-sdk-3.1

  • 安装SuperVisor yum install -y supervisor,这里也许要安装依赖 yum install epel-release

  • 配置启动,在/etc/supervisord.d/ 新建 QuartzJob.Sample.ini 配置文件。directory指向到发布包目录。

      [program:QuartzJob.Sample]
    command=dotnet QuartzJob.Sample.dll
    directory=~/gt/QuartzJob.Sample
    environment=ASPNETCORE__ENVIRONMENT=Production
    user=root
    stopsignal=INT
    autostart=false
    autorestart=false
    startsecs=1
    stderr_logfile=/var/log/quartzJob.err.log
    stdout_logfile=/var/log/quartzJob.out.log

这么做的原因可以查看 /etc/supervisord.conf 配置中这一段 files=supervisord.d/*ini,表示默认加载启动supervisord.d目录下的 .ini文件配置

  • 启动SuperVisor,sudo service supervisord start。服务正常启动

集成Topshelf

Topshelf组件在framework时代是一款非常方便生成服务windows服务的工具,可以通过代码的方式配置并生成windows服务。可惜目前没有看到.netcore的配合版本。且由于依赖windows系统,似乎不太切合.netcore跨平台的特性。这里给出集成的方式,只适用windows平台。

    static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build(); HostFactory.Run(x =>
{
x.Service<IHost>(s =>
{
s.ConstructUsing(n => host);
s.WhenStarted(tc => tc.StartAsync());
s.WhenStopped(tc => tc.StopAsync().Wait());
});
x.RunAsLocalSystem(); x.SetDisplayName("Quartz.Sample.JobService");
x.SetServiceName("Quartz.Sample.JobService");
});
}

install 命令:

  • .\QuartzJob.Sample.exe install
  • .\QuartzJob.Sample.exe start

uninstall 命令:

  • .\QuartzJob.Sample.exe stop
  • .\QuartzJob.Sample.exe uninstall

这里的原理就是用Topshelf的Host替代.netcore的Host,在Topshelf Host启动时再启动.netcore Host。反正看着很变扭。

另外特别注意 s.WhenStarted(tc => tc.StartAsync()); 这里使用的是StartAsync方法而不是Start方法,因为Start方法是同步堵塞的,在部署到windows服务时,由于这一步堵塞,会导致windows服务一直卡在启动状态直至超时启动失败。

.NET CORE QuartzJob定时任务+Windows/Linux部署的更多相关文章

  1. .NET Core Generic Host Windows服务部署使用Topshelf

    此文源于前公司在迁移项目到.NET Core的过程中,希望使用Generic Host来管理定时任务程序时,没法部署到Windows服务的问题,而且官方也没给出解决方案,只能关注一下官方issue # ...

  2. windows/Linux下设置ASP.Net Core开发环境并部署应用

    10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...

  3. linux centos7 和 windows下 部署 .net core 2.0 web应用

    centos7 下部署asp.net core 2.0应用 安装CentOS7 配置网络[可选] 安装.Net core2.0 创建测试Asp.net Core应用程序 正式部署项目 安装VMware ...

  4. Windows 服务器部署 asp.net core

    踩坑日记与 Windows 服务器部署 asp.net core 指南. 准备 操作系统:Windows Server 2008 R2 或更高版本 文件: Microsoft Visual C++ 2 ...

  5. .Net Core Linux部署

    .Net Core是微软最新的开源框架跨平台框架 官网文档 .Net Core相关发布指令,以及发布RId便于查看 RID链接 .Net Core要想发布到Linux有俩种方案,分别是依赖框架的部署( ...

  6. [亲测]ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问

    前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...

  7. .NET Core多平台开发体验[3]: Linux (Windows Linux子系统)

    如果想体验Linux环境下开发和运行.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像R ...

  8. [亲测]七步学会ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问

    前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...

  9. 怎么部署 .NET Core Web项目 到linux

    .NET Core is free, open source, cross platform and runs basically everywhere. STEP 0 - GET A CHEAP H ...

随机推荐

  1. Maven依赖管理之BOM

    目录 什么是BOM 一个BOM的格式 怎么使用BOM 通过parent引用 通过dependencyManagement引用 怎么查看依赖的某个BOM的具体清单 版本冲突时的一些规则 何为依赖调节 参 ...

  2. (转载)Tomcat 7集群浅析

    本文转载自:http://blog.csdn.net/wangyangzhizhou. 如有侵权,请联系处理!   简介 每个节点都要维护一份集群节点信息列表,集群组通知的默认实现是在使用 UDP 数 ...

  3. Dynamically allocated memory 动态分配内存【malloc】Memory leaks 内存泄漏

    内存泄露Memory leaks :没有指针指向原来a分配出来的那段空间了

  4. Book of Shaders 04 - 网格噪声:Worley Noise

    0x00 思路 假设要生成 4 个网格,可以先在空间中指定 4 个特征点.对于每个像素点,计算它到最近特征点的距离,将这个距离当作结果值输出. #ifdef GL_ES precision mediu ...

  5. IDM下载度盘文件

    百度网盘是百度公司推出的一款个人云服务产品.百度网盘官方版操作简单,我们打开后就可以使用该软件来上传.下载文件等.不仅如此百度网盘软件还可以批量上传文件.支持断点传续等功能,重要的是上传的文件不会占用 ...

  6. Linux 百度网盘卡在等待页

    解决办法1 如果无法登录百度网盘,一直在加载,运行命令:rm -rf ~/baidunetdisk 然后关闭百度客户端,重新登录百度客户端. 解决办法2 如果已经登录进百度网盘,退出百度网盘时,不要直 ...

  7. Combine 框架,从0到1 —— 5.Combine 常用操作符

    本文首发于 Ficow Shen's Blog,原文地址: Combine 框架,从0到1 -- 5.Combine 常用操作符. 内容概览 前言 print breakpoint handleEve ...

  8. 超级简单的照片画廊MVC

    下载Gallery.zip - 23.5 MB 介绍 我想在我的个人网站上添加一个简单的图片库,但找不到任何合适的方法来从文件夹而不是数据库中挑选图片.也许我应该看得更仔细些!尽管如此,下面是我实现的 ...

  9. 在自己电脑上查看git远程分支列表比实际云端的远程分支要多一些

    问题 最近打开一个很久没有打开过的项目,使用git branch -a查看了一下所以分支,其中有些远程分支没有什么用了 于是准备去gitlab上删除它,结果到gitlab上发现没有这些分支,猜测是自己 ...

  10. 从0到1进行Spark history分析

    一.总体思路 以上是我在平时工作中分析spark程序报错以及性能问题时的一般步骤.当然,首先说明一下,以上分析步骤是基于企业级大数据平台,该平台会抹平很多开发难度,比如会有调度日志(spark-sub ...