阿里云日志服务作为云原生可观测与分析平台。提供了一站式的数据采集、加工、查询分析、可视化、告警、消费与投递等功能。全面提升用户的研发、运维、运营、安全场景的数字化能力。
日志服务平台作为可观测性平台提供了数据导入、数据加工、聚集加工、告警、智能巡检、导出等功能,这些功能在日志服务被称为任务,并且具有大规模的应用,接下来主要介绍下这些任务的调度框架的设计与实践。
本次介绍主要分为四个部分:
调度在计算机里面是一个非常常见的技术,从单机到分布式再到大数据系统,调度的身影无处不在。这里尝试总结出调度的一些共同特征。
这里简单的对调度做一个抽象,如上图所示,调度负责将不同的Task分配到不同的Resource上执行,Task可以是进程、Pod、子任务;Resource为具体执行Task任务的资源,可以是处理器、线程池、节点、机器。通过这个抽象,可以看出调度在系统中的位置。
调度的覆盖面很广,本文主要集中在任务调度框架的设计与实践,这里先通过一些例子来看下任务调度的一些特点,以下主要讲任务分为定时类的任务和依赖类的任务两种来展开。
定时执行可以理解为每个任务之间有时间先后顺序,并且要在特定的时间点执行,比如每隔1小时对日志进行监控,00点的监控任务需要首先执行,01点的监控任务需要在01点准时执行;同样,类似的定时场景,还有仪表盘订阅、定时计算等。
除了定时执行,还有另外一种编排形式,比如顺序依赖,各个任务之间有先后执行的依赖,也叫Pipeline方式,还有一种比较常见的编排形式,拓扑依赖,也称为DAG,比如Task2/Task3需要等到Task1执行完成才可以执行,Task5需要等到Task3/Task4执行完才可以执行。
任务调度在执行的过程中需要尽可能均衡的将任务分派到合适的机器或者执行器上去执行,比如要根据执行器的当前负载情况,要根据任务自身的特征进行分派执行;在执行器执行的过程中也可能会崩溃,退出,这时候需要将任务迁移到其他的执行器中。整个调度过程需要考虑到调度策略、FailOver、任务迁移等。接下来来看下任务调度的一个简单应用。
上图中原始日志为一条Nginx访问日志,其中包括IP、时间、Method、URL、UserAgent等信息,这样一些原始日志并不利于我们进行分析,比如我们想统计访问最高的Top 10 URL,通过命令处理是这样的:
cat nginx_access.log |awk '{print $7}'| sort|uniq -c| sort -rn| head -10 | more
抛开命令的复杂性和原始日志的数据量不谈,即使需求稍微变化,命令就需要大量的改动,非常不利于维护,对日志进行分析的正确方式必然是使用分布式日志平台进行日志分析,原始日志蕴含着大量“信息”,但是这些信息的提取是需要一系列的流程。
首先是数据采集、需要通过Agent对分布在各个机器上的数据进行集中采集到日志平台,日志采集上来后需要进行清洗,比如对于Nginx访问日志使用正则提取,将时间、Method、URL等重要信息提取出来作为字段进行存储并进行索引构建,通过索引,我们可以使用类SQL的分析语法对日志进行分析、例如查看访问的Top 10 URL,用SQL来表达就会非常简洁清晰:
select url, count(1) as cnt from log group by url order by cnt desc limit 10
业务系统只要在服务,日志就会不断产生,可以通过对流式的日志进行巡检,来达到系统异常的检测目的,当异常发生时,我们可以通过告警通知到系统运维人员。
从这样一个日志分析系统可以提取出一些通用的流程,这些通用的流程可以概括为数据摄入、数据处理、数据监测、数据导出。
除了日志,系统还有Trace数据、Metric数据,它们是可观测性系统的三大支柱。这个流程也适用于可观测性服务平台,接下来来看下一个典型的可观测服务平台的流程构成。
从以上四个过程我们可以抽象出各类任务,分别负责摄入、处理、检测等,比如数据加工是一种常驻任务,需要持续对数据流进行处理,仪表盘订阅是一种定时任务,需要定时发出仪表盘到邮件或者工作群中。接下来将要介绍对各类任务的调度框架。
根据上面对可观测平台任务的介绍,可以总结一个典型的可观测平台的任务的特点:
根据平台任务的特点,对于其调度框架,我们需要达到上图中的目标
基于上述的调度设计目标,我们设计了可观测性任务调度框架,如上图所示,下面从下到上来介绍。
接下来从几方面对任务调度框的设计要点进行介绍,主要包括以下几方面来介绍:
接下来看下任务模型的抽象:
服务基础框架使用了Master-Worker架构,Master负责任务的分派和Worker的管控,Master将数据抽象为若干Partitions,然后将这些Partitions分派给不同的Worker,实现了对任务的分而治之,在Worker执行的过程中Master还也可以根据Worker的负载进行Partitions的动态迁移,同时在Worker重启升级过程中,Master也会对Partition进行移出和移入;
任务的调度主要在Worker层来实现,每个Worker负责拉取对应Partitions的任务,然后通过JobLoader对任务进行加载,注意:这里只会加载当前Worker对应Partitions的任务列表,然后Scheduler对任务进行调度的编排,这里会涉及常驻任务、定时任务、按需任务的调度,Scheduler将编排好的任务发送到JobExecutor进行执行,JobExecutor在执行的过程中需要实时对任务的状态进行持久化保存到RedoLog中,在下次Worker升级重新启动的过程中,需要从RedoLog中加载任务的状态,从而保证任务状态的准确性。
通过任务服务框架的介绍,我们知道Partitions是Master与Worker沟通的桥梁,也是对大规模任务进行分而治之的介质。如上图所示,假设有N个任务,按照一定的哈希算法将N个任务映射到对应的Partition,因为Worker关联特定的Partition,这样Worker就可以跟任务关联起来,比如任务j1、j2对应的partition是p1,而p1对应的Worker是worker1,这样j1、j2就可以在worker1上执行。需要说明的如下:
服务的高可用主要是服务的可用性时间,作为后台服务肯定有重启、升级的需求,高可用场景主要涉及到Partition迁移的处理,在Worker重启、Worker负载较高时、Worker异常时,都会有Partition迁移的需求,在Partition迁移的过程中,任务也需要进行迁移,任务的迁移就涉及到状态的保留,类似CPU上进程的航线文切换。
对于任务的切换,我们使用了RedoLog的方式来保存任务的状态,一个任务可以被分为多个阶段,对应任务执行的状态机,在每个阶段执行时都对其进行内存Checkpoint的更新和RedoLog的更新,RedoLog是持久化到之前提到的分布式文件系统中,使用高性能的Append的方式进行顺序写入,在Partition迁移到新的Worker后,新的Worker在对RedoLog进行加载,就可以完成任务状态的恢复。
这里涉及一个优化,RedoLog如果一直使用Append的方式进行写入,势必会造成RedoLog越来越膨胀,也会造成Worker加载Partition时速度变慢,对于这种情况,我们使用了Snapshot的方式,将过去一段时间的RedoLog进行合并,这样只需要在加载Partition时,加载Snapshot和Snaphost之后的RedoLog就可以减少文件读取的次数和开销,提高加载速度。
稳定性建设主要涉及以下几方面内容:
下面是一些服务侧的部分大盘示例,展示的是告警的一些执行状态。
下面是用户侧的任务监控状态和告警的展示。
在日志服务,任务的调度已经有了大规模的应用,下面是某地域单集群的任务的运行状态,因为告警是定时执行且使用场景广泛,其单日调度次数达到了千万级别,聚集加工在Rolling up场景中有很高场景的应用,也达到了百万级别;对于数据加工任务因为是常驻任务,调度频率低于类似告警类的定时任务。
接下来以一个聚集加工为例来看下任务的调度场景。
聚集加工是通过定时对一段时间的数据进行聚集查询,然后将结果存入到另一个库中,从而将高信息密度的信息进行提取,相对于原始数据具有降维、低存储、高信息密度的特点。适合于定时分析、全局聚合的场景。
这里是一个聚集加工的执行状态示例,可以看到每个时间区间的执行情况,包括处理行数、处理数据量、处理结果情况,对于执行失败的任务,还可以进行手动重试。
对于聚集加工并非定时执行这么简单的逻辑,在过程中需要处理超时、失败、延迟等场景,接下来对每种场景进行一个简单介绍。
无论实例是否延迟执行,实例的调度时间都是根据调度规则预先生成的。虽然前面的实例发生延迟时,可能导致后面的实例也延迟执行,但通过追赶执行进度,可逐渐减少延迟,直到恢复准时运行。
在当前时间点创建聚集加工作业后,按照调度规则对历史数据进行处理,从调度的开始时间创建补运行的实例,补运行的实例依次执行直到追上数据处理进度后,再按照预定计划执行新实例。
如果需要对指定时间段的日志做调度,则可设置调度的时间范围。如果设置了调度的结束时间,则最后一个实例(调度时间小于调度结束时间)执行完成后,不再产生新的实例。
修改调度配置后,下一个实例按照新配置生成。一般建议同步修改SQL时间窗口、调度频率等配置,使得实例之间的SQL时间范围可以连续。
原文链接
本文为阿里云原创内容,未经允许不得转载。