简介

过去,如果在业务中需要处理任务调度的时候,大家都会使用第三方的任务调度组件,而第三方组件有一套自己的规则,在微服务的中显得那么格格不入,这样就会造成代码臃肿,耦合性高,如果有分布式还需要搭建新的分布式环境,如果把任务调度做成组件服务,这个就完全满足了微服务的模块化,组件化,而下面谈的是在surging 中如何支持规则引擎自定义脚本。

调度频率设置

首先在开始之前,先看看如何通过脚本分配多种调度计划,先看下表:

方法 描述
EveryMinute() 每分钟执行一次任务
EveryFiveMinutes(); 每五分钟执行一次任务
EveryTenMinutes();  每十分钟执行一次任务
EveryThirtyMinutes() 每半小时执行一次任务
Hourly(); 每小时执行一次任务
HourlyAt(10) 每一个小时的第 10 分钟运行一次
Daily() 每到午夜执行一次任务
DailyAt("3:00") 在 3:00 执行一次任务
TwiceDaily(1, 3) 在 1:00 和 3:00 分别执行一次任务
Weekly() 每周执行一次任务
Monthly() 每月执行一次任务
MonthlyOn(4, "3:00") 在每个月的第四天的 3:00 执行一次任务
Quarterly() 每季度执行一次任务
Yearly() 每年执行一次任务
Timezone("utc") 设置utc时区

举个例子,在工作日每三秒在时间8:00-23:30内执行任务。脚本如下:

parser.TimeZone(""utc"")
.Weekdays()
.SecondAt(3)
.Between(""8:00"", ""23:30"")

额外的限制条件列表如下:

方法 描述
Weekdays() 限制任务在工作日
Sundays() 限制任务在星期日
Mondays() 限制任务在星期一
Tuesdays() 限制任务在星期二
Wednesdays() 限制任务在星期三
Thursdays() 限制任务在星期四
Fridays() 限制任务在星期五
Saturdays() 限制任务在星期六
When( function(lastExecTime)) 限制任务基于一个script脚本返回为真的验证
Skip( function(lastExecTime)) 限制任务基于一个script脚本返回为假的验证

举个例子,在工作日每三秒在时间8:00-23:30内执行任务。如果设置When返回为true,skip返回false 就会执行,脚本如下:

parser.TimeZone(""utc"")
.When(function(lastExecTime){
return true;
})
.Skip(
function(lastExecTime){
return false;
})
.Weekdays()
.SecondAt(3)
.Between(""8:00"", ""23:30"")

然后在function 脚本中支持DateUtils对象,可以针对lastExecTime进行逻辑判断,比如是否是周末:DateUtils.IsWeekend(lastExecTime), 是否是今天DateUtils.IsToday(lastExecTime),代码如下:

parser.TimeZone(""utc"")
.When(function(lastExecTime){
return DateUtils.IsToday(lastExecTime);
})
.Skip(
function(lastExecTime){
return DateUtils.IsWeekend(lastExecTime);
})
.Weekdays()
.SecondAt(3)
.Between(""8:00"", ""23:30"")

编写调度服务

surging微服务引擎是支持后台管理托管服务的,如果要基于BackgroundService编写任务调度,那服务就要继承BackgroundServiceBehavior,还要继承ISingleInstance以设置注入单例模式,

首先,创建接口服务,这样就可以远程添加任务,开启关闭服务了,代码如下:

   [ServiceBundle("Background/{Service}")]
public interface IWorkService : IServiceKey
{
Task<bool> AddWork(Message message); Task StartAsync(); Task StopAsync();
}

然后创建业务领域服务,以下代码是通过规则引擎自定义脚本设置执行频率,并且可以设置execsize 以标识同时执行任务的大小,通过以下业务逻辑代码大家可以扩展支持持久化。

