ToB企服应用市场:ToB评测及商务社交产业平台

标题: 作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化 [打印本页]

作者: 美食家大橙子    时间: 前天 14:54
标题: 作业帮基于 Apache DolphinScheduler 3_0_0 的缺陷修复与优化
文|作业帮大数据团队(阮文俊、孙建业)
背 景

基于 Apache DolphinScheduler (以下简称DolphinScheduler)搭建的 UDA 任务调度平台有效支撑了公司的业务数据开发需求,处置惩罚着日均百万级别的任务量。
整个 UDA 的架构如下图所示,此中我们的引擎层紧张基于 DolphinScheduler 的 Master 和 Worker 服务搭建,充分利用了 Master 节点优秀的高并发调度能力、worker 节点的分组隔离能力。我们通过一层适配层对 DolphinScheduler 对外暴露的接口举行了封装和增强来适配我们的业务场景。

随着利用的深入,我们发现 DolphinScheduler3.0.0 版本中存在一些难以通过运维本领规避的题目,这些题目影响数据平台的稳定性,导致隔一段时间必要重启服务来使集群规复正常,并且核心组件对外暴露的可观测性指标非常有限,导致题目的排查定位过程非常繁琐。我们认为一个可以稳定运行的调度引擎应该具备以下能力

在这些能力上,开源版 3.0.0 的 Apache DolphinScheduler 尚存在一些题目,对此,我们举行了系列优化改造和修复,同时积累了丰富的运维履历。
优化实践

2.1 题目修复

2.1.1 HadoopUtils 引发的线程走漏题目

在某次巡检的过程中,我们发现服务节点的线程数在过去一段时间呈明显的上升态势,根据履历判断,应该是程序中存在线程走漏的地方,团结 metrics 发现走漏速率为恒定速率,并且与任务并发量无关。

通过堆栈发现走漏的线程紧张是与 HDFS 相关,进一步将代码范围缩小至 HadoopUtils 之后,我们发现此处存在引发线程走漏的代码逻辑。在 DS HadoopUtils 中存在一个 Cache,会以恒定的速率不断生成新的 HadoopUtils 实例,并放入 Cache,在 HadoopUtils 实例化的过程中会创建 HDFS FileSystem,但是却不会关闭原有的 FileSystem。
开源的 DS 在利用 HDFS FileSystem 时走漏速率比较慢,不易发现。我们在生产环境中利用的是腾讯云提供的 CosFileSystem 插件,该插件中会利用多线程来加快文件的上传 / 下载操作。通过插件中的线程数目比对,与我们走漏的线程数完全同等。

至此,我们确定线程走漏的原因是 HadoopUtils 在更新 Cache 的时间没有关闭 FileSystem,于是我们在更新 Cache 的时间关闭 FileSystem,并通过读写锁保证不会由于异步关闭导致文件操作失败,成功的解决了线程走漏的题目。对比上线前后的 JVM 线程指标,修复之后线程数保持在一个小恒定范围内。

2.1.2 TaskExecuteRunnable 内存走漏引发 CPU 飙高题目

在生产环境中,我们发如今任务量没有发生明显变革时,Master 服务随着运行时间越长,其 CPU 利用率出现增长趋势。

通过分析火焰图,我们发现 Master 中存在一处代码逻辑,随着程序的运行时间越久,这段代码逻辑对 CPU 的消耗会越来越高。

通过梳理代码逻辑,我们发现 DolphinScheduler 的 Master 服务在运行工作流时采用变乱驱动的方式,每个任务实例在运行过程中会生成一个对应的 TaskExecuteRunnable 对象,任务在运行过程中产生的生命周期变乱会存放在 TaskExecuteRunnable 对象中。会有一个后台线程轮询当前服务中存活的 TaskExecuteRunnable 对象,然后提交变乱处置惩罚任务到变乱线程池。
不外,当任务运行竣事之后,TaskExecuteRunnable 并不会被释放,还会存放在任务变乱线程池中,这就会导致任务变乱处置惩罚线程空转时间越来越长。
通过分析堆栈,我们的判断得到了验证,TaskExecuteRunnable 的确会走漏,不外由于 TaskExecuteRunnable 占用内存很少,因此很难从内存中反应出来。我们的集群中有 4 台 Master,任务实例数一天百万左右,因此对于单台 Master 一天会走漏大约 25w TaskExecuteRunnable,随着时间的积累会拖慢引擎的变乱处置惩罚。

于是我们举行了代码修复,在任务实行竣事之后,移除内存中的 TaskExecuteRunnable 对象。对比修复前后的 JVM CPU 指标,修复之后,Master 的 CPU 指标随着运行时间始终维持在一个小的恒定范围内。

