使用Quartz.net实现多线程任务定时执行,动态配置Job,结合Topshelf构建Windows服务

几个月前有这么个需求:需要执行一些Job,这些Job会各自按照不同的时间频次执行,且它们做的事情也不同,有的是监控站点,有的是监控服务器存储情况,有的是监控报表PROCEDURE的执行状况…

OK,当看到这么个需求,首先我想肯定是要用多线程来处理了,并且还要考虑到性能问题,领导当时给出一个方案,建议看一下Quartz.net,于是,研究了几天,看了不少资料,总算把监控引擎做出了一个版本,今天有时间,来整理一下大致过程,记录一下代码。

整个监控引擎,简单来说,一是使用Quartz.Net实现多线程执行任务,二是使用Topshelf构建Windows Server

一:Quartz.Net,官方:https://www.quartz-scheduler.net/

二:TopShelf,请自行度娘吧

工具:VS2019

首先创建一个控制台应用程序

然后NuGet引用Topshelf.dll程序集

添加控制台代码如下:

class Program
	{
		static void Main(string[] args)
		{
			var rc = HostFactory.Run(x =>
			{
				x.Service<TownCrier>(s =>
				{
					s.ConstructUsing(name => new TownCrier());
					s.WhenStarted(tc => tc.Start());
					s.WhenStopped(tc => tc.Stop());
				});
				x.RunAsLocalSystem();

				x.SetDescription("服務说明");
				x.SetDisplayName("DisplayName自定义");
				x.SetServiceName("ServiceName自定义");
			});

			var exitCode = (int)Convert.ChangeType(rc, rc.GetTypeCode());
			Environment.ExitCode = exitCode;
		}
	}
	public class TownCrier
	{
		public void Start()
		{
			//开始执行
		}
		public void Stop()
		{
			//停止执行
		}
	}

以上是程序的入口,接下来才真正涉及到如何开展多线程任务,如何使用Quartz.net

先创建一个类库QuartzBLL,并安装Quartz引用:

这里直接提供工具类QuartzManage,看了很多网友的例子,但都没调通,然后自己摸索着整理出来一个工具类,或有不足之处,请指出,多谢!

	public class QuartzManage
	    {
	        static Task<IScheduler> task_scheduler = StdSchedulerFactory.GetDefaultScheduler();
	        static IScheduler scheduler;
	        private static readonly object objlock = new object();
	        static QuartzManage()
	        {
	            if (scheduler == null)
	            {
	                lock (objlock)
	                {
	                    if (scheduler == null)
	                    {
	                        scheduler = task_scheduler.Result;
	                        scheduler.Start();
	                    }
	                }
	            }
	        }
	
	        /// <summary>
	        /// 以Simple开始一个工作
	        /// </summary>
	        /// <typeparam name="T"></typeparam>
	        /// <param name="name"></param>
	        /// <param name="simpleInterval"></param>
	        public static void StartJobWithSimple<T>(string name, Action<SimpleScheduleBuilder> simpleInterval) where T : IJob
	        {
	            IJobDetail job = JobBuilder.Create<T>().WithIdentity(name + "_job", name + "_group").Build();
	            ITrigger Simple = TriggerBuilder.Create().StartNow()
	                               .WithSimpleSchedule(simpleInterval)
	                               .Build() as ISimpleTrigger;
	
	            scheduler.ScheduleJob(job, Simple);
	        }
	
	        /// <summary>
	        /// 以Cron开始一个工作
	        /// </summary>
	        /// <typeparam name="T"></typeparam>
	        /// <param name="name"></param>
	        /// <param name="CronExpression"></param>
	        public static void StartJobWithCron<T>(string name, string CronExpression) where T : IJob
	        {
	
	            IJobDetail job = JobBuilder.Create<T>().WithIdentity(name + "_job", name + "_group").Build();
	
	            ITrigger CronTrigger = TriggerBuilder.Create().StartNow().WithIdentity(name + "_trigger", name + "_group")
	                                    .WithCronSchedule(CronExpression, w => w.WithMisfireHandlingInstructionDoNothing())
	                                    .Build() as ICronTrigger;
	
	            scheduler.ScheduleJob(job, CronTrigger);
	        }
	
	        /// <summary>
	        /// 這種辦法可以根據job名稱找到觸發器,也可以找到Job,這樣就可以在任何地方修改Job頻次,不再限於IJob的實現方法Execute內
	        /// 以此實現了:即時修改執行頻次即時生效
	        /// </summary>
	        /// <param name="cronExpression"></param>
	        /// <param name="name"></param>
	        public static async void ModifyJob(string cronExpression, string name)
	        {
	            //触发器的key
	            TriggerKey triggerKey = new TriggerKey(name + "_trigger", name + "_group");
	            ITrigger trigger = await scheduler.GetTrigger(triggerKey);
	
	            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.CronSchedule(cronExpression);
	            trigger = trigger.GetTriggerBuilder().WithIdentity(triggerKey).WithSchedule(scheduleBuilder).Build();
	
	            await scheduler.RescheduleJob(triggerKey, trigger);
	        }
	
	        /// <summary>
	        /// 删除Job,即時刪除,不再限於IJob的實現方法Execute內
	        /// </summary>
	        /// <param name="job"></param>
	        /// <param name="trigger"></param>
	        public static void DeleteJob(string name)
	        {
	            //触发器的key
	            TriggerKey triggerKey = new TriggerKey(name + "_trigger", name + "_group");
	            //Job的Key
	            JobKey jobKey = new JobKey(name + "_job", name + "_group");
	            scheduler.PauseTrigger(triggerKey);//暫停觸發器
	            scheduler.UnscheduleJob(triggerKey);//移除觸發器
	            scheduler.DeleteJob(jobKey);
	        }
	
	        /// <summary>
	        /// 删除Job
	        /// </summary>
	        /// <param name="job"></param>
	        /// <param name="trigger"></param>
	        public static void DeleteJob(IJobDetail job, ITrigger trigger)
	        {
	            scheduler.PauseTrigger(trigger.Key);//暫停觸發器
	            scheduler.UnscheduleJob(trigger.Key);//移除觸發器
	            scheduler.DeleteJob(job.Key);
	        }
	
	        /// <summary>
	        /// 停止運行
	        /// </summary>
	        public static void ShutDownJob()
	        {
	            if (scheduler != null && !scheduler.IsShutdown)
	            {
	                scheduler.Shutdown();
	            }
	        }
    }