public class WorkService : BackgroundServiceBehavior, IWorkService, ISingleInstance
{
private readonly ILogger<WorkService> _logger;
private readonly Queue<Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>> _queue = new Queue<Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>>();
private readonly ConcurrentDictionary<string, DateTime> _keyValuePairs = new ConcurrentDictionary<string, DateTime>();
private readonly IServiceProxyProvider _serviceProxyProvider;
private AtomicLong _atomic=new AtomicLong(1);
private const int EXECSIZE = 1;
private CancellationToken _token; public WorkService(ILogger<WorkService> logger, IServiceProxyProvider serviceProxyProvider)
{
_logger = logger;
_serviceProxyProvider = serviceProxyProvider;
/* var script = @"parser
.Weekdays().SecondAt(3).Between(""8:00"", ""22:00"")";*/
var script = @"parser
.TimeZone(""utc"")
.When(
function(lastExecTime){
return DateUtils.IsToday(lastExecTime);
}).Skip(
function(lastExecTime){
return DateUtils.IsWeekend(lastExecTime);
}).Weekdays().SecondAt(3).Between(""8:00"", ""23:30"")";
var ruleWorkflow = GetSchedulerRuleWorkflow(script);
var messageId = Guid.NewGuid().ToString();
_keyValuePairs.AddOrUpdate(messageId, DateTime.Now, (key, value) => DateTime.Now);
_queue.Enqueue(new Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>(new Message() { MessageId= messageId,Config=new SchedulerConfig() { IsPersistence=true} }, GetRuleEngine(ruleWorkflow), ruleWorkflow)); } public Task<bool> AddWork(Message message)
{
var ruleWorkflow = GetSchedulerRuleWorkflow(message.Config.Script);
_keyValuePairs.AddOrUpdate(message.MessageId, DateTime.Now, (key, value) => DateTime.Now);
_queue.Enqueue(new Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>(message, GetRuleEngine(ruleWorkflow), ruleWorkflow));
return Task.FromResult(true);
} protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
_token = stoppingToken;
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
_queue.TryDequeue(out Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>? message);
if (message != null)
{
var parser = await GetParser(message.Item3, message.Item2);
await PayloadSubscribe(parser, message.Item1, message.Item2, message.Item3);
_keyValuePairs.TryGetValue(message.Item1.MessageId, out DateTime dateTime);
parser.Build(dateTime == DateTime.MinValue ? DateTime.Now : dateTime);
}
if (!_token.IsCancellationRequested && (message == null || _atomic.GetAndAdd(1) == EXECSIZE))
{
_atomic = new AtomicLong(1);
await Task.Delay(1000, stoppingToken); }
}
catch (Exception ex){
_logger.LogError("WorkService execute error, message:{message} ,trace info:{trace} ", ex.Message, ex.StackTrace);
}
} public async Task StartAsync()
{
if (_token.IsCancellationRequested)
{
await base.StartAsync(_token);
}
} public async Task StopAsync()
{
if (!_token.IsCancellationRequested)
{
await base.StopAsync(_token);
}
} private async Task PayloadSubscribe(RulePipePayloadParser parser, Message message, RulesEngine.RulesEngine rulesEngine, SchedulerRuleWorkflow ruleWorkflow)
{
parser.HandlePayload().Subscribe(async (temperature) =>
{
try
{
if (temperature)
{
await ExecuteByPlanAsyn(message);
_logger.LogInformation("Worker exec at: {time}", DateTimeOffset.Now); }
}
catch (Exception ex) { }
finally
{
if (message.Config.IsPersistence || (!temperature && !message.Config.IsPersistence))
_queue.Enqueue(new Tuple<Message, RulesEngine.RulesEngine, SchedulerRuleWorkflow>(message, rulesEngine, ruleWorkflow)); }
});
} private async Task<bool> ExecuteByPlanAsyn(Message message)
{
var result = false;
var isExec = true;
try
{
if (!string.IsNullOrEmpty(message.RoutePath))
{
var serviceResult = await _serviceProxyProvider.Invoke<object>(message.Parameters, message.RoutePath, message.ServiceKey);
bool.TryParse(serviceResult?.ToString(), out result);
isExec = true;
}
}
catch { }
finally
{
if (isExec && message.Config.IsPersistence)
_keyValuePairs.AddOrUpdate(message.MessageId, DateTime.Now, (key, value) => DateTime.Now);
else if (!message.Config.IsPersistence)
_keyValuePairs.TryRemove(message.MessageId, out DateTime dateTime);
}
return result;
} private async Task<RulePipePayloadParser> GetParser(SchedulerRuleWorkflow ruleWorkflow, RulesEngine.RulesEngine engine)
{
var payloadParser = new RulePipePayloadParser();
var ruleResult = await engine.ExecuteActionWorkflowAsync(ruleWorkflow.WorkflowName, ruleWorkflow.RuleName, new RuleParameter[] { new RuleParameter("parser", payloadParser) });
if (ruleResult.Exception != null && _logger.IsEnabled(LogLevel.Error))
_logger.LogError(ruleResult.Exception, ruleResult.Exception.Message);
return payloadParser;
} private RulesEngine.RulesEngine GetRuleEngine(SchedulerRuleWorkflow ruleWorkFlow)
{
var reSettingsWithCustomTypes = new ReSettings { CustomTypes = new Type[] { typeof(RulePipePayloadParser) } };
var result = new RulesEngine.RulesEngine(new Workflow[] { ruleWorkFlow.GetWorkflow() }, null, reSettingsWithCustomTypes);
return result;
} private SchedulerRuleWorkflow GetSchedulerRuleWorkflow(string script)
{
var result = new SchedulerRuleWorkflow("1==1");
if (!string.IsNullOrEmpty(script))
{
result = new SchedulerRuleWorkflow(script);
}
return result;
}
}