2.1.3 Master 实行逻辑任务重复提交变乱,导致变乱堆积题目

DolphinScheduler 中任务分为两类,分别为以 dependent 为代表的逻辑任务和以 Shell 为代表带的物理任务,此中逻辑任务在 Master 中实行,物理任务在 Worker 中实行。不管是逻辑任务还是物理任务在 Master 处置惩罚过程中都会履历以下阶段。

逻辑任务和物理任务的区别在于 Dispatch 和 Run 阶段的实现不同,对于逻辑任务不必要触发真正的 Dispatch,Run 阶段运行在 Master 中。而物理任务在 Dispatch 阶段会将任务分发给 Worker,Run 阶段运行在 Worker 中。
无论是哪种任务,当 Dispatch 阶段实行成功之后,会注册到 StateWheelThread 中,该组件会定时的每隔 5 秒钟为每个任务生成一个 TaskStateChangeEvent,提交到任务的变乱队列中,TaskStateChangeEvent 被处置惩罚的时间会触发 Run 任务,对于逻辑任务会不断的通过 TaskStateChangeEvent 触发实行。

这里是一个典型的生产消费模型,生产者以固定的速率(每隔 5 秒)生成变乱写入队列,消费者异步的从队列中消费变乱。
因此当消费者处置惩罚的速率小于生产者生产的速率时,这里就会出现变乱堆积。
而实际环境下,由于生产者生产变乱的时间是纯内存计算,没有任何 io 壅闭,而消费者处置惩罚变乱的时间必要多次查询 db。对于 Dependent 这类逻辑任务的运行时间通常都很长,因此如果到达肯定的并发量,这里极大概率会出现变乱积存,导致整个 master 中全部任务的状态变乱处置惩罚出现延迟、增加数据库压力,严重的话还会导致 Master OOM 服务宕机。
在我们的测试环境中,单台 Master 服务,变乱线程池巨细为 100,Dependent 并发数超过 500,此时就 Master 中的 StateEvent 就会出现堆积的环境。

我们发现堆积的变乱都是用来触发逻辑任务 Run 阶段,并且对于同一任务实例存在多个重复的触发变乱,我们通过对变乱去重从而修复堆积的环境。在修复之后,变乱的堆积环境得到解决,一旦变乱的消费速率低于增长速率,变乱的堆积量最多为任务的并发数,不会出现不绝积累的环境。

2.1.4 Master 任务调度不均匀

Master 在分配任务给 Worker 的时间,会利用负载均衡计谋,使任务的分配只管均衡。默认的均衡计谋是 LOW_WEIGHT,该计谋会通过 Worker 的心跳信息来计算一个负载量,会将任务分配给负载量最低的 worker。

在实际的利用过程中我们发如今大多数环境下,这种负载计谋会出现严重的任务分配不均衡的环境,在同一个 WorkerGroup 下,不同的 Worker 被分配到的任务量可能会相差几十倍。

究其原因我们发现紧张是由两个方面导致

在计算 Worker 节点的负载时,Master 会对 Worker 的 CPU、内存、Load、期待任务数分别加一个权重来做归一化,但是针对各资源加权值和归一化算法表达不严谨。导致计算出来的负载值实际上并不能正确的反应 Worker 的真实负载环境,并且实际生产很难通过调节权重得到一个真实的值。

Worker 的心跳上报是定时上报,Master 在分发任务时利用的 Worker 心跳数据并不能反映当前 Worker 的真实环境,这会导致某个时刻一旦出现一个负载量偏低的 worker,master 在接下来一段时间中可能会将大量的任务都发送给这台 worker,从而导致任务倾斜。
分析完原因之后,我们决定利用 RANDOM 计谋来分发任务,保证 Master 在分发时绝对均衡,然后由 Worker 自己通过自身负载决定是否要担当 Master 的分发请求。对比修复前后同一个 worker 分组下不同 Pod 接收的任务,发现修复后不同的 pod 接收的任务变得均衡,不再出现任务倾斜的环境。

2.1.5 Master 变乱处置惩罚卡住题目

在生产环境中,我们发现 Master 的 CPU 一连升高,通过服务日志发现 Master 不绝在处置惩罚某个变乱,并且伴随异常,我们推测此时出现了变乱死循环的环境。通过研究 DolphinScheduler3.0.0 中 Master 变乱驱动流程我们发如今该版本中存在三类变乱。

WorkflowStartEvent 是工作流启动变乱,该变乱是由一个单独的后台线程产生,并且由一个单独的后台线程处置惩罚,用于启动工作流,工作流的元数据出现异常时会导致 WorkflowStartEvent 实行出现异常,此时异常的 WorkflowStartEvent 会不绝重试并壅闭后面其他变乱,直到在数据库中对元数据举行修复。


