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>();
      });
      

代码及相关资料

  1. Sundial-v2.5.6源码
  2. Sundial帮助文档
posted @ 2022-12-30 15:22  摧残一生  阅读(106)  评论(0编辑  收藏  举报