Sundial (二)
相关重要的组件一览
Triggers(触发器)相关类
- 保存触发器相关参数,例如起止时间,次数,间隔时间等,其中Sundial支持多种类型触发器
- 多种类型的触发器必须重写GetNextOccurrence方法,用于返回下一个触发时间
CronTrigger
引用了第三方包TimeCrontab 3.2.1
构造函数
//支持三种方式的触发器构建
public CronTrigger(String schedule, object args){
// 处理 int 转 CronStringFormat
/** int 从0开始
public enum CronStringFormat
{
Default,
WithYears,
WithSeconds,
WithSecondsAndYears
}
**/
if (args is int formatValue)
{
// 强转int变为CronStringFormat
Crontab = Crontab.Parse(schedule, (CronStringFormat)formatValue);
}
// 处理 CronStringFormat
else if (args is CronStringFormat format)
{
Crontab = Crontab.Parse(schedule, format);
}
// 处理 Macro At
// fields 为数组形式的cron,可用string.Join(",", fields.Select((object f) => f.ToString()).ToArray());解析
/**
schedule一般为:
case "@secondly":
return SecondlyAt(fields);
case "@minutely":
return MinutelyAt(fields);
case "@hourly":
return HourlyAt(fields);
case "@daily":
return DailyAt(fields);
case "@monthly":
return MonthlyAt(fields);
case "@weekly":
return WeeklyAt(fields);
case "@yearly":
return YearlyAt(fields);
**/
else if (args is object[] fields)
{
Crontab = Crontab.ParseAt(schedule, fields);
}
else throw new NotImplementedException();
}
GetNextOccurrence
- 直接使用了Crontab.GetNextOccurrence(startAt)获得下次触发时间
PeriodTrigger
- 构造函数
- 时间单位为毫秒,最小计数为100ms
- GetNextOccurrence
- 每次只需要在当前时间加上设置的毫秒数即可
- startAt.AddMilliseconds(Interval)
Trigger
Trigger主要分为了属性类和方法类两个文件(Trigger.cs和Trigger.Methods.cs)
属性类中主要包含了Trigger的相关属性信息,例如触发时间,次数等基础信息
方法类内容如下
/**
虚函数,每个继承Trigger的类都要实现该方法
**/
// 下一个触发的时间
public virtual DateTime GetNextOccurrence(DateTime startAt) => throw new NotImplementedException();
// 执行检查条件
public virtual bool ShouldRun(JobDetail jobDetail, DateTime startAt)
{
// 下次运行时间不能晚于当前时间且最近执行的时间不能是下次执行事件
return NextRunTime.Value <= startAt
&& LastRunTime != NextRunTime;
}
/// <summary>
/// 记录运行信息和计算下一个触发时间
/// </summary>
/// <param name="jobDetail">作业信息</param>
/// <param name="startAt">当前时间</param>
internal void Increment(JobDetail jobDetail, DateTime startAt)
{
// 阻塞状态并没有实际执行,此时忽略次数递增和最近运行时间赋值
if (Status != TriggerStatus.Blocked)
{
//触发次数再加一
NumberOfRuns++;
// 最近一次的执行时间为下次执行事件
LastRunTime = NextRunTime;
}
// 根据当前时间判断下次执行时间
NextRunTime = GetNextRunTime(startAt); // 检查下一次执行信息
CheckAndFixNextOccurrence(jobDetail);
}
// 计算下一次运行时间
// internal DateTime? GetNextRunTime(DateTime startAt)
// 相关数据转换为sql的方法 ConvertToSQL
Triggers 静态类
主要用于创建TriggerBuilder(作业触发器构建器)
创建TriggerBuilder
创建时间间隔的触发器:Create(interval)
创建Cron的触发器: Create(schedule, CronStringFormat.Default)
// 创建TriggerBuilder
public static TriggerBuilder Create<TTrigger>(params object[] args)
where TTrigger : Trigger
{
return Create<TTrigger>().SetArgs(args);
}
// 最底层的构造参数
public static TriggerBuilder Create(Type triggerType)
{
// TriggerBuilder方法的相关信息参见TriggerBuilder类说明
return new TriggerBuilder()
.SetTriggerType(triggerType)
.Appended();
}
// 设置作业触发器参数
public TriggerBuilder SetArgs(params object[] args)
{
Args = args == null || args.Length == 0
? null
: Penetrates.Serialize(args);
RuntimeTriggerArgs = args;
// 返回当前的TriggerBuilder
return this;
}
TriggerBuilder与Trigger,JSon的相互转换
- 参见TriggerBuilder类的From方法
TriggerBuilder
触发器构建器
- PeriodTrigger
- CronTrigger
触发器转换器
From
//Trigger 转 TriggerBuilder
/**
1.通过 typeof(TriggerBuilder).GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) 创建实例
2.如果失败则用Activator.CreateInstance<TTarget>()创建
3.获得TriggerBuilder属性targetType.GetProperties(bindFlags)
4.获得trigger的相关属性和数值
5.赋值,各种命名方式的进行碰撞,有符和的就赋值
// 多种属性命名解析
var propertyName = property.Name;
var camelCasePropertyName = Penetrates.GetNaming(propertyName, NamingConventions.CamelCase);
var pascalPropertyName = Penetrates.GetNaming(propertyName, NamingConventions.Pascal);
var underScoreCasePropertyName = Penetrates.GetNaming(propertyName, NamingConventions.UnderScoreCase);
// 穷举方式获取值
object value;
if (sourcePropertyValues.ContainsKey(propertyName)) value = sourcePropertyValues[propertyName];
else if (sourcePropertyValues.ContainsKey(camelCasePropertyName)) value = sourcePropertyValues[camelCasePropertyName];
else if (sourcePropertyValues.ContainsKey(pascalPropertyName)) value = sourcePropertyValues[pascalPropertyName];
else if (sourcePropertyValues.ContainsKey(underScoreCasePropertyName)) value = sourcePropertyValues[underScoreCasePropertyName];
else continue; // 忽略空值控制
if (ignoreNullValue && value == null) continue; property.SetValue(target, value);
**/
var triggerBuilder = trigger.MapTo<TriggerBuilder>(); // 初始化运行时作业触发器类型和参数
triggerBuilder.SetTriggerType(triggerBuilder.AssemblyName,triggerBuilder.TriggerType).
SetArgs(triggerBuilder.Args);
// 持久化的型为变更为更新
return triggerBuilder.Updated();
Trigger的JSON转换为TriggerBuilder,多了一步Penetrates.Deserialize(json)---底层调用为:JsonSerializer.Deserialize(json, GetDefaultJsonSerializerOptions())
/// <summary>
/// 获取默认的序列化对象
/// </summary>
/// <returns><see cref="JsonSerializerOptions"/></returns>
internal static JsonSerializerOptions GetDefaultJsonSerializerOptions()
{
var jsonSerializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
ReadCommentHandling = JsonCommentHandling.Skip,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
AllowTrailingCommas = true
};
// 处理时间类型
jsonSerializerOptions.Converters.Add(new DateTimeJsonConverter());
return jsonSerializerOptions;
}
设置作业的基础信息
SetTriggerId 设置触发器id
SetDescription 设置描述信息
SetStatus 设置触发器状态
SetStartTime SetEndTime 设置起始和结束时间
设置作业触发器类型
// 不做 null 检查
if (triggerType == null) return this; // 检查 triggerType 类型是否派生自 Trigger
if (!typeof(Trigger).IsAssignableFrom(triggerType)
// 是否是Trigger类型
|| triggerType == typeof(Trigger)
// 是否是接口
|| triggerType.IsInterface
// 是否是抽象类
|| triggerType.IsAbstract) throw new InvalidOperationException("The <triggerType> is not a valid Trigger type."); // 最多只能包含一个构造函数
if (triggerType.GetConstructors().Length > 1) throw new InvalidOperationException("The <triggerType> can contain at most one constructor.");
//作业触发器类型所在程序集
AssemblyName = triggerType.Assembly.GetName().Name;
// 作业触发器类型
TriggerType = triggerType.FullName;
// 作业触发器运行时类型
RuntimeTriggerType = triggerType;
return this;
Scheduler(作业计划) 相关类
- IScheduler 作业计划接口
- Get方法---返回相关实例:SchedulerModel,SchedulerBuilder,JobBuilder,TriggerBuilder,JobDetail等
- TryGet方法--尝试查找相关实例:ScheduleResult枚举(不存在,已存在,成功,失败,未找到)
- 对作业触发器的操作:Add,Update , Remove
- 对作业的操作:Persist,Start,Pause,Collate,Reload
- Scheduler 作业计划
- 作业id,作业组名称
- JobDetail,IJob,ISchedulerFactory,IScheduleLogger,ILogger
- Scheduler.Methods 作业计划的方法
- IScheduler 方法的实现
- SchedulerModel 作业计划模型
Job(作业信息)相关类
IJob 作业处理程序
- 自身业务继承的接口
- 具体处理逻辑在ExecuteAsync方法写出
JobDetail 作业类
- 作业的属性
JobBuilder 作业信息构建器(继承JobDetail)
创建JobBuilder
AssemblyName = assemblyName;
JobType = jobTypeFullName; // 只有 assemblyName 和 jobTypeFullName 同时存在才创建类型
if (!string.IsNullOrWhiteSpace(assemblyName)
&& !string.IsNullOrWhiteSpace(jobTypeFullName))
{
// 加载 GAC 全局应用程序缓存中的程序集及类型
var jobType = Assembly.Load(assemblyName)
.GetType(jobTypeFullName);
return SetJobType(jobType);
}
return this;
JobDetail.Methods 相关方法
JobDetailOptions 配置选项
IJobExecutor 作业处理程序执行器
调度作业服务提供了
IJobExecutor
执行器接口,可以让开发者自定义作业处理函数执行策略,如超时控制,失败重试等等
。public class YourJobExecutor : IJobExecutor
{
private readonly ILogger<YourJobExecutor> _logger;
public YourJobExecutor(ILogger<YourJobExecutor> logger)
{
_logger = logger;
} public async Task ExecuteAsync(JobExecutingContext context, IJob jobHandler, CancellationToken stoppingToken)
{
// 实现失败重试策略,如失败重试 3 次
await Retry.InvokeAsync(async () =>
{
await jobHandler.ExecuteAsync(context, stoppingToken);
}, 3, 1000
// 每次重试输出日志
, retryAction: (total, times) =>
{
_logger.LogWarning("Retrying {current}/{times} times for {context}", times, total, context);
});
}
} 接着模拟 MyJob 执行出错:
public class MyJob : IJob
{
private readonly ILogger<MyJob> _logger;
public MyJob(ILogger<MyJob> logger)
{
_logger = logger;
} public Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
_logger.LogInformation($"{context}"); throw new Exception("模拟出错");
return Task.CompletedTask;
}
} 最后,在注册 Schedule 服务中注册 YourJobExecutor:
services.AddSchedule(options =>
{
// 添加作业执行器
options.AddExecutor<YourJobExecutor>();
});
代码及相关资料
Sundial (二)的更多相关文章
- 【小程序分享篇 二 】web在线踢人小程序,维持用户只能在一个台电脑持登录状态
最近离职了, 突然记起来还一个小功能没做, 想想也挺简单,留下代码和思路给同事做个参考. 换工作心里挺忐忑, 对未来也充满了憧憬与担忧.(虽然已是老人, 换了N次工作了,但每次心里都和忐忑). 写写代 ...
- 前端开发中SEO的十二条总结
一. 合理使用title, description, keywords二. 合理使用h1 - h6, h1标签的权重很高, 注意使用频率三. 列表代码使用ul, 重要文字使用strong标签四. 图片 ...
- 【疯狂造轮子-iOS】JSON转Model系列之二
[疯狂造轮子-iOS]JSON转Model系列之二 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇<[疯狂造轮子-iOS]JSON转Model系列之一> ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 谈谈一些有趣的CSS题目(十二)-- 你该知道的字体 font-family
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- MIP改造常见问题二十问
在MIP推出后,我们收到了很多站长的疑问和顾虑.我们将所有疑问和顾虑归纳为以下二十个问题,希望对大家理解 MIP 有帮助. 1.MIP 化后对其他搜索引擎抓取收录以及 SEO 的影响如何? 答:在原页 ...
- 如何一步一步用DDD设计一个电商网站(二)—— 项目架构
阅读目录 前言 六边形架构 终于开始建项目了 DDD中的3个臭皮匠 CQRS(Command Query Responsibility Segregation) 结语 一.前言 上一篇我们讲了DDD的 ...
- ASP.NET Core 之 Identity 入门(二)
前言 在 上篇文章 中讲了关于 Identity 需要了解的单词以及相对应的几个知识点,并且知道了Identity处在整个登入流程中的位置,本篇主要是在 .NET 整个认证系统中比较重要的一个环节,就 ...
- MVVM模式和在WPF中的实现(二)数据绑定
MVVM模式解析和在WPF中的实现(二) 数据绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...
- Key/Value之王Memcached初探:二、Memcached在.Net中的基本操作
一.Memcached ClientLib For .Net 首先,不得不说,许多语言都实现了连接Memcached的客户端,其中以Perl.PHP为主. 仅仅memcached网站上列出的语言就有: ...
随机推荐
- 基于JESD204B和PCIe DMA的多通道数据采集和回放系统
基于JESD204B和PCIe DMA的多通道数据采集和回放系统 在主机端PCIe驱动的控制和调度下,数据采集与回放系统可以同时完成对多个JESD204B接口AD数据的采集以及JESD204B接口DA ...
- python的一些运算符
# 1.算术运算符 print('1.算术运算符') # 1.1 + 求和 a = 10 b = 20 c = a + b print(c) print('a+b={}'.format(c)) pri ...
- 云原生之旅 - 9)云原生时代网关的后起之秀Envoy Proxy 和基于Envoy 的 Emissary Ingress
前言 前一篇文章讲述了基于Nginx代理的Kuberenetes Ingress Nginx[云原生时代的网关 Ingress Nginx]这次给大家介绍下基于Envoy的 Emissary Ingr ...
- <五>关于类的各类成员
类的各种成员-> 成员方法 & 成员变量 普通的成员方法=>编译器会添加一个this形参变量 1:属于类的作用域 2:调用该方法时,需要依赖一个对象,而且常对象不能调 3:可以任意 ...
- Vue3笔记(二)了解组合式API的应用与方法
一.组合式API(Composition API)的介绍 官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html 组 ...
- bootstrap-table参数
table.bootstrapTable({ url:'/Home/geurl', //请求后台的URL(*) method:'get', //请求方式(*) toolbar:'#toolbar', ...
- MYSQL5.7 保姆级安装教程
现在要是说mysql是什么东西,就不礼貌了 虽然有的同学没有进行系统的深入学习,但应该也有个基本概念 [不了解也没关系,后续会进行mysql专栏讲解]简单来说,存储数据的 学习mysql,就要先安装它 ...
- i春秋xss平台
点开是个普普通通的登录窗口,没有注册,只有登录,抓住包也没获取什么有用的信息,看了看dalao的wp才知道怎么做,首先抓包然后修改参数的定义来让其报错,pass原本的应该为整数,pass[]=就可以让 ...
- .net如何优雅的使用EFCore
EFCore是微软官方的一款ORM框架,主要是用于实体和数据库对象之间的操作.功能非常强大,在老版本的时候叫做EF,后来.net core问世,EFCore也随之问世. 本文我们将用一个控制台项目Ho ...
- 使用Python实现多线程、多进程、异步IO的socket通信
多线程实现socket通信服务器端代码 import socket import threading class MyServer(object): def __init__(self): # 初始化 ...