StateEvent 是工作流和任务实行相关的变乱,用于驱动 DAG 拓扑实行。
StateEvent 有以下变乱类型:工作流状态变更、任务状态变更、工作流超时、任务超时、TaskGroup 中的任务被唤醒、任务重试、工作流壅闭。
对于一个工作流来说,里面全部的 StateEvent 都存储在一个队列中,变乱按照进入队列的先后次序被实行。并且采用的是 DFS 的方式被线程池消费,即一个队列被 fire 的时间会被分配给一个线程,该线程直随处置惩罚完队列中的全部变乱才会退出,如果一旦有某个队列在处置惩罚时无法退出,那么线程会被不绝占用。


当任务实例在运行时发生了变革会生成 TaskEvent,即该变乱是由 Worker 发送的任务数据所转换而来,以下环境都会生成 TaskEvent,TaskEvent 处置惩罚流程和 StateEvent 类似。

值得注意的是以上三种变乱都采用死信队列的方式存放,即只有当变乱被处置惩罚成功才会将变乱从队列移除,社区最初这么设计是希望在某些环境下由于基础设施故障,比方 db 抖动等不会影响到变乱的处置惩罚,但实际上有很多其他的意外环境会导致变乱处置惩罚失败,比方数据库存在非正常数据,变乱发送过程中出现乱序等。
我们认为对于引擎来说,必要避免由于某一个工作流变乱处置惩罚出现题目,从而影响到引擎的稳定性。因此,我们移除了这里的死信队列,当变乱处置惩罚失败的时间,会直接扬弃变乱,并将工作流快速置为失败,由上层举行重试,并团结 Metrics 监控各类变乱的处置惩罚环境。修复后,Master CPU 保持稳定,服务日志也不再出现不绝重复处置惩罚某个变乱。



2.2 稳定性优化

2.2.1 工作流实例康健查抄

如今 DolphinScheduler 中 Master 实行工作流的时间会将工作流实例的元数据存储在内存,然后通过变乱驱动的方式去举行状态流转,直至工作流中全部的任务都竣事然后将工作流实例从内存卸载。在某些环境,比方网络原因导致变乱丢失,大概变乱在处置惩罚过程中由于状态机 bug 处置惩罚失败从而丢失,此时会导致工作流实例处置惩罚流程卡住,从而导致工作流实例成为孤儿实例,即永远不可能竣事。
此时如果发现了可以在上层通过 kill 的方式去停止工作流实例,从而卸载,不外这种方式存在两个题目。一是依靠业务方自行检测,必要业务方定期的巡检整个系统,当业务方发现题目时每每业务已经受到了影响。二是处置惩罚的方式很繁琐,一旦运行的工作流实例数比较多的时间,逐个操作本钱比较高。我们希望调度引擎可以或许有自检功能,可以或许自己检测工作流实例是否已经酿成僵尸实例,并且自动上报,自动做规复操作。
对此,我们举行了优化,在 Master 中添加一个组件 WorkflowInstanceHealthCoordinator,该组件用于定期对当前 Master 运行中的每个工作流实例实行康健查抄。在康健查抄的时间会通过 HeartbeatEmitter 去触发工作流实例的心跳检测,当一连多次的心跳检测失败之后,会通过 DeadWorkflowInstanceHandler 去清除该工作流实例,并上报 metrics。

整个检测紧张是由 WorkflowInstanceHealthCoordinator 负责,该组件在每个 Master 中采用单例的形式,里面包含一个后台线程,和 EventExecuteService 工作模式类似,当一个工作流实例被加载到 Cache 之后,会同时在 WorkflowInstanceHealthCoordinator 中注册自己,当工作流实例实行竣事从 Cache 中移除的时间也会同步从 WorkflowInstanceHealthCoordinator 移除。

WorkflowInstanceHealthCoordinator 中有一个后台线程会定期的(默认 5min 一次,可自定义配置检测间隔)对注册进来的工作流实例做康健查抄。
康健查抄的方式是通过对工作流实例中全部未竣事的任务做心跳探测,如果探测成功,则表明该工作流实例是存活的,如果探测失败,则表明该工作流实例可能已经出现了异常。对一个工作流实比方果探测失败的次数超过了阈值,我们认为该工作流实例已经成为僵尸工作流实例,我们如今会举行告警,由运维同学参与,当前我们尚未实现自动故障规复,因为此类僵尸实例发生的环境不会很多,后续我们会考虑实现对僵尸实例自动运维。
2.2.1 Worker 中任务变乱 TTL

