https://www.infoq.cn/article/xTvBg1_9iUL0z5Pjf0Os
本文是苏宁大数据离线任务开发调度平台实践系列文章之上篇,详解苏宁的任务调度模块。
目 录
1. 绪言 1
2. 设计目标与主要功能 2
3. 专业术语 3
4. 调度架构设计 5
5. 服务重启和任务状态恢复 6
5.1 Master Active 组合服务 7
5.2 Master HA 高可用设计 7
5.3 Recover 任务状态恢复设计 7
6.Web API 接口服务 9
7. 后续 10
1. 绪言
在上一篇文章《苏宁大数据离线任务开发调度平台实践》中,从用户交互功能、任务调度、任务执行、任务运维和对外服务等几方面,宏观层面进行了理论和实践的概述。
产品的用户功能重点需要把握用户实际的任务开发运维需求,合理的规划设计产品功能,在使用和运维上便于用户操作,降低用户的开发使用成本。简单的说就是主要保证用户任务、任务流等关键元数据的配置信息的准确性,以及任务状态的查询和干预能力,技术上实现不存在难点,在此不再详细说明。
任务执行模块侧重于任务被领取后,如何根据任务类型选择不同的执行器(Executer)提交任务执行,并将任务的执行状态及时准确的返回,由任务调度服务根据返回状态做相应的下一步处理,除此以外还涉及到任务资源加载、任务配置解析与转换、自身健康状态检查与汇报、worker 进程与任务子进程通信、任务隔离、对外接口服务等,这块将在后面一节再跟大家详细分享。
任务运维模块主要关注平台的自身稳定性、健壮性等各个指标的监控与预警、平台任务执行异常的监控、任务运行诊断分析、动态扩缩容和应急降级等方面,涉及到的内容也很多,后续章节会陆续跟大家分享。
今天我们重点详细阐述苏宁大数据离线任务调度开发平台的核心模块—任务调度模块的架构设计以及开发实践过程中的关键功能点。
2. 设计目标与主要功能
调度模块的核心目标要保证任务能够按照用户配置的调度时间、依赖关系准实时调度和执行,同时也允许用户根据实际需要随时启动和停止任务调度,调整任务执行计划。所谓准时实调度,指的是调度模块会按照各个上线的任务流的调度时间生成调度执行计划,当触发时间到了,平台会按照调度执行计划精确的生成任务流实例和任务实例。但是在任务执行上,并不保证准实时的分配机器执行。实际上平台以整体资源使用情况为最高原则,并按照一定的限流策略控制任务的执行,比如:任务优先级、任务组并发度、平台任务并发数、任务特定执行时间等因素。在保证平台资源允许的情况下,尽量按时执行任务。为了保障任务的实时性,必须保障任务资源的可用性和计划可控性。
调度模块的主要核心服务功能包括以下几点:
服务重启和任务状态恢复功能
在调度服务重启、主备切换后,系统状态以及任务运行状态能否准确的恢复。比如,主节点崩溃或维护期间,发生状态变更的任务在主节点恢复以后,能否正确更新状态等等。
Web API 接口服务
用户通过 Web 控制后台管理作业,而 Web 控制后台与 Master 服务器之间的交互透过 Rest 服务来执行,Rest 服务也可以给 Web 控制后台以外的其它系统提供服务(用于支持外部系统和调度系统的对接)。另外为了便于监控和调查分析调度异常和问题,提供 Master 内存关键信息的查询和人工干预的接口能力。
数据信息缓存服务
缓存上线任务流、任务、事件、系统配置、服务器的关键元数据信息,这些信息一般在任务流上线后不会经常发生变更,没必要实时从数据库中读取。并对外提供这些元数据信息的同步接口服务,保证缓存信息与数据库的一致性。
缓存任务流实例、任务实例、事件实例等中间状态信息,同时持久化到数据库中。便于在任务状态切换、任务依赖执行快速找到对应的运行中的关键数据。并在任务实例数上升一定量级以后可以快速的从内存中缓存的中间状态数据完成依赖检查和触发执行逻辑,降低对数据库因为频繁访问造成的压力。
任务调度服务
主要负责上线任务流的配置检查、生成任务流执行计划、按照执行计划生成任务流与任务实例,生成任务实例状态机和节点之间的依赖触发关系。除了这些系统调用主要功能外,还提供人工干预任务执行的服务功能,比如:任务流上下线、任务补数据、任务重跑、任务杀死、失败重试等
任务状态机管理
任务流按照调度服务的执行计划会在每个调度周期内生成需要执行的任务流实例和任务实例信息,这些实例在调度过程中存在多种临时状态,并具备一定的生命周期。状态切换的时候触发一定的业务逻辑,比如:任务实例由新建状态切换到待分配状态,由待分配状态切换到已分配状态,由执行中状态切换到执行结束状态都可能需要完成一定的处理。这里我们采用了状态机的管理机制来确保任务执行状态的持续性和完整性。
任务状态分析服务
任务实例在调度过程中存在多种临时状态的切换,每次状态切换必须成功才能保证状态变化的持续性和完整性,从而保证任务实例从生成到结束的完整生命周期。如果状态切换过程中发生意外或者长时间停滞在某个状态不变,可能会导致调度异常和用户使用恐慌,为了准确及时的分析任务实例的状态停滞原因,需要在任务状态生成和切换的时候进行检查校验,把不能切换的原因及时记录,便于分析问题。
任务状态发布服务
平台上的任务处理的是数据,数据处理的及时性和准确性对业务系统是有极大的影响。而平台的任务运行状态往往只会记录在本平台数据库中,外部系统无法感知。在很多场景下,业务系统需要根据任务的执行状态来执行自己的特定业务逻辑,通过传统的任务状态查询接口又存在延迟性和性能问题,比如:任务状态的变更,执行时间长短会因为多种因素而变得不确定;多个外部系统调用平台接口可能会导致平台自身压力的不确定性。可以在任务实例生成和状态切换的时候,将任务实例状态按照用户的配置要求,及时的发布出去,业务系统根据需要进行订阅,确保任务状态更新的及时性,又降低对平台的侵入和压力。
任务分配与流控服务
主要负责满足执行条件的任务实例的分配,以及在任务执行高峰、资源紧张的情况下如何智能有效的进行相应的流控。在以整体资源使用情况为最高原则,并按照一定的限流策略控制任务的执行,比如:任务优先级、任务组并发度、平台任务并发数、任务特定执行时间等因素。在保证平台资源允许的情况下,尽量按时执行任务。为了保障任务的实时性,必须保障任务资源的可用性和计划可控性。
事件触发服务
主要解决复杂业务场景里,跨任务流依赖、跨系统平台依赖的调度执行问题。比如:平台内部多个系统多个任务流之间的依赖调度,以及外部业务系统在某种条件下需要通知调度平台执行自己的任务。另外需要解决各种频率之间的依赖关系,比如:天依赖天、天依赖小时、周月依赖天等.
主机健康监控服务
负责管理可以执行任务的机器资源, 并根据各机器的健康度合理的分配任务。主要包括:worker 机器的发现与管理、worker 机器的健康度评估、worker 检活、主机黑白名单(加入黑名单的机器不能领取和执行任务)等
异步更新服务
平台中存在大量的持久化操作,比如:任务实例的生成与状态更新、事件的触发实例生成、任务流的启停状态、任务运行状态原因分析等。有些持久化操作需要伴随业务逻辑同步更新,确保操作的事务完整性,比如:任务流上下线、任务实例的状态切换,必须保证内存和数据库一致性。有些操作则不要求高度一致性和实时性,甚至有些数据的更新错误或者丢失也可以忽略不计。同步更新在确保事务、数据的完整和一致性外,带来了平台性能的一定下降。而异步更新服务可以提高平台的运行性能和并发能力,这些低有求的操作和数据同步服务就可以采用异步更新服务来完成。比如:任务运行状态停滞原因分析、任务状态的对外发布等
3. 专业术语
苏宁大数据离线任务开发调度平台具有和业内同款平台产品的共性,也具备自己的特殊性和专业性。在理解和使用我们的平台之前,需要了解平台常见的专业术语,以免造成理解和使用上的分歧。
任务流实例的中间运行状态,主要包括:待调度、执行中、执行失败、执行成功。
任务实例的中间运行状态,主要包括:待调度、待分配、已分配、已领取、参数检查错误、资源准备失败、执行中、杀死、执行失败、失败重试、执行成功、忽略失败。
4. 调度架构设计
从系统架构的角度出发,模块化的设计有利于功能隔离,降低组件耦合度和单个组件的复杂度,提升系统的可拓展性,一定程度上有利于提升系统稳定性,但带来的问题是开发调试会更加困难,从这个角度来说又不利于稳定性的改进。所以各个功能模块拆不拆,怎么拆往往是需要权衡考虑的。
平台采用常见的主从式架构,按照功能模块划分清晰,职责单一而不紧耦合,避免繁重复杂的业务耦合设计。调度模块在系统架构上分为 web 接口服务、重启恢复服务、数据缓存服务、任务状态发布服务、事件触发服务、异步更新服务、任务调度服务、任务状态机管理、任务分配服务、主机健康监控服务以及任务实例状态监听服务等十几个主要服务功能。每个服务模块负责的功能清晰,互相耦合度低,具有良好的扩展性、稳定性和容错性。调度的整体架构设计如下图所示。
调度模块涉及到多种功能服务,这些功能服务内部涉及到大量复杂的、交互的事件处理、状态转换,同时,这些事件调度和状态转换又对实时性和效率提出了极高的要求。可以想见,没有一个规整的、通用型良好的调度器,平台代码无论是对读者,还是对开发者,都将变成一场灾难,同时平台的运行效率也会变得无法忍受。统一的、设计良好的、通用的和共用的调度器,对于调度模块不同组件的开发者来说是一种解脱,大大降低了平台在事件调度、状态转换的底层出错的可能性,提高了代码稳定性和可读性。
如何组装、如何进行有效的接口调用来支撑平台百万级的任务高效稳定的执行。在组装设计上需要慎重选型。一般多服务调用分为函数调用和事件驱动两种模式。
相比于基于函数调用的编程模型,这种编程方式具有异步、并发等特点,更加高效,因此更加适合大型分布式系统。调度模块的十几个服务之间的大部分服务调用也基本是基于事件驱动的编程模型进行设计。开发实践过程中,Hadoop 的核心调度器 AsyncDispatcher 的设计和实现同 Hadoop 状态机一样,这个通用调度器设计得十分通用,完美可扩展可重用,我们在自己的项目中完全可以使用 Hadoop 的调度器实现我们自己的事件调度逻辑。
5. 服务重启和任务状态恢复
该服务主要是将调度模块的所有服务组件进行统一的注册和管理,并按照平台的业务逻辑顺序进行顺序初始化和启动。并通过 HAService 服务往 ZK 抢注 Master 的服务器节点目录,来完成主备 Master 的状态切换。通过 RecoverService 服务完成 从数据库中同步任务流、任务、事件等元数据信息和任务实例、事件实例等实例信息的内存恢复操作。根据任务实例的数据库和 zk 中保存的状态进行任务状态机的创建和后续状态的持续触发操作。
5.1 Master Active 组合服务
如前文所述,调度模块包括了十几个核心功能服务,如何有效的管理和协同这些服务的起停顺序、服务之间的调度关系,我们在 Java 设计模式上采用了组合模式 (Composite),将这十几个服务按照调度的业务关系进行了组合包装。
Hadoop Yarn 的 CompositeService 提供了一个比较好的组合封装服务,包括服务的注册(添加和移出)、服务的初始化和启停操作,这些服务被顺序的保存在一个 List 对象中,各个服务会按照顺序进行逐个初始化和启停。调度模块的这十几个关键服务统一打包在 MasterActiveService 中。
5.2 Master HA 高可用设计
高可用性 H.A.(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性。HA 系统是目前企业防止核心计算机系统因故障停机的最有效手段。
在 HA 方面,按照准实时的设计目标,平台并没有打算做到秒级别的崩溃恢复速度,系统崩溃时,只要能在分钟级别范围内,重建系统状态,就基本能满足系统的设计目标需求。
所以其实高可用性设计的重点,关键在于重建的过程中,系统的状态能否准确的恢复。比如,主节点崩溃或维护期间,发生状态变更的任务在主节点恢复以后,能否正确更新状态等等。而双机热备份无缝切换,目前来看实现难度较大(太多流程需要考虑原子操作,数据同步和避免竞争冲突),实际需求也不强烈,通过监控,自动重启和双机冷备的方式来加快系统重建速度,基本也就足够了。
本平台在设计的时候采用了“主从方式”实现 HA,主要设计要点:
(1) 一个状态管理功能模块
实现一个 zkFailover,常驻在每一个 Master 服务节点内,每一个 failover 负责监听自己所在节点,利用 zk 进行状态标识。当需要进行状态切换时,由 zkFailover 实现状态切换,切换时需要注意防止 brain split 现象发生。
(2) 对外服务方式
除了 HAService 服务外,只能有一个 Master 节点可以托管和执行其他所有服务。另外一个节点只能启动 HAService 监听主节点的状态。只有主节点停止服务后,才能启动其他服务进行工作。
5.3 Recover 任务状态恢复设计
在调度服务重启、主备切换后,系统状态以及任务运行状态能否准确的恢复。比如,主节点崩溃或维护期间,发生状态变更的任务在主节点恢复以后,能否正确更新状态等等是一个任务调度平台的重要功能和考核指标。
Recover 不仅需要恢复各种实例信息的元数据信息和状态信息,确保每个任务实例状态切换的连续性、完整性和正确性,还要保证每个任务流内部各个节点之间按照依赖关系及时的触发和正确执行。在某些场景下, 还需要对因为调度服务停止期间遗漏的任务流和任务实例进行补偿。
第一步完成任务配置相关的元数据信息的恢复。
即从数据库中同步必要的元数据信息到调度内存中。元数据信息在数据库中不是存放了一份,为什么还要从数据库中同步一份到调度的内存中呢?在任务量很少的情况下每次读写数据库不会对数据库造成压力。但是在任务量上升,任务实例的生成量和状态切换的量成几何级上升,随着对数据库的读写 TPS 也会上升。这样一方面可能会造成数据库的压力偏大,另一方面如果数据库服务不稳定、网络抖动等外部因素而导致调度服务卡住。
在大多数情况下,任务流一旦上线后不会轻易发生变更。如果有部分变动,可以通过 Master 的 web 接口同步内存和数据库的配置信息。为了保证状态的一致统一,和任务相关的信息变更,无论是用户发起的作业配置修改,还是执行器反馈的作业状态变更,都会提交给 Master 节点同步写入到数据库。具体参考下图。
第二步完成实例信息和任务状态的恢复。
实例信息的恢复主要包括:任务流实例、任务实例、事件实例的状态恢复,已经结束的任务流实例信息不作为恢复的对象。这一步不仅仅的单纯同步实例的信息到调度内存里,更重要的是要恢复任务实例的状态,确保任务执行按照计划和依赖关系正确的执行下去。
任务流实例是按照任务流的执行计划不断生成的运行个体。当重启扫描数据中“未执行结束”的任务流实例时,可能会存在大量的实例个体,执行恢复的时候,智能根据“未执行结束”的任务流实例个数启动一定数量的线程,按照任务流实例进行切分,进行批量恢复。
第三步补偿丢失的任务实例批次
Master 调度重启或者服务器宕机等因素造成任务调度计划被打断,再次恢复后需要对服务终止期间的丢失的任务实例进行补偿,否则会造成某些任务执行计划被错过而没有得到调度执行,引发数据故障。
根据故障恢复的时间长短,结合每个频率的任务做出不同的补偿措施。下表是根据不同频率类型按照对应策略进行补偿。
对于一些复杂的业务场景的任务,也不是必须要把所有遗漏的批次都补偿完毕,可以适当补偿一些遗漏批次,其他遗漏批次在服务重启后人工补偿。如果把历史遗漏批次都补偿,可能会因为补偿的实例数过多而导致当前批次被延后执行。
6.Web API 接口服务
用户通过 Web 控制后台管理作业,而 Web 控制后台与 Master 服务器之间的交互透过 Rest 服务来执行,Rest 服务也可以给 Web 控制后台以外的其它系统提供服务(用于支持外部系统和调度系统的对接)。另外为了便于监控和调查分析调度异常和问题,提供 Master 内存关键信息的查询和人工干预的接口能力。
考虑到调度模块的代码部署不依赖外部容器,比如:Tomcat、JBoss 等,又要对外提供 Web 接口服务,因此在技术选型上需要注意这一点。目前市场上流行的 SpringBoot、内嵌 Jettty 等其他 Servlet 容器方案都可以解决这个问题。我们的平台使用的架构是 Jersey+Guice+Jetty+ Mybatis,jersey 作为 Rest 服务框架,Guice 作为 DI 框架,使用内嵌 Jetty 作为应用容器,Mybatis 负责数据库的持久化操作,并舍弃 web.xml 配置,这样使得开发和部署十分轻量和简单。
下图是调度模块里各个服务、容器、上下文之间的访问交互图。
Master 上下文承载了配置文件、注册服务的查找与发现、元数据和实例数据信息以及各个服务的调用转发器(Dispatcher)。其他服务组件通过 Master 上下文可以很方便的获取系统配置信息,调用其他组件接口。Guice 框架目前主要托管了数据库相关操作类以及 web 服务接口类,被托管的服务类以单例的形式保存。整个调度模块内嵌了 Jetty 容器,部署和启动了 WebServer 服务,提供外部与 Master 内部服务的交互入口。
7. 后续
上述文章讲述了调度模块的架构设计、恢复和 web 服务,后续文章会接着讲述调度服务的设计细节。调度服务是整个调度模块非常核心的服务组件,涉及到任务流上下线、任务状态机管理、任务重跑补数据等运维操作,限于篇幅和时间问题,本次的调度模块的分享先写这么多,后续会陆续对其他服务组件进行详细阐述,敬请期待。
微信公众号: 架构师日常笔记 欢迎关注!