还有一种方式,是可以自定义调度器的属性:比如线程池数量threadCount…默认的数量好像是10吧,记不清楚了,也有人把配置文件放在xml文件中,程序里读取配置文件来创建调度器。具体怎么玩,视情况而定吧。

	public class QuartzHelper
	    {
	        private static IScheduler scheduler = null;
	        static QuartzHelper()
	        {
	            var properties = new NameValueCollection();
	            // 设置线程池
	            properties["quartz.threadPool.type"] = "Quartz.Simpl.SimpleThreadPool, Quartz";
	            //设置线程池的最大並發线程数量
	            properties["quartz.threadPool.threadCount"] = "5";
	            //设置作业中每个线程的优先级
	            properties["quartz.threadPool.threadPriority"] = ThreadPriority.Normal.ToString();
	
	             远程输出配置
	            //properties["quartz.scheduler.exporter.type"] = "Quartz.Simpl.RemotingSchedulerExporter, Quartz";
	            //properties["quartz.scheduler.exporter.port"] = "555";  //配置端口号
	            //properties["quartz.scheduler.exporter.bindName"] = "QuartzScheduler";
	            //properties["quartz.scheduler.exporter.channelType"] = "tcp"; //协议类型
	            if (scheduler == null)
	            {
	                //创建一个工厂
	                var schedulerFactory = new StdSchedulerFactory(properties);
	                //启动
	                scheduler = schedulerFactory.GetScheduler().Result;
	                //1、开启调度
	                scheduler.Start();
	            }
	        }
	}

  添加测试类

拿TestJob1举例,旧版的Quartz好像在实现IJob接口的时候,还不是异步方法,最新版本是异步,注意async await:

class TestJob1 : IJob
	    {
	        public async Task Execute(IJobExecutionContext context)
	        {
	            await Task.Run(() =>
	            {
	                Console.WriteLine($"第一个Job执行了,Time:{DateTime.Now}");            });
	        }
	    }
	
	 class TestJob2 : IJob
	    {
	        public async Task Execute(IJobExecutionContext context)
	        {
	            await Task.Run(() =>
	            {
	                Console.WriteLine($"第二个Job执行了,Time:{DateTime.Now}");            });
	        }
	    }

然后定义一个JobStart类,作为程序业务入口类,将TestJob1和TestJob2注册在Quartz中,调度器会按照各自的频次(CronExpression)执行Job,可以这么理解:一个Job对应一个线程,如果所有Job的CronExpression一样,Job就会在同一时间同时执行,此时如果线程池中线程数量不够同时处理这些Job,则会排序等待,就像消息队列一样(线程池线程数量可以自定义)

	public class JobStart
	    {
	        public void Start()
	        {
	            QuartzManage.StartJobWithCron<TestJob1>("TestJob1", "0/5 * * * * ?");//5秒钟一次
	            QuartzManage.StartJobWithCron<TestJob2>("TestJob2", "0/8 * * * * ?");//8秒钟一次
	            Console.ReadLine();
	        }
	    }

