微服务 - 作业调度 · Hangfire集成式 · 仪表盘 · DolphinScheduler分布式 · 定义流程

前言

关于调度,个人用的不太多,更多的重心不在这里,调度自己负责的并不多,或者仅仅是调用下,或者另有替代方案。。。
早期的,有Windows服务方式、Crontab方式等等。现在市场上的产品更为丰富了,功能全面,还带UI,界面也非常友好。。。
所以选择一款适合的应用起来,也没必要再去写一套,直接使用已经非常成熟的产品,方便又快捷。除非要求很高,除非很有必要。。。

经过网罗类似产品:Hangfire、XXL-JOB、ElasticJob、Azkaban、Airflow、DolphinScheduler...

In .NET,涉及到生态及语言环境,综合考量对比后,打算用 Hangfire、DolphinScheduler 这两个产品来一起学习一下。

一、Hangfire

在.NET中,听到的、反馈的、评价的等,第三方的调度产品,Hangfire 热度不低。所以,那么,首先来学习下 Hangfire。
我把官方文档整体看下来,我的理解以及实践应用,写下来,分享给大家。它的内容较简单,如果哪里写错了,提出来让我纠正😄
Pro 版嘛。。。收费的算了,咱还是按免费的来吧🤣

有时候,我们需要定时完成一些固定的动作,重复性的动作,比如 阶段性的生成报表,比如 定时同步数据等。
有时候,我们需要完成一些任务,不是当前必须的任务,属于流程延展的任务,非及时性的动作。比如 线上购买商品后的短信通知。
有时候,我们需要某种作业顺利完成后的再触发新的作业任务,也就是两个作业任务存在先后关系。等等等等吧。

就譬如,一次性的作业任务、定时重复任务、延迟任务、触发性的...  那么来看看,Hangfire 是如何做到的!

1.1 产品介绍

Hangfire 是一个开源产品,可参考 Github,维护更新频繁,这使得我们在选择产品时很有保障。

Hangfire 是一款基于.NET平台的产品,支持 .NET Framework、.NET Core 等众多不同版本。

Hangfire 是一款集成于项目中的作业调度产品,而非独立运行的产品,我想这也是官方的产品定位。

Hangfire 官网:Hangfire – Background jobs and workers for .NET and .NET Core

Hangfire 也可认证第三方负责维护的扩展包,比如 基于MySQL的存储方式,参考 官方扩展包列表

Hangfire 也有自己的 Dashboard,可以很直观的看出,所有任务的执行状况。

作者:[Sol·wang] - 博客园,原文出处:https://www.cnblogs.com/Sol-wang/

1.2 产品定位

免费开源、基于.NET,集成在项目中,如NuGet方式,由项目代码触发调用,独立的库,多种作业方式,可设定不同的作业队列,互不影响的队列方式,不同的服务实例,共同的 Dashboard。多种存储方式,不仅支持官网提到的MSSQL/Redis等,也包括官网认证的由第三方提供及维护的MYSQL/ORACLE/PostgreSQL等的常见DB。官方提供的存储方式支持更多的功能,如并发设置/限流限速设置。

从开发者的角度切入,适合满足本服务的调度需求,使用集成包中的方法,所有调度任务均由本服务触发,本服务有绝对控制权。

1.3 产品三大块

Client

创建作业任务的入口,由你在业务代码中通过调用 Hangfire 不同的方法,创建出不同的作业类型,如 定时任务、单次任务、触发任务等。会将创建的作业任务详细信息,先存起来。

Storage

目前官网支持的存储方式有MSSQL/Redis,所以重启后继续运行不中断任务。它存储比如实例编号、创建的作业任务、异常机制、作业队列、并发限流等等。当然也有被官网认可的扩展存储方式,由第三方负责维护更新。可参考官网扩展。但官方提供的存储方式支持更多的功能,如并发设置,限流限速设置等。

Server

负责拉取已存储的作业数据,然后按照作业类型的方式,(或许用反射等)执行逻辑或方法并记录结果。所以是负责具体执行作业任务的后台服务。

官网图例展示了以上三者的关系:

1.4 集成到项目

用 VS 创建一个 WebApi,作为以下案例。

以下用 VS2022 / .NET6 / Hangfire v1.7 / SqlServer2019 版本集来学习。

