前面放完建设四个现代化大数据平台乌托邦理想的大卫星,接下来的文章得谈谈具体组件的生产大跃进了。
第一篇,先来讨论一下大数据开发平台的核心组件之一:作业调度系统。
作业调度系统是一个相对复杂的系统,涉及的内容繁杂,针对的场景多种多样,实现的方案千差万别,是一个需要理论和实践并重的系统。
本文重点谈理论,会先从大的场景划分的角度对市面上的各种调度系统进行分类讨论,然后再针对具体的作业调度系统,探讨一下各自的架构流派和实现方案,并简单分析一下各自的优缺点。希望能让大家对作业调度系统要做什么,该怎么做,有一个大致的了解
调度系统,更确切地说,作业调度系统(Job Scheduler)或者说工作流调度系统(workflow Scheduler)是任何一个稍微有点规模,不是简单玩玩的大数据开发平台都必不可少的重要组成部分。
除了Crontab,Quartz这类偏单机的定时调度程序/库。开源的分布式作业调度系统也有很多,比较知名的比如:oozie,azkaban,chronos,zeus等等,此外,还有包括阿里的TBSchedule,SchedulerX,腾讯的Lhotse,当当的elastic-job,唯品会的Saturn等等
可以说,几乎每家稍微有点规模的数据平台团队,都会有自己的调度系统实现方案,要不然自研,要不然在开源的基础上进行一些封装和改造(比如很多公司采取了封装oozie的方式)。
在继续讨论作业调度系统之前,首先允许我讨论一下作业调度系统和资源调度系统(或者集群调度系统)的区别,因为往往有同学把这两者混为一谈。后者的典型代表比如:Yarn/Mesos/Omega/Borg,还有阿里的伏羲,腾讯的盖娅(Gaia),百度的Normandy等等。
资源调度系统,它的工作重点是底层物理资源的分配管理,目标是最大化的利用集群机器的CPU/磁盘/网络等硬件资源,所调配和处理的往往是与业务逻辑没有直接关联的通用的程序进程这样的对象。
而作业调度系统,关注的首要重点是在正确的时间点启动正确的作业,确保作业按照正确的依赖关系及时准确的执行。资源的利用率通常不是第一关注要点,业务流程的正确性才是最重要的。
作业调度系统有时也会考虑负载均衡问题,但保证负载均衡更多的是为了系统自身的健壮性,而资源的合理利用,作为一个可以优化的点,往往依托底层的资源调度系统来实现。
那么,为什么市面上会存在这么多的作业调度系统项目,作业调度系统为什么没有像Hdfs/Hive/HBase之类的组件,形成一个相对标准化的解决方案呢?归根到底,还是由作业调度系统的业务复杂性决定的。
一个成熟易用,便于管理和维护的作业调度系统,需要和大量的周边组件对接,不仅包括各种存储计算框架,还可要处理或使用到包括:血缘管理,权限控制,负载流控,监控报警,质量分析等各种服务或事务。这些事务环节,在每家公司往往都有自己的解决方案,所以作业调度系统所处的整体外部环境,千差万别,再加上各公司各种业务流程的定制化需求进一步加大了环境的差异性,所以,调度系统很难做到既能灵活通用的适配广大用户的各种需求,又不落到太过晦涩难用的地步。
市面上的各种开源的作业调度系统,要不然就是在某些环节/功能上是缺失的,使用和运维的代价很高,需要大量二次开发;要不然就是只针对特定的业务场景,形态简单,缺乏灵活性;又或者在一些功能环节上是封闭自成体系的,很难和外部系统进行对接。
那么,理想中,完备的作业调度系统,到底都要处理哪些事务呢?要做好这些事务都有哪些困难要克服,有哪些方案可以选择,市面上的开源调度系统项目又是如何应对这些问题的,容我和大家一起来探讨一下。
首先,让我来对作业调度系统进行一下大的分类。市面上的作业调度系统,根据实际的功能定位,主要分为两大类方向:定时分片类作业调度系统和DAG工作流类作业调度系统
这两类系统的架构和功能实现通常存在很大的差异。所以下文先做一下两者的简单的比较。
定时分片类系统的方向,重点定位于任务的分片执行场景,这类系统的代表包括:TBSchedule,SchedulerX,Elastic-job, Saturn。我司自研的Vacuum也是这样的系统。
这种功能定位的作业调度系统,其最早的需要来源和出发点往往是做一个分布式的Crontab/Quartz。
一开始各个业务方八仙过海,自己玩自己的单机定时任务,然后,随着业务的增长,各种定时任务越来越多,分散管理的代价越来越高。再加上有些业务随着数据量的增长,为了提高运行效率,也需要以分布式的方式在多台机器上并发执行。这时候,分布式分片调度系统也就孕育而生了。
这类系统的实际应用场景,往往和日常维护工作或需要定时执行的业务逻辑有一定关联。比如需要定时批量清理一批机器的磁盘空间,需要定时生成一批商品清单,需要定时批量对一批数据建索引,需要定时对一批用户发送推送通知等等。
这类系统的核心目标基本上就是两点:
对作业分片逻辑的支持:将一个大的任务拆成多个小任务分配到不同的服务器上执行, 难点在于要做到不漏,不重,保证负载平衡,节点崩溃时自动进行任务迁移等
高可用的精确定时触发要求:因为往往涉及到实际业务流程的及时性和准确性,所以通常需要保证任务触发的强实时和可靠性
所以,负载均衡,弹性扩容,状态同步和失效转移通常是这类调度系统在架构设计时重点考虑的特性
从接入方案和流程上来说,因为要支持分片逻辑,要支持失效转移等,这类调度系统,对所调度的任务通常都是有侵入性要求的。
常见的做法是用户作业需要依赖相关分片调度系统的客户端库函数,拓展一个作业调度类做为作业触发的入口点。一般还需要接收和处理分片信息用于对数据进行正确的分片处理。此外通常还需要实现一些接口用于满足服务端的管理需求,比如注册节点,注册作业,启动任务,停止任务,汇报状态等等。有些系统还要求作业执行节点常驻Daemon进程,用于协调本地作业的管理和通讯。
从触发实现逻辑的角度来说,为了在海量任务的情况下,保证严格精确定时触发,这类调度系统有一大半,其定时触发逻辑,实际上是由执行节点自身在本地触发的,也就是说要求作业或守护进程处于运行状态,向服务端注册作业,服务端分配分片信息和定时逻辑给到客户端,但定时的触发,是由客户端库函数封装的如Quartz等定时逻辑来实际执行触发的。
这样做的首要目的当然是为了保证触发的精度和效率,降低服务端负载,此外如果服务端短时间内挂掉,只要作业配置保持不变,作业还是能够在客户端正常触发的。
也有些系统,比如SchedulerX,是采用服务端触发逻辑的。这对服务端的要求就高了很多,因为这时候,服务端不光要协调分片逻辑,还要维护触发队列。所以服务端触发的系统,首先需要保证服务端的高可用,其次还要保障性能,因此,通常都是采用集群方案
这一类系统的方向,重点定位于任务的调度依赖关系的正确处理,分片执行的逻辑通常不是系统关注的核心,或者不是系统核心流程的关键组成部分,如果某些任务真的关注分片逻辑,往往交给后端集群(比如MR任务自带分片能力)或者具体类型的任务执行后端去实现。
这类系统的代表,包括:oozie,azkaban,chronos,zeus,Lhotse,还有各种大大小小的公有云服务提供的那些可视化工作流定义系统。我司自研的Jarvis调度系统也属于这类系统
DAG工作流类调度系统所服务的往往是作业繁多,作业之间的流程依赖比较复杂的场景,比如大数据开发平台的离线数仓报表处理业务,从数据采集,清洗,到各个层级的报表的汇总运算,到最后数据导出到外部业务系统,一个完整的业务流程,可能涉及到成百上千个相互交叉依赖关联的作业。
所以DAG工作流类调度系统关注的重点,通常会包括:
足够丰富和灵活的依赖触发机制:比如时间触发任务,依赖触发任务,混合触发任务
而依赖触发自身,可能还要考虑,多亲依赖,长短周期依赖(比如小时任务依赖天任务,或者反过来),依赖范围判定(比如所依赖任务最后一次成功就可以触发下游,还是过去一个星期的所有任务都成功才可以触发下游),自身历史任务依赖,串并行触发机制等等
作业的计划,变更和执行流水的管理和同步
这一条,定时分片类调度系统固然也要考虑,但通常都相对简单。
而在DAG工作流类调度系统中,因为任务触发机制的灵活性和作业依赖关系的复杂性,这个需求就尤为重要,需要提供的功能更加复杂,具体的问题解决起来也困难很多。
任务的优先级管理,业务隔离,权限管理等
在定时分片类调度系统中,通常情况下,具体执行端的业务的隔离很多情况下是天然的,注册了特定业务的节点才会去执行特定的任务。然后,加上业务链路一般都比较短,以及强实时性要求,所以对优先级的管理通常要求也不高,基本靠资源隔离来实现资源的可用,不太存在竞争资源的问题,权限管理也同理。
而在DAG工作流类调度系统中,往往一大批作业共享资源执行,所以优先级,负载隔离,和权限管控的问题也就突显出来
各种特殊流程的处理,比如暂停任务,重刷历史数据,人工标注失败/成功,临时任务和周期任务的协同等等
这类需求,本质上也是因为业务流程的复杂性带来的,比如业务逻辑变更啦,脚本写错啦,上游数据有问题啦,下游系统挂掉啦等等,而业务之间的网状关联性,导致处理问题时需要考虑的因素很多,也就要求处理的手段要足够灵活强大
完备的监控报警通知机制
最简单的比如,任务失败报警,超时报警,再进一步,流量负载监控,业务进度监控和预测,如果做的再完善一点,还可以包括业务健康度监控分析,性能优化建议和问题诊断专家系统等
需要注意的是,这两类系统的定位目标,并不是绝对冲突矛盾的。只不过,要同时圆满的支持这两大类需求,从实现的角度来说是很困难的,因为侧重点的不同,在架构上多少会对某些方面做些取舍,当前的系统都没有能够做到完美的两者兼顾。但这不代表他们就一定是不可调和的,好比离线批处理计算框架和流式实时计算框架,长期以来各行其路,但是,随着理论,实践的进步,也开始有依托一种框架统一处理两类计算业务的可能性出现。
前面说到,DAG工作流调度系统有很多开源实现,各大公司也往往有自己的系统实现。这些系统,从开发语言,支持的任务类型,调度方式,监控报警,到业务接入的方式,周边管理工具的完备性等角度来说,都是千差万别的。这些差别在很大程度上都是产品形态招式上的差异,但是有一条,不止招式那么简单,我认为是心法流派的差异,它在很大程度上影响了一个系统的核心框架设计思想。
这个差异就是:具体任务的执行,是依托于一个静态的执行列表还是动态的执行列表,此话怎讲?别急,待我详细解释一下。
要谈执行列表的心法流派问题,首先,得明确作业计划和任务实例两个概念
通常情况下,既然你把一个作业丢到调度系统上去监管执行,那除了个别一次性作业,多数情况下这些作业都是要周期性重复执行的,只是有些纯粹由时间驱动,有些还有前置任务依赖关系要处理。
那么什么时候执行这个作业,是每个月月底执行一次,还是每天凌晨2点执行一次,又或者还是早上9点到晚上6点之间,每个小时执行一次?任务触发的条件,是前置任务全部成功,还是任一前置任务成功,又或者要求自身的上一次执行结果也要成功? 回答这些问题的,就是所谓的作业计划(Plan)
而具体落到某年某月某天,一个作业什么时候真正执行一次?这一次具体的执行,就是所谓的任务执行实例(Instance)
回头继续讨论,所谓的静态执行列表流派,是说一个作业的具体执行实例,是跟据作业计划提前计算并生成执行列表的,然后调度系统按照这个提前生成的执行列表去执行任务。 更进一步,有些系统实际上压根就没有区分作业计划和任务执行列表,两者是和二为一的,人工定义一个确定了依赖和先后关系任务列表,定期执行这整个列表。
对于实际有作业计划和执行列表之分的系统,常见的做法是在头一天晚上接近凌晨的时候,分析所有作业的时间要求和作业间的依赖关系,然后生成下一天所有需要执行的作业的实例列表,将具体每个任务实例的执行时间点和相互依赖关系固化下来。调度系统执行任务时,遍历检查这个列表,触发满足条件的任务的执行。
oozie,azkaban和大多数公有云上的workflow服务,和我司的第一代Jarvis调度系统,基本上都是属于这个流派。其中oozie,azkaban基本采用的是作业计划和执行列表一体的方案,我司的Jarvis一代采用的是两者分离,由作业计划定期生成执行列表的方案。
然后,所谓的动态执行列表流派,是说某个作业的具体执行实例,并没有提前固化计算出来,而是在它的上游任务(纯时间周期任务的话就是上个周期的任务)执行完毕时,根据当时时刻点最新的作业计划和依赖关系动态计算出来的。
zeus,chronos和我司的第二代Jarvis调度系统,基本上是属于这个流派的。
这两个流派没有绝对的优劣之分,各有优缺点,各有自己擅长处理的场景和不擅长处理的场景,所以有时候系统的具体实现也不完是绝对互斥的,在某些功能方面也是有变化取舍的。
那么,为什么会有两种流派?提前生成执行列表还是需要的时候再生成,又有什么关系吗?两种流派各自的主要问题和难点是什么?
之所以会有两种流派,问题的源头在于,作业计划和执行实例列表,这两者服务的对象不同。
从周期性作业管理的角度来说,你面向的对象当然应该是作业计划,当你想改变一个周期性作业的执行策略时,你修改的是作业的执行计划本身。而做为调度系统,在具体任务的执行过程中,它面向的对象则是任务的一次执行实例,而非计划本身。
所以当计划产生变更时,就涉及到作业计划和执行列表之间的同步问题。
对于静态执行列表流派来说,处理确定的任务执行列表是它的长项,因为执行列表一旦生成,那你就可以对它进行任意的修改,可以进行各种Hack,不再需要考虑原有的作业计划依赖关系等等。比如你今天想临时跳过一部分任务的执行,直接把它们从实例列表中删除,并从下游任务的依赖列表中移除依赖关系就好。
而对于动态执行列表流派来说,这种临时的Hack动作就会比较难处理,因为计划和实例是根据规则强关联的。要影相今天的任务实例,可能就要修改作业的计划,而修改了计划,后续比如明天的任务实例也可能会受到影响。
但是,对于执行实例或者具体实例的依赖关系难以提前确定和生成的场景,比如不等周期的依赖(比如月底的月任务依赖于每天的日任务)或者任意成功条件即可触发,触发实例个数不确定的情况,就几乎无法提前生成静态的执行列表克。
再比如在一些短周期任务计划变更,或者任务依赖关系调整,任务列表已经有部分任务执行完毕的情况下,静态执行列表方案能否快速,正确的更新执行列表, 都会遇到很大的挑战。
再比如,一天的所有任务中,有些任务的修改是临时的,有些任务的修改是长期的,这种情况下,静态执行列表的方案因该如何应对呢? 对于计划和执行列表一体的系统,几乎是没法做的,只能再复制生成一份临时执行列表,却别对待。而对于从计划列表定时生成执行列表的系统,则势必需要部分修改已实例化的任务执行列表,部分修改未实例化的作业计划,在这种模式下,如何保证两边的修改不冲突,如果冲突,以谁为主,甚至能否发现冲突,往往都是很困难的。
所以,简单总结来说,静态执行列表方案,擅长处理时间范围确定的(最好是当前周期的),已知的,一次性的任务变更,前提是你对如何Hack执行列表有清楚的认识。动态执行列表方案擅长处理尚未发生的,长期的计划变更,对于不等周期和短周期任务的变更时效性也会好很多,临时的一次性变更则需要通过其它方式来辅助完成。
当然,这两种流派针对自己不擅长的场景,多少也能找到各种的补救手段来应对,并非完全一筹莫展,只是补救手段的复杂程度和代价大小的问题。
这两种流派,我们都实践过,总体看来,静态执行列表方案,系统架构相对简单,系统运行逻辑相对直白,容易分析问题,但是能处理的场景也比较有限。动态执行列表方案,能覆盖的场景范围更广,计划变更响应更及时,但是系统架构实现相对复杂,运行逻辑也更加复杂,牵扯的因素较多,有时候不容易一眼理顺逻辑。
所以,如果是在业务场景比较简单,任务依赖容易理清的场景下,静态执行列表方案的系统维护代价会比较小,反之,则应该考虑构建动态执行列表方案的系统。
最后,这两种方案也并非完全互斥,我司的jarvis二代调度系统,就在一些局部功能中使用了静态执行列表的思想,来辅助处理一些动态执行列表方案比较难应对的问题。比如,用户需要知道今天有哪些任务将要执行,什么时候执行,这就会需要一个实例化的执行列表,总不能跟用户说我们的任务是动态实例化的,所以还没有执行的任务无法展示吧 :)
谈完心法再来谈谈招式,不论流派如何,最后落实到系统实现,从系统的角度,需要考虑具备哪些特性,可以提高稳定性,降低管理维护成本,从用户的角度,关心的则是能够提供哪些功能,可以提高工作效率,降低开发使用成本。
既然是工作流调度系统,用户首先要面对的问题,当然是如何定义和管理工作流。
静态显式定义和管理工作流
多数静态执行列表流派的系统,比如oozie,azkaban以及各种公有云的workflow服务,都会包含创建工作流Flow这样一个过程,用户需要定义一个具体的作业流程里面都包含哪些作业,他们的先后依赖关系如何。所不同的是,用户通过什么手段来定义和描述这个工作流:
比如oozie要求用户提供XML文件(也可以通过API提交),按照规定的格式描述各个工作流的拓扑逻辑和Job的依赖关系,各种任务类型的细节配置等等。
Azkaban要求用户定义.job文件来描述作业的依赖关系,然后为每个没有依赖关系的作业及其下游作业创建一个工作流,如果要嵌套子工作流,则需要显式的申明和创建。然后将所有的.job文件和作业执行需要的依赖打包成zip包通过服务器上传,最终在服务器上创建出工作流并展示给用户。
oozie和Azkaban采取的这两种方式,从系统设计的角度来说,对外部系统的关联和依赖比较小,是一个相对独立封闭的环境,演进起来比较自由。但这两个系统最大的问题是,周边的运维使用工具太过缺乏,易用性很差。作为工具使用可以,但是做为平台服务,缺失了太多内容,工作流的定义和维护成本太高。所以有很多公司是在oozie和Azkaban的基础上对工作流的提交管理进行了二次开发封装,来降低使用难度。
而各种公有云的workflow服务,则多半是通过图形化拖拽作业节点的方式,来让用户显式的定义工作流,本质上和oozie的方式没有太大区别,只是通过可视化的操作来屏蔽配置的语法细节,降低工作流定义的难度。
动态隐式定义和管理工作流
Chronos,Zeus和我司的两代Jarvis调度系统,走的则是另一条路。系统中并没有让用户显式的定义一个工作流。实际上,这些系统的管理维度是作业,用户定义的是作业之间的依赖关系,哪些作业构成一个工作流,系统实际上并不关心,用户也不需要申明,系统只负责按规则将所有满足条件的任务调度起来,将一批任务圈定成一个Flow这种行为,对系统来说并非必需。你甚至可以理解为整个系统里的所有作业就是一个多进多出并发执行的大Flow。
对比
你要问那比如权限隔离,调度配置这些,没有了Flow的概念怎么处理? 事实上,这些概念和一组任务的执行链路这个概念压根就没有必然的关系。Flow关注的是的依赖关系,其它上面提到的那些概念关注的是资源的管控,这两者涉及的对象可以重叠,但是并非一定要重叠,有些时候也不适合重叠。
那两种处理方式各有什么优缺点呢?
显式定义工作流Flow这种方式,优点是用户明确的知道哪些任务是一组的,适合处理工作流内作业规模较小,工作流之间的作业没有交叉依赖,不会频繁变更的场景,用户的掌控感可能较强,但是反之,作业规模大,关联复杂,变更频繁的场景实际上是不太适合的,另外对依赖和触发逻辑的支持,局限性也较大(下一节详解)
而不显式定义工作流的方式,用户无需理会和手工定义和处理工作流这个概念,使用灵活,作业之间依赖变更,业务调整等行为,都会自动反映到整体的任务执行流程中,对于用户而言,管理的压力较小,作业流程变更操作简单。相对不足的地方是作业的分组这个概念没有Flow来承接,资源的管理需要通过其它方式来实现。
显式静态定义工作流的系统,对作业运行周期的管理,通常都是以整个工作流为单位来定义和管理的。当调度时间到达时,启动整个工作流,工作流内部的作业按照依赖关系依次执行。所以如果一个工作流内部存在需要按不同周期进行调度的作业,就会很难处理,需要想各种补救手段去间接规避,比如拆分工作流,创建子工作流,或者复制多份作业等。
非显式动态定义工作流的系统,对作业运行周期的管理,通常是以单个作业为单位的(因为压根就没有固定的Flow这个单位可以管理 ),所以用户只需要按需定义自己作业的运行周期就好了。相对的,对调度系统开发者来说,实现的难度会比较大,因为正确的自动判定依赖触发关系的逻辑会比较复杂。
在开始讨论依赖管理之前,我们先来看看通常用做判断一个作业的具体任务实例是否可以开始运行的条件都有哪些?
首先当然是时间依赖,大量的定时触发任务,依托时间来判断是否满足运行条件。其次是任务依赖,需要根据前置任务的执行情况,来决定当前任务是否满足运行条件。
通常情况下,这两种依赖条件构成了大多数调度系统启动任务运行的核心判定依据。但是有时候还有第三种依赖条件,就是数据依赖,通过判断任务运行所依托的数据是否存在来决定是否启动任务。
理论上,如果所有生成数据的任务的运行状态和结果都能被调度系统所控制或者感知,那么数据依赖这种依赖关系就是一个伪命题,既非必要,往往也不是最佳解决方案。
为什么这么说?因为数据依赖意味着对调度系统而言,业务逻辑不再是透明的,一方面,你需要知道获取数据信息的途径,另一方面,如何判定一个任务依赖的数据是否完备了,本身也不是一件容易的事,容错性往往也不好。
比如你通过文件是否存在来判断,那么文件里的内容是错的呢?或者生成文件的任务跑了一半失败了,文件内容不完整呢?即使你能保证文件的正确性和原子性,那么如果上游任务重跑刷新了数据呢?你如何判定这种情况?
总体来说,个人认为,一个调度系统,如果对数据依赖这种依赖关系依托得越多,那么相对来说整个系统就越不成熟和完备,需要人工干涉的可能性越高。当然,事事无绝对,也有一些使用数据依赖才是最合理有效的解决方案的场景。而且,退一步说,调度系统再完善,也是一个有边界的系统,总难免一些依赖要通过外部数据的判定来实现。
回头继续讨论作业依赖关系的管理
对于采用人工显式定义工作流的系统而言,作业依赖的管理,在很大程度上,其实是通过对工作流的拓扑逻辑的管理来实现的,用户改变工作流的拓扑逻辑的过程,实际上也就改变了作业间的依赖关系。 而作业的任务依赖关系,其边界,基本上就是在当前的工作流范围之类。
对于非显式定义工作流的系统而言,用户直接管理作业的依赖,所以这类系统一般都会提供给用户配置上游任务和触发条件的接口/界面。用户通过改变作业之间的依赖关系,间接的影响相关联作业的运行流程拓扑逻辑
所以,你要问,这两种定义和管理作业依赖的方式,看起来只是角度不同而已,换汤不换药,采用那种,只是流程的需要,实际效果没有什么区别吧?实际上,并不完全如此,比如,从用户的角度来说:
前面一种管理方式,需要用户对工作流内部的作业比较了解,对当前的工作流拓扑逻辑也足够清晰,才能较好的保证将新的作业安排放置到正确的位置上去。但只要满足依赖,作业节点的安排位置也可以比较自由。
举个简单的例子,比如BC两个作业分别依赖作业A,其它没有相互依赖关系。那么,我可以把BC作业并行的放在A作业后面,也可以为了控制资源使用,让BC作业串联的放置在A后面(相当于人工将作业C改为依赖作业B)。
而后面一种管理方式,用户只需要关心当前任务的前置任务有哪些就可以了,因此对用户的要求降低了不少。不过这样一来拓扑逻辑图是唯一且自动生成的(上例ABC作业,就只能是BC作业并行放在A作业后面),缺点是你无法自由调整工作流程,但实际上你多半也没有调整的必要。如果是为了控制作业优先级,大可通过其它方式实现
后者还有一个很大的优势,就是如果你的任务的依赖关系可以自动分析出来的话(比如hive任务,可以通过解析脚本,自动判断上下游数据表,然后再通过数据表关联自动找到上下游任务),那么多数情况下,用户甚至都可以不用配置作业依赖关系,直接添加具体作业就完事了,工作流的拓扑关系全部交给系统自动分析,添加和调整。比如,我司的Jarvis调度系统,结合Hive元数据血缘分析工具,就基本上达到了这样的效果 ;)
最后,用户显式定义工作流这种模式,对跨工作流的任务依赖也很难处理,原本在一个工作流内部可以通过任务依赖来实现的控制,在跨工作流以后,通常就只能通过数据依赖的手段来辅助实现了,但如前所述,这么做的话,一来用户可能需要定制数据依赖检测逻辑,二来在遇到数据重跑之类的场景,任务的正确执行就需要进一步的人工干预处理了。
常在河边走,哪有不湿鞋,运行作业多了,总会出问题。所以对用户来说,作业异常流程管理能力的好坏,也是工作流调度系统是否好用的一个重要考虑因素。
比如,如果一个中间任务的脚本逻辑有错,需要重跑自身及后续下游任务,该如何处理?用户通过什么样的方式完成这件工作?需要手工重新创建一个新的工作流?还是可以通过勾选作业,自动寻找下游任务的方式实现?
比如一个任务运行失败,但是结果数据通过其它手段进行了修复,那么如何跳过该任务继续运行后续任务?
再比如,任务失败是否能够自动重试?重试有什么前提条件,需要做什么预处理,任务失败应该报警,向谁报警?以什么方式报警?什么情况下停止报警?任务运行得慢要不要报警? 怎么知道比以前慢?多慢该报警?不同的任务能否区别对待?等等
所有这些方面都决定了用户的实际体验和系统的好用/易用程度,同时,对系统的整体流程框架设计也可能会带来一些影响。
开源的工作流调度系统在这些方面做得通常都相对简单,这也是很多公司二次开发改造的重点方向。
有人的地方就有江湖。任务多了,势必就需要进行资源和权限管控。
最直接的问题就是,如果有很多任务都满足运行条件,资源有限的情况下,先跑哪个?任务优先级如何定义和管理?
再退一步,你怎么知道哪些资源到了瓶颈?如果调度系统管理执行的任务类型很多,任务也可以在不同的机器或集群上运行,你如何判定哪些任务需要多少资源?哪些机器或集群资源不足?能否按照不同的条件区别管理,分类控制并发度或优先级?
最后,谁能编辑,运行,管理一个任务?用户角色怎么定义?和周边系统,比如Hadoop/Hive系统的权限管理体系如何对接配合?
这些方面的内容,多数的开源的工作流调度系统也做得并不完善,或者说很难做到通用,因为很多功能需要和周边系统深度配合才可能完成。
系统的运维能力:包括是否有系统自身状态指标的监控,是否有业务操作日志,执行流水等便于分析排查问题,系统维护,升级,上下线,能否快速完成,系统是否具备灰度更新能力等等。
工作流调度系统做为大数据开发平台的核心组件,牵扯的周边系统众多,自身的业务逻辑也很复杂,根据目标定位的不同,场景复杂度和侧重点的不同,市面上存在众多的开源方案。
但也正因为它的重要性和业务环境的高度复杂性,多数有开发能力的公司,还是会二次开发或者自研一套甚至多套系统来支撑自身的业务需求,我司也不例外。关于我司Jarvis调度系统的架构实现,功能设定和产品方案,后续再另写一篇文章详细阐述。
常按扫描下面的二维码,关注“大数据务虚杂谈”,务虚,我是认真的 ;)