奇富科技(原 360 数科)是人工智能驱动的信贷科技服务平台,致力于凭借智能服务、AI 钻研及利用、平安科技,赋能金融机构提质增效,助推普惠金融高质量倒退,让更多人享受到平安便捷的金融科技服务。作为国内当先的信贷科技服务品牌,累计注册用户数 2 亿多。
奇富科技之前应用的是自研的任务调度框架,基于 Python 研发的,常常面临着调度不稳固的情况,难以保护。起初引入了 Apache DolphinScheduler 作为公司的大数据任务调度零碎,面对大量任务调度的考验,经验了半年磨合期,目前 Apache DolphinScheduler 在奇富科技运行十分稳固。本文将介绍该公司团队最近一年在开源版 Apache DolphinScheduler 根底上所做的优化和改良。
一、技术架构
在咱们公司的大数据离线任务调度架构中,调度平台处于中间层。用户通过数据集成平台提交数据同步工作给调度平台,通过数据开发平台提交工作流给调度平台。用户不和调度平台间接交互,而是和数据集成平台和数据开发平台交互(图 1)。
因为咱们是一个金融相干业务的公司,业务须要保障高可用。因而,咱们的调度平台是异地双机房架构,外围工作流会异地双机房运行。集群角色分为 cluster A 和 cluster B,其中 cluster A 为主集群,cluster B 为从集群(图 2)。用户的工作流在 A 集群运行,其中外围要害工作流会在 A 和 B 集群双机房运行。以下是调度集群各服务个数。其中 Api、Alter、Master 服务在虚拟机部署,Worker 和 Logger 部署在物理机上。
二、业务挑战
01 调度任务量大
咱们目前每天调度的工作流实例在 3 万多,工作实例在 14 万多。每天调度的任务量十分宏大,要保障这么多任务实例稳固、无提早运行,是一个十分大的挑战 2
02 运维简单
因为每天调度的工作实例十分多,咱们经验了几次调度机器扩容阶段。目前 2 个调度集群有 6 台 Master、34 台 Worker 机器。而且调度机器处于异地 2 个城市,减少了很多治理运维复杂性。
03 SLA 要求高
因为咱们业务的金融属性,如果调度服务稳定性出问题,导致工作反复调度、漏调度或者异样,损失会十分大。
三、调度优化实际
咱们在过来一年,对于调度服务稳固,咱们做了如下 2 个方向的优化。第一,调度服务稳定性优化。第二、调度服务监控。
01 反复调度
在 2023 年初,用户大规模迁徙工作流时,遇到了工作流反复调度问题。该问题,景象是同一个工作流会在同一个集群同一时间,生成 2 个工作流实例。通过排查,是因为用户在迁徙时,会有工作流迁徙我的项目的需要,比方从 A 我的项目迁徙到 B 我的项目。在工作流上线时,用户通过提交工单,批改了调度数据库中工作流的我的项目 ID,进行迁徙。这么做会导致该工作流所对应的 quartz 元数据产生 2 条数据,进而导致该工作流反复调度。如图 3 所示,JOB_NAME 为’job_1270’的记录,有 2 条数据,而 JOB_GROUP 不一样。查问源码 job_name 对应工作流的定时器 ID,JOB_GROUP 对应我的项目 ID。因而批改工作流对应的我的项目 ID,会导致 quartz 数据反复和反复调度。正确迁徙工作流我的项目的形式是,先下线工作流,而后再批改我的项目 ID。
如何防止和监控此问题,咱们依据这个逻辑,写了反复调度的监控 sql,在最近一年中,数次提前发现了 quartz 的漏调度问题。
SELECT count(1)FROM (SELECT TRIGGER_NAME, count(1) AS num FROM QRTZ_TRIGGERS GROUP BY TRIGGER_NAME HAVING num > 1 )t
02 漏调度
在 2023 年初,在凌晨 2 点,有些工作流产生漏调度,咱们排查后发现是凌晨 2 点 0 分调度太集中,调度不过去。因而咱们优化了 quartz 参数,将 org.quartz.jobStore.misfireThreshold 从 60000
调整为 600000。
如何监控和防止此问题,监控 sql 摘要如下:
select TRIGGER_NAME,NEXT_FIRE_TIME ,PREV_FIRE_TIME,NEXT_FIRE_TIME-PREV_FIRE_TIMEfrom QRTZ_TRIGGERSwhere NEXT_FIRE_TIME-PREV_FIRE_TIME=86400000*2
原理就是依据 quartz 的元数据表 QRTZ_TRIGGERS
的上一次调度工夫 PREV_FIRE_TIME
和下一次调度工夫 NEXT_FIRE_TIME
的差值进行监控。如果差值为 24 小时就失常,如果差值为 48 小时,就阐明呈现了漏调度。
如果曾经产生了漏调度如何紧急解决?咱们实现了漏调度补数逻辑通过自定义工作流进行 http 接口调用。如果监控到产生了漏调度状况,能够立刻运行此工作流,就能把漏调度的工作流立刻调度运行起来。
03 Worker 服务卡死
这个景象是凌晨调度 Worker 所在机器内存占用飙升至 90% 多,服务卡死。
咱们思考产生该问题的起因是,调度 worker 判断本机残余内存时,有破绽。比方咱们设置 worker 服务残余内存为 25G 时,不进行任务调度。然而,当 worker 本机残余内存为 26G 时,服务判断本机残余内存未达到限度条件,那么开始从 zk 队列中抓取工作,每次抓取 10 个。而每个 spark 的 driver 占用 2G 内存,那么本地抓取的 10 个工作在将来的内存占用为 20G。咱们能够简略计算得出本机残余内存为 26G-20G 为 6G,也就是说抓取了 10 个工作,将来的残余内存可能为 6G,会面临严重不足。
为了解决这个问题,咱们参考 Yarn,提出了”预申请”机制。预申请的机制是,判断本机残余内存时,会减去抓取工作的内存,而不是简略判断本机残余内存。
如何获取将要抓取工作的内存数呢?有 2 种形式,第一种是在创立工作流时指定本工作 driver 占用的内存,第二种是给一个固定平均值。
咱们综合思考,采纳了第二种形式,因为对于用户来说,是没有感知的。咱们对要抓取的每个工作配置 1.5G(经验值)内存,以及达到 1.5G 内存所须要的工夫为 180 秒,抓取工作后,会放入缓存中,缓存过期工夫为 180(经验值)秒。残余内存计算公式,本机残余内存 = 本机实在物理残余内存 - 缓存中工作个数 1.5G+ 本次筹备抓取的工作数 1.5G。
还是同样的场景,本机配置的残余内存为 25G,本机理论残余内存为 26G,要抓取的工作为 10 个。每个工作将来占用的 driver 内存为 1.5G。简略计算一下,本机残余内存 =26G-10*1.5G。在“预申请”机制下,本机残余内存为 1G,小于 25G,不会抓取,也就不会导致 Worker 机器的内存占用过高。那么会不会导致 Worker 服务内存使用率过低呢,比方 shell、python、DataX 等占用内存低的工作。论断是不会,因为咱们有 180 秒过期机制,过期后,计算失去的本机残余内存为变高。
依据同样的原理,CPU 占用,咱们也加上了同样的机制,给每个要抓取的任务分配肯定的 cpu 负载值。
加上内存预申请后,最近半年,没有遇到因为内存占用过高导致 worker 服务卡死的问题。以下是咱们加上内存预申请机制后,worker 内存使用率状况,能够看见 worker 最大内存使用率始终稳固放弃在 80% 以下。
04 工作反复运行
在 worker 服务卡死时,咱们发现 yarn 上的工作没有被杀死,而 master 容错时导致工作被反复提交到 yarn 上,最终导致用户的数据异样。
咱们剖析后发现,工作实例有一个 app_link 字段,寄存用户提交的 yarn 工作的 app id,而第一次调度的工作的 app id 为空。排查代码发现 worker 在运行工作时,只有实现的 yarn 工作,才会更新 app_link 字段。这样导致 master 在容错时,拿不到 app id,导致旧工作没有被杀死,最终导致工作反复提交。
咱们进行的第一个改良点为,在 worker 运行 yarn 工作时,从 log 中实时过滤出 app id,而后每隔 5 秒将 app id 更新到 app_link 字段中。这样 yarn 工作在运行时,也就能获取到 app id,master 容错时就能杀死旧工作。
第二个改良点为,在 worker 服务卡死从而他杀时,杀死本机上正在运行的调度服务,这样可能 master 就不须要进行容错了。
施行这个计划后,最近半年没有遇到反复调度的 yarn 工作了。
05 弱依赖
经营标签对于时效性要求很高,关系到广告投放成果。他们提出了一个需要,他们对于某些依赖工作流,不是强依赖的,如果该父工作流在约定的工夫没有实现,那么就不进行依赖。为了实现这个需要,咱们引入了弱依赖的机制。旧依赖模式,咱们定义为强依赖,如果该工作流在约定周期没有运行实现,那么永远不能依赖胜利。而弱依赖,会期待到某个工夫,如果还没有实现,那么也会胜利。
06 虚构节点
咱们调度集群是双机房运行的,因而有些外围工作流是运行在 2 个机房的。比方有些数仓 ads 相干工作流是输入 hive 数据到 mysql 表的,而 mysql 数据源来不及双数据源,只有一个 mysql。因而主集群导入数据到 mysql 表,从集群就不应该导入数据到 mysql 表中。因而咱们实现了虚构节点的性能,实现的指标是,此节点在主集群实在运行,在从集群虚构运行。
07 工作的 yarn 队列动静切换
咱们的 yarn 队列是依据大业务线进行划分的,队列个数并不多。咱们对于用户的调度工作稳定性须要保障,而常常须要到的一个状况是,yarn 的队列常常被补数工作占用过多,导致用户失常的调度工作提交不下来。
因而,咱们提出了工作的 yarn 队列动静切换计划。原理就是当用户补数时,数据开发平台依据用户所属业务线,找到该用户所属的 yarn 队列名称,而后将该队列名称提交到全局变量中。调度 worker 在对该工作进行调度时,会判断该全局变量是否有值,如果有就进行替换。
通过该计划,咱们实现了调度工作在失常队列中运行,而补数工作进入补数的小队列中运行。从而保障了失常调度工作的时效性和稳定性。
08 实例分页查问接口优化
每天调度的工作实例有 14 万多,咱们保留了 2 个月数据,那么工作实例的记录数约为 1000 多万条。而 DolphinScheduler 查问工作流实例和工作实例有 join 关系,须要通过 join 查问 project_id,在查问一些大的我的项目的工作实例时,耗时最大为几分钟甚至间接卡死。
咱们提出的解决方案是,通过字段冗余,在工作流实例和工作实例中存储 project_id, 将 join 分页查问改为单表分页查问。优化后,大我的项目的工作实例分页查问 p99 耗时从几分钟升高到 200ms。
09 Worker 保护模式
在 worker 发版时,咱们不应该影响用户调度的工作。因而,咱们实现了 worker 的保护模式。当 worker 开启保护模式时,该 worker 不会再新抓取工作,而曾经抓取的工作持续运行,从而不影响用户的调度工作。过 4 小时后,判断该 worker 上工作运行实现,再对该 worker 进行 jar 包替换和重启服务。通过这种形式,咱们可能做到 DolphinScheduler 发版对用户的调度工作无影响,用户无感知。
10 worker 和 nodemanager 混部
随着业务倒退,公司每天调度的工作流实例越来越多,worker 服务常常内存不足,须要申请大内存的机器作为 worker 调度机。不过,面临着降本增效的压力,咱们思考 DolphinScheduler 的 worker 服务能不能和 yarn 的 nodemanager 进行混合部署,因为咱们的 yarn 集群有 1000 多台机器。咱们心愿通过这种形式达到不必申请新的机器,从而降低成本的指标。
咱们的解决方案如下,新扩容 worker 服务在 nodemanager 上,在早晨 23 点,通过 yarn 命令将该混部的 nodemanager 可用内存调低为 1 核 4G,从而进行 yarn 将任务调度到该机器上,而后调用 api 接口,敞开该 worker 的保护模式,让该 worker 调度 ds 调配的工作。在早上 10 点,通过调用 api 接口,关上 worker 的保护模式,从而进行 worker 调度 ds 调配的工作,并通过 yarn 命令将 nodemanager 的内存和 cpu 复原为正常值,从而让 yarn 分配任务到该机器上。
通过这种计划,咱们实现了凌晨该机器给 DolphinScheduler 的 worker 应用,白天给 yarn 的 nodemanager 应用,从而达到降本增效的指标。新扩容的 worker,咱们都采纳了这种形式。
四、服务监控
一个稳固的零碎,除了代码上的优化,肯定离不开欠缺的监控。而 DolphinScheduler 服务在每天调度这么大量时,咱们作为开发和运维人员须要提前晓得调度零碎和工作健康状况。因而依据咱们的教训,咱们在 DolphinScheduler 服务的监控方向做了如下事件。
01 办法耗时监控
咱们通过 byte-buddy、micrometer 等,实现了自定义轻量级 java agent 框架。这个框架实现的指标是监控 java 办法的最大耗时、均匀耗时、qps、服务的 jvm 健康状况等。并把这些监控指标通过 http 裸露进去,而后通过 prometheus 抓取,最初通过 grafana 进行展现,并依据 prometheus 指标进行告警。以下是 master 拜访 zk 和 quartz 的最大耗时,均匀耗时,qps 等。
以下是 master 服务的 jvm 监控指标
通过该 java agent,咱们做到了 api、master、worekr、zookeeper 等服务办法耗时监控,屡次提前发现问题,防止将问题扩充到用户感知的情况。
02 任务调度链路监控
为了保障调度工作的稳定性,有必要对任务调度的生命周期进行监控。咱们晓得 DolphinScheduler 服务调度工作的全流程是先从 quartz 中产生 command,而后从 command 到工作流实例,又从工作流实例再到工作实例。咱们就对这个工作链路进行生命周期监控。
1)监控 quartz 元数据
后面曾经讲了咱们通过监控 quartz 元数据,发现漏调度和反复调度问题。
2)监控 command 表积压状况
通过监控 command 表积压状况,从而监控 master 是否服务失常,以及 master 服务的性能是否可能满足需要。
3)监控工作实例
通过监控工作实例期待提交工夫,从而监控 worker 服务是否失常,以及 worker 服务的性能是否可能满足需要。
通过如上全生命周期监控,咱们屡次提前发现 worker 服务的性能问题,提前解决,胜利防止影响到用户调度服务。
03 日志监控
后面咱们通过 java agent 实现了办法耗时的监控,不过这还不够。因而,咱们还通过 filebeat 采集了 3 台 api、6 台 master、34 台 worker 的服务日志到咱们公司的日志核心,而后对日志进行异样突增告警。
五、用户收益
通过最近一年对 DolphinScheduler 代码的优化,咱们取得的最大收益是近半年没有因为调度服务导致用户的 SLA 受影响,并屡次在调度服务呈现问题时,提前解决,没有影响到用户工作的 SLA 达成率。
六、用户简介
图片
奇富科技(原 360 数科)是人工智能驱动的信贷科技服务平台,秉承“始于平安、恒于科技”的初心,凭借智能服务、AI 钻研及利用、平安科技,赋能金融机构提质增效,助推普惠金融高质量倒退,让更多人享受到平安便捷的金融科技服务,助力实现共同富裕。作为国内当先的信贷科技服务品牌,累计注册用户数 2 亿多。
作者介绍
- 刘坤元
奇富科技数据平台部大数据开发工程师,19 年入职奇富科技,目前负责大数据任务调度零碎开发和工作治理工作。
- 王洁
奇富科技数据平台部大数据开发工程师,19 年入职奇富科技,目前负责大数据任务调度零碎开发工作。
本文由 白鲸开源科技 提供公布反对!