DolphinScheduler 中 Worker 紧张职责是担当任务,实行任务,上报任务变乱。
此中任务变乱在上报的时间存放在内存中的一个死信队列中。

整个过程为

如许做的利益是可以或许避免因为网络抖动大概 master 因为故障而导致某段时间内变乱上报不成功从而丢失变乱,不外如许也会导致可能出现内存走漏的题目。比方,如果 master 发生容错,那么会举行工作流容错,容错的时间会先 kill 任务,然后重新提交,在 kill 的时间如果发送 RPC 给 worker 失败了,此时 worker 中的任务变乱将永远不会被清除,并且由于工作流实例发生了容错,此时某些任务变乱可能无法发送给容错后的 master,即会不绝重试,酿成僵尸消息。
即一旦消息的目的地发生了变革,但是 worker 感知不到,那么会导致消息走漏到内存。
一旦发生走漏,可能会导致重试线程中堆积大量的无效变乱,这会占用线程资源,导致有效变乱发送出现延迟,并且这类无效变乱永远不会被释放,会造成内存走漏,影响服务稳定性。
对此,我们在变乱中添加了 TTL,每个变乱在创建的时间会带有 createTime,如果 currentTime-createTime>ttl,那么表明变乱在给定的时间内没有发送成功,此时说明变乱可能已经出现了走漏,会在 prometheus 中办理,并自动从死信队列队列中清除。

2.2.2 历史数据生存计谋

随着系统利用时间的增长,数据库和磁盘数据逐渐积累,会影响服务运行和数据库稳定性。实际运维中我们发现,数据库中增长的紧张是一些实例元数据,这些数据的积累会导致数据库压力越来越大,同时会伴随慢查询越来越多。磁盘中增长的紧张是任务实例日志和任务实例工作目次,这些数据的积累会导致磁盘可用容量和 inode 变得越来越少。
我们希望程序可以或许自动的清理无用的历史数据。比方,在数据库中仅生存最近一个月的运行实例数据。磁盘上生存最近一周的临时文件,超出生存限期的数据则自动删除,以淘汰人工运维的工作。
由于 DolphinScheduler 原生的删除接口在做数据清理的时间是按照工作流实例的维度,即清理历史数据的时间必要先找出工作流实例下的任务实例,然后分别清理每个任务实例的数据,这个过程涉及大量的数据库操作和 RPC 操作,并且实行批量删除操作的时间会给服务带来很大的压力,不适用大批量数据的清理。为此,我们分别在 Master 和 Worker 中添加了 InstanceDBPurgerThread 和 TaskFilePurgerThread 两个后台线程组件,分别负责数据库实例数据和磁盘文件的定期查抄、上报与清除工作。
数据库方面紧张清理工作流实例、任务实例和告警变乱等数据。磁盘方面,紧张是清理 exec 目次下的临时文件夹,log 目次下的任务实例日志等数据。同时,通过暴露相关的 metrics 对这些数据量举行监控。将数据库和磁盘的清理分开可以极大的加快历史数据的清除速率。

2.3 巡检流程

为了确保系统稳定运行,我们不光配置了大量告警,还定期举行一样平常、周、月巡检。通过巡检,我们可以或许提前发现潜伏题目,不断的美满自动化运维流程。我们如今发现的大多数题目都是通过巡检提前发现,避免了对业务造成实际影响。
同时,巡检也帮助我们不断优化我们的监控大盘和告警项。如今我们从集群、项目、WorkerGroup 和 Pod 等维度搭建了监控面板和告警,以辅助巡检工作。





在一样平常巡检中,我们紧张关注集群、项目、WorkerGroup 三个维度下的指标。在集群维度上,关注 Master Slot 变革、集群水位、并发量、资源利用等稳定性指标。在项目和 WorkerGroup 维度,关注异常任务、任务量的同比变革,WorkerGroup 下 Slot 利用率及业务运行环境。Pod 维度则用于周巡检和题目排查。
未来规划
如今 DolphinScheduler3.0.0 已经在我们的生产环境中稳定运行,我们针对利用场景中发现的题目,在不举行大规模架构调整的条件下做出了修复和优化,并且沉淀出了一套适用于当前业务场景的运维本领。社区在后续版本中对某些题目举行了更美满的修复,如针对逻辑任务变乱壅闭的题目,重构了整个逻辑任务实行流程;针对状态机卡住题目,重构了状态机模型等。未来,随着业务量和利用场景的扩展,我们会考虑版本升级到 3.2+ 版本,以尽可能与社区保持同步,并将我们所做的一些优化项反馈至社区。
   本文由 白鲸开源科技 提供发布支持!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4