1.4.1 安装与配置

ℹ️最简单的安装方式,Hangfire内包括了所有需要用到的包

dotnet add package Hangfire
dotnet add package Microsoft.Data.SqlClient

或者按需安装,需要什么装什么

dotnet add package Hangfire.Core
dotnet add package Hangfire.SqlServer
dotnet add package Microsoft.Data.SqlClient

ℹ️首先,在运行前,你要先创建一个空库,比如创建一个名为 HangfireTest 的数据库。那么接下来以这个空库,来初始化 Hangfire。

CREATE DATABASE HangfireTest;

appsettings 配置

{
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Hangfire": "Information",
            "Microsoft.AspNetCore": "Warning"
        }
    },
    "ConnectionStrings": {
        "HangfireConnection": "Server=13.13.1.13;Database=HangfireTest;uid=sa;pwd=********;"
    }
}

ℹ️Program 中的服务注册

public static void Main(string[] args)
{
    var builder = WebApplication.CreateBuilder(args);
    // ...省略...
    
    # ⭐️ 配置 Hangfire
    builder.Services.AddHangfire(configuration => configuration
    .SetDataCompatibilityLevel(CompatibilityLevel.Version_170)
    .UseSimpleAssemblyNameTypeSerializer()
    .UseRecommendedSerializerSettings()
    .UseSqlServerStorage(builder.Configuration.GetConnectionString("HangfireConnection")));
    
    # ⭐️ 注册 Hangfire 服务
    builder.Services.AddHangfireServer();
    
    var app = builder.Build();
    // ...省略...
    
    # ⭐️ 启用 Dashboard
    app.UseHangfireDashboard();
    
    app.Run();
}

然后,在此项目的业务代码中,Hangfire 就可以直接的用了。

项目第一次 Run 起来,自动初始化DB

Dashboard 默认访问地址 /hangfire,也可自定义地址,如 app.UseHangfireDashboard("/myhangfire");

1.4.2 创建作业任务

一个 [ Say hi ] 的任务,很简单。假设我们在控制器中写下:

[HttpGet(Name = "Say")]
public void Get()
{
    BackgroundJob.Enqueue(() => Console.WriteLine("Say hi"));
}

当然,我们要贴合实际应用,为此建立以下假设的功能类,用以协助案例演示。

# 演示的业务类,供示例调用
public class JobTest {
    public void Enqueue_method(string uname) {
        Console.WriteLine($"Hi {uname}, I'm Enqueue!");
    }
    public void Delayed_method() {
        Console.WriteLine($"I'm Delayed!");
    }
    public void Recurring_method() {
        Console.WriteLine($"I'm Recurring!");
    }
}

ℹ️ 创建一次性作业,调用了上面 JobTest 类的 Enqueue_method 方法

# 仅执行一次后,作业失效
BackgroundJob.Enqueue<JobTest>(m => m.Enqueue_method(uname));

ℹ️ 创建延迟作业,设定延迟时间

# 7 秒后,再执行 Delayed_method 方法 
BackgroundJob.Schedule<JobTest>((m) => m.Delayed_method(), TimeSpan.FromSeconds(7));

ℹ️ 创建定时作业,设定间隔时间

# 周期性 循环执行 Recurring_method 方法
RecurringJob.AddOrUpdate<JobTest>((x) => x.Recurring_method(), Cron.Minutely);
# Crontab 表达式 方式,设置间隔时间,每秒执行
RecurringJob.AddOrUpdate<JobTest>((x) => x.Recurring_method(), "0/1 * * * * ?");
# 带 标识编号 的重复作业任务,每分钟
RecurringJob.AddOrUpdate<JobTest>("Job_Rec_001", (x) => x.Recurring_method(), Cron.Minutely);

ℹ️ 临时执行一次定时作业,不影响重复作业的下次运行

# 临时执行下 上面名为 Job_Rec_001 的作业
RecurringJob.TriggerJob("Job_Rec_001");

ℹ️ 删除指定的定时作业,永久停止重复运行

# 删除 已存在的 名为 Job_Rec_001 的重复作业
RecurringJob.RemoveIfExists("Job_Rec_001");

ℹ️ 创建顺序作业,触发执行作业