最后,在Program,TownCrier的Start方法中启动程序。

看一下效果

OK,继续吧。

此处仅示例展示,如果是配置在数据库中的Job,需要动态加载?

我的做法是建一个工厂类JobFactory,來根據不同的任务设定的Type,來返回不同的TestJob类(如果Job类执行的任务大致相同,比如:你的系统中有很多执行计划的任务,其实就是不同的sql或者procedure,那么不需要建很多的TestJob类,只需要一个类即可)。

然後自定义一个接口(IJobMonitor),声明两个方法,StartJob和DeleteJob。

将TestJob类实现该接口IJobMonitor。

也可以单独建立一个类库文件,通过反射创建Job,当然要视情况而定,需不需要用反射?

		/// <summary>
	        /// 通過反射加載
	        /// </summary>
	        /// <param name="category"></param>
	        public static void CreatJob(Config_DataVerifyCategory category)
	        {
	            //加载程序及路径
	            Assembly ass = Assembly.LoadFrom(@"D:\xx\TestJobAssembly.dll");
	            if (ass != null)
	            {
	                Type[] types = ass.GetExportedTypes();
	                foreach (Type t in types)
	                {
	                    if (typeof(IJob).IsAssignableFrom(t))
	                    {
	                        IJobDetail jobDetail = JobBuilder.Create(t).WithIdentity(category.Code + "_CaseHandingJob", category.Code + "_CaseHandingJobGroup").Build();
	                        jobDetail.JobDataMap.Put("cronExpression", category.CronExpression);
	                        jobDetail.JobDataMap.Put("categoryCode", category.Code);
	                        //新建一个触发器
	                        ITrigger trigger = TriggerBuilder.Create()
	                            .StartNow()
	                            .WithCronSchedule(category.CronExpression).Build();
	                        //任务和触发器关联放入调度器
	                        scheduler.ScheduleJob(jobDetail, trigger);
	                        scheduler.Start();
	                        break;
	                    }
	                }
	            }
	        }

其实,我实际的项目要复杂很多,业务在不断的变化着,代码也改了很多次,深层的讲解我就不做了(主要我研究的也不深入),我想各位应该看的明白,截图看下吧

目前我的项目中,已经扩展到两部分,一部分是监控引擎,一部分是数据监控,所谓数据监控,是很多很多的方案,每个方案对应几个Task,每个task就是sql或者mdx等查询数据的脚本,还好它们可以用一个Job类实现IJob去处理,而监控引擎则比较复杂,每个Job都做着各自不同的事情,所以,我为每个Job创建Job类,让它们职责单一,如果以后扩展,就新建一个Job类,然后改一下工厂类即可,这样也符合开闭原则。

我给程序设置半小时一次的定时器,查询数据库是否有新增或禁用、修改的Job,每次和静态缓存的对象对比,以实现增删改Job之后,动态Update新的Job,该新增就新增,该禁用就禁用,如果大家有更好的思路,请一定要分享给我喔,谢谢!

再展示一下上端代码:

public class MonitorStartEngine
    {
        private static List<Config_Frequency> cacheList;

        public void Start()
        {
            LogHelper.Info("----------------監控引擎windows服務定時執行----------------");
            try
            {
                //在此查詢配置項
                List<Config_Frequency> queryList = MonitorFrequencyDAL.GetAllFrequencyList();
                if (queryList != null)
                {
                    queryList.ForEach(action =>
                    {
                        Config_Frequency msf = GetConfig_Frequency(action.MonitorCategory);
                        IJobMonitor monitor = MonitorJobFactory.GetMonitor(action.MonitorCategory);
                        if (monitor != null) monitor.StartJob(msf, action);
                    });
                }

                if (cacheList != null)
                {
                    //刪除已禁用的Job
                    var deletedJobs = ListCompare(cacheList, queryList);
                    deletedJobs.ForEach(action =>
                    {
                        Config_Frequency msf = GetConfig_Frequency(action.MonitorCategory);
                        IJobMonitor monitor = MonitorJobFactory.GetMonitor(action.MonitorCategory);
                        if (monitor != null) monitor.DeleteJob();
                    });
                }

                cacheList = queryList;
            }
            catch (Exception ex)
            {
                LogHelper.Error("監控引擎異常信息: " + ex.Message);
                QuartzManage.ShutDownJob();
            }
        }

        private Config_Frequency GetConfig_Frequency(string monitorCatogory)
        {
            if (cacheList != null)
                return cacheList.Where(c => c.MonitorCategory == monitorCatogory).FirstOrDefault();
            else
                return null;
        }

        //取出存在cacheList中但不存在queryList中的数据,差异数据放入deletedList
        private List<Config_Frequency> ListCompare(List<Config_Frequency> cacheList, List<Config_Frequency> queryList)
        {
            List<Config_Frequency> deletedList = new List<Config_Frequency>();
            if (queryList == null)
                deletedList = cacheList;
            else
            {
                foreach (Config_Frequency cf in cacheList)
                {
                    var mfs = queryList.Where(c => c.MonitorCategory == cf.MonitorCategory).FirstOrDefault();
                    if (mfs == null)
                    {
                        deletedList.Add(cf);
                    }
                }
            }
            return deletedList;
        }
    }

