当前位置: 首页 > 科技观察

亿级异构任务调度框架设计与实践

时间:2023-03-12 13:00:55 科技观察

1.背景阿里云日志服务是一个云原生的可观察分析平台。提供一站式的数据采集、处理、查询分析、可视化、告警、消费和投放功能。全面提升用户在研发、运维、运营、安全等场景的数字化能力。日志服务平台作为一个可观测平台,提供数据导入、数据处理、聚合处理、告警、智能巡检、导出等功能。这些功能在日志服务中被称为任务,具有大规模的应用。下面主要介绍针对这些任务的调度框架的设计与实践。本篇介绍主要分为四个部分:任务调度背景可观测平台亿级任务调度框架设计任务调度框架在日志服务中的大规模应用前景任务调度背景通用调度调度是一个非常普遍的技术计算机,从单机到分布式再到大数据系统,调度无处不在。这里试图总结一些调度的共同特征。操作系统:从单机操作系统Linux的角度来看,内核通过时间片来控制进程在处理器上的执行时间,进程的优先级与时间片挂钩。简单的说,进程在单个CPU或者一个CPU上的执行由调度器控制;K8s被称为分布式时代的操作系统。Pod创建后,K8s控制平面调度器对节点进行打分排序,最终选择合适的Node运行Pod。大数据分析系统:从最早的MapReduce使用公平调度器支持作业优先级和抢占,到SQL计算引擎Presto通过Coordinator调度器将执行计划中的任务分配给合适的worker执行,再到Spark通过DAGSchedulerIntoStage拆分,TaskScheduler最终将Stage对应的TaskSet调度到合适的Worker上执行。任务调度框架:数据处理中常见的ETL处理任务和定时任务,这些任务具有多模式的特点:定时执行、连续运行、一次性执行等。在任务执行过程中,需要解决任务安排和状态一致性问题被考虑。这是调度的简单抽象。如上图所示,调度负责将不同的任务分配给不同的资源执行。任务可以是进程、pod和子任务;resources是专门执行task任务的资源,可以是processingserver,threadpool,node,machine。通过这个抽象,我们可以看出调度在系统中的位置。调度涵盖的范围很广。本文主要关注任务调度框架的设计与实践。这里我们先通过一些例子来了解一下任务调度的一些特点。下面的任务主要分为两种:定时任务和依赖任务。扩张。任务调度和定时任务的定时可以理解为每个任务都有时间顺序,必须在特定的时间点执行。比如每小时监控一次日志。00点的监控任务需要先执行,01点的监控任务需要在01点准时执行;类似的,类似的时序场景还有dashboard订阅、时序计算等。除了依赖任务的定时执行,还有另外一种编排形式,比如顺序依赖。顺序执行的任务之间存在依赖关系,也称为流水线方法。还有一种更常见的编排形式,拓扑依赖,也称为DAG,例如Task2/Task3需要等到Task1执行完后才能执行,Task5需要等到Task3/Task4执行完后才能执行可以执行。任务调度特点任务调度需要在执行过程中尽可能均匀地将任务分配到合适的机器或执行器上,例如根据执行器当前的负载,必须根据任务本身的特点进行调度执行;in执行器也有可能在执行过程中崩溃退出。这时候就需要将task迁移到其他executor上。整个调度过程需要考虑到调度策略、FailOver、任务迁移等,下面我们来看一个任务调度的简单应用。任务调度应用:一个日志的奇遇上图中的原始日志是一个Nginx的访问日志,包括IP、时间、Method、URL、UserAgent等信息,这样的原始日志不利于我们的分析。例如,我们要统计最高访问量Top10的URL是通过这样的命令处理的:catnginx_access.log|awk'{print$7}'|sort|uniq-c|sort-rn|头-10|more不管命令的复杂程度和原始日志的数据量大小,即使需求稍有变化,命令也需要大量改动,不利于维护。正确的日志分析方式一定是使用分布式日志平台进行日志分析。原始日志包含很多“信息”,但是提取这些信息需要一系列的过程。首先是数据收集。需要通过Agent将分布在各台机器上的数据集中收集到日志平台??。日志收集后,需要清理。例如对Nginx访问日志进行定时抽取,抽取时间、方式、URL等重要信息。它被存储为一个字段并被索引。通过索引,我们可以使用类似SQL的分析语法来分析日志。比如查看访问的前10个URL,用SQL表达会很简洁明了:selecturl,count(1)ascntfromloggroupbyurlorderbycntdesclimit10只要业务系统是在服务中,日志会不断产生。我们可以通过查看流式日志来达到检测系统异常的目的。当异常发生时,我们可以通过告警通知系统运维人员。通用流程提取从这样的日志分析系统中,可以提取一些通用流程。这些一般流程可以概括为数据采集、数据处理、数据监控和数据导出。除了日志,系统还有Trace数据和Metric数据,它们是可观察性系统的三大支柱。这个过程同样适用于可观察性服务平台。下面我们来看一个典型的可观察性服务平台的流程组成。典型的可观察服务平台数据流数据摄入:在可观察服务平台上,首先需要扩展数据源。数据源可能包括各种日志、消息队列Kafka、存储OSS、云监控数据等,也可能包括各种数据库数据,通过丰富的数据源的摄入,可以对系统有一个全面的观察。数据处理:数据被引入平台后,需要对数据进行清洗和处理。我们将这个过程统称为数据处理。数据处理可以理解为数据的各种变换和丰富等。聚合处理支持数据的时序上卷操作,比如每天计算过去一天的汇总数据,提供更高信息密度的数据。数据监控:可观测性数据本身反映了系统的运行状态。系统通过公开每个组件的特定指标来公开组件的健康状况。可以通过智能巡检算法监测异常指标,比如QPS或者Latency有突然升高或者降低,当出现异常时,可以通过告警的方式通知相关运维人员。可以根据指标进行各种运维或操作。每天定时给群发市场也是一个场景需要。数据导出:可观测数据的价值往往会随着时间的推移而衰减,因此可以将长期的日志数据导出到其他平台进行存档。从以上四个流程,我们可以抽象出各种任务,分别负责摄取、处理、检测等。例如,数据处理是一项常驻任务,需要连续处理数据流。仪表板订阅是一项计划任务。有必要定期将仪表板发送到电子邮件或工作组。接下来,我们将介绍各种任务的调度框架。可观察平台的亿级任务调度框架设计可观察平台任务特点根据上面对可观察平台任务的介绍,一个典型的可观察平台任务的特点可以概括为:业务复杂,任务类型多:数据摄入,只有单一数据摄入过程可能涉及数十个或数百个数据源。用户多、任务多:由于是云上业务,每个客户都有大量的任务创建需求。SLA要求高:服务可用性要求高,后台服务升级和迁移不能影响用户现有任务的运行。多租户:云上的业务客户不能直接相互影响。Observable平台任务调度设计目标根据平台任务的特点,对于它的调度框架,我们需要实现上图的目标来支持异构任务:告警、仪表盘订阅、数据处理、聚合处理各任务的特点不同的是,比如告警是定时任务,数据处理是常驻任务,dashboard订阅预览是一次性任务。海量任务调度:对于单个报警任务,如果每分钟执行一次,那么一天会有1440次调度。这个数乘以用户数再乘以任务数就是一个海量的任务调度;我们需要达到的目标是任务数量的增加不会影响机器的性能,特别是要实现水平伸缩,任务数量的增加或者调度数量只需要线性增加机器即可。高可用:作为云上的业务,需要做到后台服务升级或重启,甚至宕机,不影响用户任务执行。用户层和后台服务层都需要具备监控任务执行的能力。运维简单高效:对于后台服务,需要提供可视化的运维仪表盘,可以直观的展示服务问题;同时,还需要为服务配置告警,在服务升级和发布的过程中尽可能实现无人值守。多租户:云上的环境是天然的多租户场景。每个租户的资源必须严格隔离,它们之间不能有资源或性能依赖。可扩展性:面对客户的新需求,未来需要支持更多的任务类型。比如已经有MySQL和SqlServer的导入任务,以后还会需要更多其他数据库的导入。这种情况下,我们需要做的是不修改任务调度框架,只需要修改插件即可完成。API-based:除了上述需求,我们还需要实现基于API的任务管控。对于云用户,国外很多客户使用API??和Terraform来管理和控制云资源,因此需要基于API的任务管理。Observable平台任务调度框架概述基于上述调度设计目标,我们设计了一个Observable任务调度框架,如上图所示,下面将从下到上进行介绍。存储层:主要包括任务元数据存储和任务运行时状态及快照存储。任务的元数据主要包括任务类型、任务配置、任务调度信息,这些信息都存储在关系数据库中;任务的运行状态和快照存储在分布式文件系统中。服务层:提供任务调度的核心功能,主要包括任务调度和任务执行,对应前面提到的任务调度和任务执行模块。任务调度主要调度三种类型的任务,包括常驻任务、定时任务和按需任务。任务执行支持多种执行引擎,包括presto、restfulinterface、K8s引擎和内部自研的ETL2.0系统。业务层:业务层包括用户可在控制台直接使用的功能,包括告警监控、数据处理、索引重建、仪表盘订阅、聚合处理、各类数据源导入、智能巡检任务、日志投递等。层:接入层使用Nginx和CGI对外提供服务,具有高可用和区域部署的特点。API/SDK/Terraform/Console:在用户端,可以使用控制台管理各种任务,提供针对不同任务的自定义接口和监控,也可以使用API??、SDK、Terraform来添加任务。删除、修改和检查。任务可视化:在控制台中,我们提供了任务执行和任务监控的可视化。通过控制台,用户可以看到任务的执行状态和执行历史,也可以启用内置告警来监控任务。任务调度框架的设计要点接下来从几个方面介绍任务调度框架的设计要点,主要包括以下几个方面:异构任务模型抽象调度服务框架大规模任务支持服务高可用设计稳定性构建任务模型抽象接下来看任务模型的抽象:对于需要定时执行的任务,比如告警监控、dashboard订阅、聚合处理等,抽象为定时任务,支持定时和Cron表达式设置。对于数据处理、重建索引、数据导入等需要持续运行的任务,抽象为常驻任务。此类任务通常只需要运行一次,并且可能有也可能没有结束状态。对于数据处理预览、仪表盘订阅预览等功能,需要创建一个任务,当用户点击时执行,执行后退出,不保存任务状态。此类任务被抽象为DryRun类型,即按要求完成的任务。调度服务框架服务基础框架采用Master-Worker架构。Master负责任务分配和Worker控制。Master将数据抽象成若干个Partition,然后将这些Partition分配给不同的Worker,实现任务的分而治之。在执行过程中,Master还可以根据Worker的负载情况,对Partition进行动态迁移。同时,在Worker的重启和升级过程中,Master也会在Partition中移出移入;任务的调度主要在Worker层实现,每个Worker负责PullPartitions对应的任务,然后通过JobLoader加载任务。注意:这里只会加载当前Worker的Partition对应的任务列表,然后由Scheduler对任务进行调度和安排,会涉及常驻任务、定时任务和按需任务。对于任务调度,Scheduler将调度好的任务发送给JobExecutor执行。在执行过程中,JobExecutor需要持久化,将任务的状态实时保存到RedoLog中。在下次Worker升级重启的过程中,需要从RedoLog中获取。加载任务状态,保证任务状态的准确性。大规模任务支持通过任务服务框架的介绍,我们知道Partitions是Master和Worker之间的桥梁,也是分治大规模任务的媒介。如上图所示,假设有N个task,将N个task按照一定的hash算法映射到对应的Partition,因为Worker关联的是特定的Partition,这样Worker就可以关联到task,比如tasksj1和j2对应的分区是p1,p1对应的Worker是worker1,这样j1和j2就可以在worker1上执行。需要说明的是:Worker和Partition的对应关系不是静态的,是动态的映射,当Worker重启或者负载高时,对应的Partition会迁移到其他Worker上,所以Worker需要实现分区进出操作。当任务数量增加时,因为有一个中间层叫做Partition,所以只需要增加Worker的数量就可以满足任务增长的需要,达到水平扩展的目的。添加新的Worker后,可以共享更多的Partition服务。服务的高可用设计主要是服务的可用时间。作为后台服务,肯定有重启升级的需求。高可用场景主要涉及Partition迁移的处理。在WorkerRestart中,当Worker负载较高,或者Worker出现异常时,就会有Partition迁移的需求。在Partition迁移过程中,任务也需要进行迁移。任务迁移涉及状态保存,类似于进程在CPU上的路由文件切换。.对于任务切换,我们使用RedoLog方法来保存任务的状态。一个任务可以分为多个阶段,对应任务执行的状态机。每个阶段执行时,更新内存Checkpoint,执行RedoLog。Update,RedoLog被持久化到前面提到的分布式文件系统,使用高性能的Append方式进行顺序写入,Partition迁移到新的Worker后,新的Worker可以加载RedoLog完成任务状态的恢复。这就涉及到一个优化。如果RedoLog一直写在Append方法中,势必会导致RedoLog越来越膨胀,同时也会导致Worker加载Partition的速度变慢。针对这种情况,我们使用Snapshot的方式将过去的MergeRedoLog转换一段时间,这样只有在加载Partition的时候,加载Snapshot和Snaphost之后的RedoLog,可以减少文件读取的次数和开销,并且提高加载速度。稳定性建设稳定性建设主要涉及以下几个方面:发布流程:从编译到发布,全程web端白屏运行,模板发布,每个版本可追溯回滚。支持集群粒度和任务类型粒度的灰度控制,发布时可以先进行小规模验证,再全面发布。运维流程:提供内部运维API,Web端操作,修复处理异常作业。减少人工干预运维。On-Call:在服务内部,我们开发了内部检查功能,可以发现异常任务。比如有些任务启动时间过长或者停止时间过长,都会打印异常日志,可以进行跟踪和监控。通过异常日志,利用日志服务告警进行监控,出现问题可以及时通知运维人员。任务监控:用户端:在控制台中,我们为各种任务提供了监控仪表板和内置告警配置。服务端:在后台可以看到集群粒度任务的运行状态,方便后台运维人员监控服务。同时,任务的执行状态和历史记录会存储在特定的日志库中,以便在出现问题时可以进行追溯和诊断。下面是一些服务端的dashboard的例子,展示了告警的一些执行状态。下面是用户侧任务监控状态和告警的展示。日志服务的大规模应用,任务调度已经大规模应用。以下是某地区单个集群任务的运行情况。由于告警定时执行,使用场景广泛,每天的调度量已经达到千万级,聚合处理在Rollingup场景应用非常高,已经达到了百万级别;对于数据处理任务,由于是常驻任务,调度频率低于类似告警类调度任务。下面以一个聚合处理为例来看下任务调度场景。典型任务:聚合处理聚合处理就是周期性地对一段时间的数据进行聚合和查询,然后将结果存储到另一个库中,从而提取信息密度高的信息。与原始数据相比,具有降维、低存储、高信息密度的特点。适用于时序分析和全局聚合场景。以下是聚合处理的执行状态示例。可以看到每个时间间隔的执行情况,包括处理的行数、处理的数据量、处理结果。对于执行失败的任务,您也可以手动重试。聚合处理并不是这么简单的定时执行的逻辑,过程中需要处理超时、失败、延时等场景。接下来,对每个场景进行简要介绍。调度场景一:实例延迟执行无论实例是否延迟,实例的调度时间都是根据调度规则预先生成的。虽然前面的实例延迟可能会导致后面的实例延迟执行,但是通过跟上执行进度,可以逐渐减少延迟,直到恢复按时运行。调度场景二:从某个历史时间点开始执行聚合处理作业。在当前时间点创建一个聚合处理作业后,按照调度规则处理历史数据,从调度的开始时间创建一个补操作实例,补操作实例依次执行,直到赶上数据处理进度,然后按照预定的计划执行新的实例。调度场景三:固定时间内执行聚合处理作业如果需要对指定时间段的日志进行调度,可以设置调度的时间范围。如果设置了计划结束时间,则在上一个实例(计划时间小于计划结束时间)执行完毕后,不会再产生新的实例。调度场景四:修改调度配置对生成实例的影响修改调度配置后,下一个实例会按照新的配置生成。一般建议同步修改SQL时间窗口、调度频率等配置,使实例间的SQL时间区间连续。调度场景五:重试失败的实例自动重试。如果实例执行失败(如权限不足、源数据库不存在、目标数据库不存在、SQL语法无效),系统支持自动重试和手动重试。当重试次数超过当您配置的最大重试次数或重试时间超过您配置的最大运行时间时,重试结束,实例状态置为失败,系统继续执行下一个实例。展望动态任务类型:添加对动态任务类型的支持,例如具有任务间依赖性的更复杂的任务调度。多租户优化:目前,任务使用简单的配额限制。未来,多租户QoS将进一步细化以支持更大的Quota设置。API优化完善:目前任务类型也在快速更新中,任务API的迭代速度还有一定的差距。需要加强任务API的优化,在不对API进行少量修改或更新的情况下,达到增加任务类型的目的。