.NET CORE QuartzJob定时任务+Windows/Linux部署
前言
以前总结过一篇基于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部署的更多相关文章
- .NET Core Generic Host Windows服务部署使用Topshelf
此文源于前公司在迁移项目到.NET Core的过程中,希望使用Generic Host来管理定时任务程序时,没法部署到Windows服务的问题,而且官方也没给出解决方案,只能关注一下官方issue # ...
- windows/Linux下设置ASP.Net Core开发环境并部署应用
10分钟学会在windows/Linux下设置ASP.Net Core开发环境并部署应用 创建和开发ASP.NET Core应用可以有二种方式:最简单的方式是通过Visual Studio 2017 ...
- linux centos7 和 windows下 部署 .net core 2.0 web应用
centos7 下部署asp.net core 2.0应用 安装CentOS7 配置网络[可选] 安装.Net core2.0 创建测试Asp.net Core应用程序 正式部署项目 安装VMware ...
- Windows 服务器部署 asp.net core
踩坑日记与 Windows 服务器部署 asp.net core 指南. 准备 操作系统:Windows Server 2008 R2 或更高版本 文件: Microsoft Visual C++ 2 ...
- .Net Core Linux部署
.Net Core是微软最新的开源框架跨平台框架 官网文档 .Net Core相关发布指令,以及发布RId便于查看 RID链接 .Net Core要想发布到Linux有俩种方案,分别是依赖框架的部署( ...
- [亲测]ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问
前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...
- .NET Core多平台开发体验[3]: Linux (Windows Linux子系统)
如果想体验Linux环境下开发和运行.NET Core应用,我们有多种选择.一种就是在一台物理机上安装原生的Linux,我们可以根据自身的喜好选择某种Linux Distribution,目前来说像R ...
- [亲测]七步学会ASP.NET Core 2.0怎么发布/部署到Ubuntu Linux服务器并配置Nginx反向代理实现域名访问
前言 ASP.NET Core 2.0 怎么发布到Ubuntu服务器?又如何在服务器上配置使用ASP.NET Core网站绑定到指定的域名,让外网用户可以访问呢? 步骤 第1步:准备工作 一台Liun ...
- 怎么部署 .NET Core Web项目 到linux
.NET Core is free, open source, cross platform and runs basically everywhere. STEP 0 - GET A CHEAP H ...
随机推荐
- SVN合并分支提示不是祖先关系
开发:dev 测试:test 开发完成后,需要合并到test然后部署,进入测试. F:主干 合并到那里,那里就是主干(要合并到的分支)[起始] T:分支 从那里合并那里就是分支[结束] 备注:需要精确 ...
- 01 学习人工智能,不做笔记?做笔记不知道如何输入数学公式?“onenote+Mathematics Add-In”拯救你!onenote安装数学输入公式插件Microsoft Mathematics Add-In for Word and OneNote教程走一波
一.Microsoft Mathematics Add-In 插件下载 Microsoft Mathematics Add-In for Word and OneNote插件下载链接: https:/ ...
- Jupyter 绘图怎么显示中文
1. 简单加2行代码即可. import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = [u'SimHei'] plt.rcPa ...
- MCU(Micro Control Unit)中文名称为微控制单元
参考:http://www.elecfans.com/dianzichangshi/mcu.html 什么是mcu_mcu是什么意思 标签:MCU(471)单片机(3098)微控制器(503) MCU ...
- 【题解】NOIP2018 旅行
题目戳我 \(\text{Solution:}\) 首先题目描述有一点不准确:回头是必须要走完一条路无路可走的时候才能返回. 对于树的情况:显然贪心做就完事了. 对于基环树的情况:对于一个\(n\)条 ...
- ORA-00017: session requested to set trace event 请求会话以设置跟踪事件
ORA-00017: session requested to set trace event ORA-00017: 请求会话以设置跟踪事件 Cause: The current se ...
- CSGO 服务端扩展插件开发记录之"DropClientReason"(1)
最近开始接触到了CSGO这款游戏,还是老套路,就是想千方百计的从里面增添新的游戏功能,当然刚开始想做到游刃有余是有点困难, 跟之前做CS1.6的第三方开发一样,都得自己慢慢的摸索过来,纵然CSGO所使 ...
- 多测师讲解自动化测试 _RF连接数据库_高级讲师肖sir
RF连接数据库:1.Connect To Database(连接数据库)2.Table Must Exist(表必须存在)3.Check If Exists In Database(查询某条件是否存在 ...
- 多测师讲解rf--定位元素--高级讲师肖sir
注意点: 注意点: rfbug:rf 点击勾选一个运行就运行两个出现用例执行 注释快捷键: 改字体大小: 快捷键:显示关键字信息 (ctrl+鼠标悬浮) 注解不能空格在注解 未保存提示 定位方法 : ...
- JVM 第六篇:极致优化 IDEA 启动速度
本文内容过于硬核,建议有 Java 相关经验人士阅读. 1. 引言 相信做 Java 开发的同学,对 IDEA 这个工具应该都不陌生,即使不使用 IDEA 做开发,那么对 Eclipse 这个工具应该 ...