最后说一下,如何创建Windows Server:
控制台应用程序发布后,使用管理員身份打開Dos命令:
找到程序exe文件所在目录,比如我本机测试发布后的目录在D:\zPublish\Job
安裝:服務.exe install
卸载:服務.exe uninstall
如圖所示:

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
首先,需要在pom.xml中引入以下依赖: ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>${quartz.version}</version> </dependency> ``` 其中,`${spring.version}`和`${quartz.version}`是对应的版本号。 接着,在Spring配置文件中配置`SchedulerFactoryBean`和`JobDetailFactoryBean`: ```xml <bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <!--自动启动 --> <property name="autoStartup" value="true" /> <!--配置数据源 --> <property name="dataSource" ref="dataSource" /> <!--配置Quartz的属性 --> <property name="quartzProperties"> <props> <prop key="org.quartz.scheduler.instanceName">Scheduler</prop> <prop key="org.quartz.scheduler.instanceId">AUTO</prop> <prop key="org.quartz.scheduler.skipUpdateCheck">true</prop> <prop key="org.quartz.scheduler.jmx.export">true</prop> <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop> <prop key="org.quartz.jobStore.driverDelegateClass">org.quartz.impl.jdbcjobstore.StdJDBCDelegate</prop> <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop> <prop key="org.quartz.jobStore.isClustered">false</prop> <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop> <prop key="org.quartz.threadPool.threadCount">10</prop> <prop key="org.quartz.threadPool.threadPriority">5</prop> </props> </property> <!--配置JobDetail --> <property name="jobDetails"> <list> <bean id="jobDetailFactoryBean" class="org.springframework.scheduling.quartz.JobDetailFactoryBean"> <!--要执行Job类 --> <property name="jobClass" value="com.example.job.MyJob" /> <!--Job的名称 --> <property name="name" value="MyJob" /> <!--Job的分组 --> <property name="group" value="MyJobGroup" /> <!--是否持久化 --> <property name="durability" value="true" /> <!--是否可以被其他任务替代 --> <property name="requestsRecovery" value="true" /> </bean> </list> </property> <!--配置Trigger --> <property name="triggers"> <list> <bean id="cronTriggerFactoryBean" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <!--绑定JobDetail --> <property name="jobDetail" ref="jobDetailFactoryBean" /> <!--触发器的名称 --> <property name="name" value="MyTrigger" /> <!--触发器的分组 --> <property name="group" value="MyTriggerGroup" /> <!--Cron表达式 --> <property name="cronExpression" value="0/10 * * * * ?" /> </bean> </list> </property> </bean> ``` 其中,`SchedulerFactoryBean`是Quartz的核心类,用于创建和管理Quartz的`Scheduler`对象。`JobDetailFactoryBean`用于创建`JobDetail`对象,`CronTriggerFactoryBean`用于创建`Trigger`对象。 在上述配置中,我们配置了一个名为`MyJob`的Job,它由`com.example.job.MyJob`类实现执行逻辑在该类的`executeInternal()`方法中。我们还配置了一个名为`MyTrigger`的Trigger,它使用Cron表达式`0/10 * * * * ?`,表示每隔10秒执行一次。 最后,在`com.example.job.MyJob`类中实现我们的业务逻辑: ```java public class MyJob extends QuartzJobBean { @Override protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException { // 执行业务逻辑 } } ``` 当配置完成后,启动Spring应用程序,Quartz就会按照我们的配置执行定时任务了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Boogaloo-Jer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值