# 先假设一个延迟的作业任务
var job_number = BackgroundJob.Schedule<JobTest>((m) => m.Delayed_method(), TimeSpan.FromSeconds(7));
# 当上个任务成功后,触发以下作业任务
BackgroundJob.ContinueJobWith(job_number, () => Console.WriteLine("Delayed > Continuation!"));

都很简单,加入到业务代码中即可。

1.5 高阶及应用

当然,默认的配置,已经很满足我们的需求,它一定是官方经过不断的测试得出的各种场景下的最优配置。有些时候,或许我们有自己的需要,细化,扩展,调优等的需求,那么下面我们来了解下其它有趣的东东。

1.5.1 服务配置

当然,之前只是默认配置项,那么下面来看看自定义的服务配置:

# ⭐️ 注册 Hangfire 服务,服务自定义配置
builder.Services.AddHangfireServer(new BackgroundJobServerOptions {
    # 主要关注项
    Queues = string new[] { "default", "alpha" },   # 自定义队列名称,默认只有 default
    WorkerCount = Environment.ProcessorCount * 5,   # 执行作业的工作线程数量,后台服务的处理能力
    # 次要配置项
    ServerName = Guid.NewGuid().ToString(),         # 服务名称,用于区别不同的实例 {Environment.MachineName}.{GUID}
    ServerTimeout = TimeSpan.FromMinutes(1),        # 后台执行超时,用户后台服务执行等待的最长时间
    HeartbeatInterval = TimeSpan.FromMinutes(1),    # 健康检测,用于检测服务的运行状况
    SchedulePollingInterval=TimeSpan.FromMinutes(1) # 间隔检测队列中要执行的作业,周期性作业的检测间隔时间,默认15秒
});

1.5.2 作业队列

作业队列,互不影响的多个作业队列,正如上节服务中配置的那样,在启动时定义好有哪些可用的作业队列,这些定义的作业队列怎么用呢?

参考 [创建作业任务] 小节中的 JobTest 类,我们把这个业务类的方法打上Queue("alpha")队列名称,也相当于此作业的归属队列:

public class JobTest {
    [Queue("alpha")]
    public void Enqueue_method(string uname) {
        Console.WriteLine($"Hi {uname}, I'm Enqueue!");
    }
}

如此,把每个作业归属到各自队列,有各自的先后顺序,互不影响,这在过多的应用场景上做划分很有用。

1.5.3 异常重试机制

由于种种原因,或许外部因素,或许内部逻辑,网络/硬件/第三方等等,可能会遇到作业执行失败的情况,那么失败后咋办,有没有可以弥补的措施呢?当然,重试机制是标配,让意外状况降到更低,更能确保应用的正常运转,来看看 Hangfire 重试机制的设定。

同样,继续参考 [创建作业任务] 小节中的 JobTest 类,我们把这个业务类的方法设定自动重试属性:AutomaticRetry

  • Attempts:重试次数,默认 10 次
  • DelaysInSeconds:重试间隔,一个秒数数组,也就是说,每次的间隔都可以设置
public class JobTest {
    [Queue("alpha")]
    [AutomaticRetry(Attempts = 5, DelaysInSeconds = new int[]{ 2, 5, 10 })]
    public void Enqueue_method(string uname) {
        Console.WriteLine($"Hi {uname}, I'm Enqueue!");
    }
}

1.5.4 机制原理闲聊

前面也提到过三大块,客户端、存储与执行服务,那么这里要说的是,,,先不说😄😄 先来看看DB中存的什么,{"t":"System.Console, mscorlib","m":"WriteLine","p":["System.String"],"a":["Say hi"]},源代码???

也就是说,客户端并没有马上立即着手执行,而是先把要执行的内容保存起来,那么谁来执行。Hangfire-Server,是的,Hangfire服务会执行到这个内容代码的,尽管客户端与服务两者的时间甚短,分工很明确。由此,又引出两点,第一,服务哪来的,集成在应用中的,也就是与应用同一进程中的线程,那么,既然这样,Hangfire服务的运行会影响到应用本身的运行,对不啦。第二,既然相当于是应用本身运行的已保存代码,代码难免有引用关系,那么,不同的应用,或者说多个应用,用同一个Hangfire库呢,绝对不行,其它应用不可能有保存代码中调用的方法等之类的引用。也就是说,Hangfire只能与服务一对一的存在。那么集群呢,单服务部署多个实例呢,当然也可以,保存的代码在不同的服务实例中都可以运行。