总结

因为工作繁忙,微服务平台暂时搁置,等公司基于surging 的物联网项目上线后,再投入时间研发,surging 一直开发中未曾放弃,也许你没看到的版本才是最强大的。之前的QQ群被封了,如果感兴趣可以加:744677125

开源地址:https://github.com/fanliang11/surging

通过surging的后台托管服务编写任务调度并支持规则引擎自定义脚本的更多相关文章

  1. DTCMS插件的制作实例电子资源管理(二)Admin后台页面编写

    总目录 插件目录结构(一) Admin后台页面编写(二) 前台模板页编写(三) URL重写(四) 本实例旨在以一个实际的项目中的例子来介绍如何在dtcms中制作插件,本系列文章非入门教程,部分逻辑实现 ...

  2. windows后台服务程序编写

    Windows后台服务程序编写 1. 为什么要编写后台服务程序 工作中有一个程序需要写成后台服务的形式,摸索了一下,跟大家分享. 在windows操作系统中后台进程被称为 service. 服务是一种 ...

  3. 验证码在后台的编写,并实现点击验证码图片时时发生更新 C# 项目发布到IIS后不能用log4net写日志

    验证码在后台的编写,并实现点击验证码图片时时发生更新   验证码在软件中的地位越来越重要,有效防止这种问题对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试:下面就是实现验证码的基本步骤: ...

  4. 编写自己的Acunetix WVS漏洞扫描脚本详细教程

    AWVS提供了自定义的脚本编程接口,可是网上的资料很少,只有官方的几篇介绍和参考手册,最近研究了一下怎么编写AWVS的漏洞脚本来写一篇简单性的文章 本文以8.0为例,首先呢安装好Acunetix We ...

  5. 编写自己的Nmap(NSE)脚本

    编写自己的Nmap脚本 一.介绍 在上一篇文章Nmap脚本引擎原理中我们介绍了基本的NSE知识,这篇文章介绍如何基于Nmap框架编写简单的NSE脚本文件,下一篇文章,Nmap脚本文件分析(AMQP协议 ...

  6. 怎么样通过编写Python小程序来统计测试脚本的关键字

    怎么样通过编写Python小程序来统计测试脚本的关键字 通常自动化测试项目到了一定的程序,编写的测试代码自然就会很多,如果很早已经编写的测试脚本现在某些基础函数.业务函数需要修改,那么势必要找出那些引 ...

  7. iOS 后台持续定位详解(支持ISO9.0以上)

    iOS 后台持续定位详解(支持ISO9.0以上) #import <CoreLocation/CoreLocation.h>并实现CLLocationManagerDelegate 代理, ...

  8. 如何编写snort的检测规则

    如何编写snort的检测规则 2013年09月08日 ⁄ 综合 ⁄ 共 16976字 前言 snort是一个强大的轻量级的网络入侵检测系统.它具有实时数据流量分析和日志IP网络数据包的能力,能够进行协 ...

  9. SSM_CRUD新手练习(6)分页后台控制器编写

    经过测试基础环境已经搭建好了,现在我们开始编写CRUD. 我们来看一下查询的逻辑该怎么写: 1.访问index.jsp页面 2.index.jsp页面发送查询员工的请求 3.EmployeeContr ...

  10. 2015元旦第一弹——WP8.1应用程序栏(C#后台代码编写)

    //第一次写博文,以后还请各位道友互相关照哈.废话不多说,直接进入正题. 相信大家对于如何在XAML添加应用程序栏应该很清楚,不清楚的话,可以打开新建个Pviot应用 就有系统自带的菜单栏. 本文主要 ...

随机推荐

  1. Linux命令系列之ls——原来最简单的ls这么复杂

    Linux命令系列之ls--原来最简单的ls这么复杂 ls命令应该是我们最常用的命令之一了,但是我们可能容易忽略那些看起来最简单的东西. 简介 ls命令的使用格式如下 ls [选项] [具体的文件] ...

  2. BZOJ3732 Network(Kruskal重构树)

    Kruskal重构树的模板题. 给你N个点的无向图 (1 <= N <= 15,000),记为:1-N.图中有M条边 (1 <= M <= 30,000) ,第j条边的长度为: ...

  3. 洛谷P1640 SCOI2010 连续攻击游戏 (并查集/匹配)

    本题介绍两种做法: 1 并查集 1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N=1000005; 4 int ...

  4. 计算机保研,maybe this is all you need(普通双非学子上岸浙大工程师数据科学项目)

    写在前面 9.28接收了拟录取通知,也终究是尘埃落定了,我人生的又一个阶段也终于结束.面对最终录取结果,或多或少会有所遗憾,但也还是基本达到了预期的目标了. 作为在今年严峻的保研形势下幸存的我,一直想 ...

  5. SQL优化步骤

    当生产数据量急剧增长后,很多SQL语句可能会开始暴露出性能问题.当面对一个有SQL性能问题的数据库时,应该从何处入手进行系统的分析,使得能够尽快定位到问题SQL处并尽快解决问题? 第一步:查看SQL执 ...

  6. C语言爱心表白程序

    #include <stdio.h> #include <math.h> #include <windows.h> #include <tchar.h> ...

  7. 24.-Django生成csv文件及下载

    一.csv文件定义 逗号分隔值(comma-separated values, csv,有时页称字符分隔值,因为分分隔字符页可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本) 说明:可被常见 ...

  8. 十九、Service Ingress

    Service Ingress Ingress-Nginx github 地址:https://github.com/kubernetes/ingress-nginx Ingress-Nginx 官方 ...

  9. JavaSPI详解

    目录 一个问题 什么是SPI API 与 SPI 一个简单的例子 SPI机制的实现 Java SPI的问题 为什么SPI机制打破了双亲委派模型 参考资料 一个问题 在项目开发中,经常会使用到数据库驱动 ...

  10. TensorFlow?PyTorch?Paddle?AI工具库生态之争:ONNX将一统天下

    作者:韩信子@ShowMeAI 深度学习实战系列:https://www.showmeai.tech/tutorials/42 本文地址:https://www.showmeai.tech/artic ...