简介

过去,如果在业务中需要处理任务调度的时候,大家都会使用第三方的任务调度组件,而第三方组件有一套自己的规则,在微服务的中显得那么格格不入,这样就会造成代码臃肿,耦合性高,如果有分布式还需要搭建新的分布式环境,如果把任务调度做成组件服务,这个就完全满足了微服务的模块化,组件化,而下面谈的是在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. JS 模块化- 05 ES Module & 4 大规范总结

    1 ES Module 规范 ES Module 是目前使用较多的模块化规范,在 Vue.React 中大量使用,大家应该非常熟悉.TypeScript 中的模块化与 ES 类似. 1.1 导出模块 ...

  2. 【linux】 第1回 linux运维基础

    目录 1. 运维的本质 2. 电脑与服务器 2.1 电脑的种类 2.2 服务器种类 2.3 服务器品牌 2.4 服务器尺寸 2.5 服务器内部组成 3. 磁盘阵列 4. 系统简介 5. 虚拟化 6. ...

  3. Windows应急响应——敬请期待!

    检查内容 进程.服务.用户.网络连接.漏洞补丁.木马查杀. 工具 火绒剑. 防护措施 杀毒软件

  4. 分享个好东西两行前端代码搞定bilibili链接转视频!

    只需要在您的要解析B站视频的页面的</body>前面加上下面两行代码即可,脚本会在客户端浏览器里解析container所匹配到的容器里的B站超链接 (如果不是外围有a标签的超链接只是纯粹的 ...

  5. Hugging Face发布diffuser模型AI绘画库初尝鲜!

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

  6. pta第一次博客

    目录 pta第一次博客 1.前言 2.设计与分析 第二次作业第二题 第三次作业第一题 第三次作业第二题 第三次作业第三题 3.踩坑心得: 4.改进建议 5.总结 pta第一次博客 1.前言 这三次pt ...

  7. 题解 SP24 FCTRL2 - Small factorials

    双倍经验. 题意 给\(t\) 组数据,求每组数据中\(n\) 的阶乘. 思路 \(n≤100\) . \(100!\) 肯定爆int128,所以高精呗. 那么就是一个阶乘的板子了,应该不难的吧. 具 ...

  8. 谷歌拼音输入法扩展API开发指南

    为了帮助开发者在谷歌拼音输入法的基本输入功能基础上,开发和定义更丰富的扩展输入功能,谷歌拼音输入法提供了以Lua脚本编程语言为基础的输入法扩展API.利用输入法扩展API,开发者可以编写自定义的输入功 ...

  9. 将vue+nodejs项目部署到服务器上(完整版)

    1.后端使用express生成器 1.1.后台node项目部署 在node项目里安装cors依赖(跨域)npm install cors --save,在app.js文件中使用var cors = r ...

  10. 2022-11-05 Acwing每日一题

    本系列所有题目均为Acwing课的内容,发表博客既是为了学习总结,加深自己的印象,同时也是为了以后回过头来看时,不会感叹虚度光阴罢了,因此如果出现错误,欢迎大家能够指出错误,我会认真改正的.同时也希望 ...