所以啊,我认为啊,Hangfire 更多的是面向开发人员,随意嵌入到业务逻辑中,针对单个服务,对不啦~

二、DolphinScheduler

2.1 产品简介

这个产品呢...
其实这里也不是为了学习这个产品,它有足够多的功能,这里就主要来看些与调度有关的了。比如,以管理员的角色,架构者的身份,面对整个平台,包括对外产品/内部产品/各服务/PC/移动/DB等等吧,从全局进行的作业调度,为实现某个额外的环节等。这个产品的侧重点主要是自定义流程,那么这里也包含有关于调度的内容,比如,网络请求,当然也能定义为调度了,我可以用它来向我内部地址定时拉取推送数据等,再比如,执行DB脚本,也可以做为调度的内容项,我可以定时做数据转移或报表计算等。DolphinScheduler 支持的内容多了,并可自动并行串行...

所以呢,把 DolphinScheduler 拿过来说说。有兴趣,看看官网的定义。几个特点:自定义 推拽 制作流程,支持的流程节点任务类型超多,SHELL/SQL/HTTP/Spark/HIVE/云/逻辑节点/甚至机器学习等等,好多我没用过(就很全吧);节点并行或串行运行等等;支持上十种告警方式,Email/Telegram/钉钉/微信等等,是不是很中国,对的,国人的作品,被 Apache 收录为孵化项目。再回过头来继续说说它的特点,管理运行自定义流程,定时运行/暂停/恢复/下线等的管理维护。上十万个任务的稳定运行。。。

2.2 部署运行

也不是多严谨的部署过程了,仅为学习,所以就用 Docker 方式咯,快捷嘛。生产环境就得认真对待了。

关于涉及到的 Docker 安装就不多说了,可参考以往文章 Docker 笔记汇总。DolphinScheduler 官网文档中有容器的运行说明,这里就用最简单的方式运行容器apache/dolphinscheduler-standalone-server来学习吧。

我们的目的是用不同的任务类型自定义流程,来吧。

ℹ️先运行 Docker 容器

docker run -dit --name dolp-server -p 12345:12345 -p 25333:25333 apache/dolphinscheduler-standalone-server:3.2.1

ℹ️登录产品

浏览器访问:http://{your-address}:12345/dolphinscheduler/ui/login
默认账号密码:admin / dolphinscheduler123

ℹ️产品首页

2.3 自定义流程

一个简单的流程制作示例,体验下 DolphinScheduler 的自定义过程。从中感受下 DolphinScheduler 为我们带来了什么。

ℹ️看我做的一个简单的示例流程:

ℹ️是的,来说说上图自定义流程的各节点任务吧:

  • SHELL 节点1 - 是的,我想通过 SHELL 脚本来操作服务器,比如对服务文件资源的操作
  • HTTP 节点2 - 我设想的是,通过一个请求,拿到返回数据。这在我们的产品平台上很常见
  • MSSQL 节点3 - 我设想的是,拿到上一个HTTP任务节点请求的数据,再存储到DB中,等等吧

通过拖拽设定,可以实现一些有趣的功能,挺好的。

ℹ️为自定义流程设定运行时间,妥妥的任务调度

国人的产品,也就是说,中文文档很齐全,阅读起来很顺畅... 😄

2.4 价值与定位

果然,DolphinScheduler 为每种不同的服务,通过鼠标的操作,实现打通壁垒,建立联系,制定流程的运行策略,当然更多的功能等待探究。。。就像之前说的那样,做为一个管理者/运维/架构等,从全局出发,面对各类运行的产品,为满足不同的业务需要或数据资源等的操作。

这里没有去更深入的探究,只是做了一个示例和了解,大概知道是怎么一回事,作为我们今后工作中,关于调度的一个可选项...

三、对比总结

所以呢,本篇:

一个是 Hangfire,从开发者的角度切入,主要以代码方式,实现关于调度;

另一个是 DolphinScheduler,从全局的角度,低代码方式,各服务实现互通,以达到业务场景需要的调度。

 

... 更多优秀的有趣的实现或方式,后续再追加吧。

posted @ 2024-05-06 09:17  Sol·wang  阅读(938)  评论(0编辑  收藏  举报