记一次 ElasticJob 从节点循环等待分片导致部分分片未执行的问题排查

本文深入分析了使用ElasticJob实现DAG调度时遇到的问题,特别是当新进程加入时导致的部分任务分片无法执行的现象,并提出了有效的解决方案。

本人水平及经验有限,内容如有错误或表述不当,请各位大佬批评指正!

分布式调度解决方案 https://github.com/apache/shardingsphere-elasticjob

本文基于 ElasticJob-Lite 3.0.0-beta-SNAPSHOT,撰写时 master 分支最新的 commit 为:
Trigger one-off job via Zookeeper (#1457)

背景描述

这段时间本人使用 ElasticJob 的 OneOffJobBootstrap 实现一个纯外置的 DAG 调度。
在调度时,DAG 中的每个节点都对应了一个 OneOffJobBootstrap 的实例,DAG 调度器通过 OneOffJobBootstrap 的 execute() 方法调度节点。

其中,每个 DAG 中的 OneOffJobBootstrap 实例是首次进行该 DAG 调度时才会初始化,后续 DAG 调度都将直接基于该实例调用 execute() 方法。

用伪代码描述:

private OneOffJobBootstrap aNodeOfDag;

public void scheduleNode() {
   
   
	if (null == aNodeOfDag) {
   
   
		aNodeOfDag = new OneOffJobBootstrap(...);
	}
	aNodeOfDag.execute();
}

DAG 模块、DAG 作业执行器模块写好后,在本地环境进行自测。

  • 在单进程情况下,DAG 作业调度正常;
  • 同时启动两个进程,DAG 作业调度正常,DAG 中每个节点的分片正常;
  • 先启动单个进程,该进程运行一段时间(至少完成过一次 DAG 作业)后,再启动新的进程加入集群。此时预期是 DAG 中的每个节点会被重新分片以分配给多个进程。但实际情况是, DAG 中某个节点的作业只有该作业的 Leader 进程执行了,非 Leader 进程的 Debug 日志循环打印等待分片,且直到下一次 DAG 调度,非 Leader 进程的分片仍然没有运行。

初步分析与判断

验证是否与 OneOffJobBootstrap 的触发方式有关

看起来像是 OneOffJobBootstrap 出了问题,只有该作业的 Leader 节点所在进程执行成功,立即联想到有没有可能和前段时间修复的 OneOffJobBootstrap 无法分布式执行的问题 有关系?

于是编写了两段使用 OneOffJobBootstrap 调度作业的代码进行验证,用伪代码描述逻辑:

private static OneOffJobBootstrap bootstrap;

public static void initializeAndExecuteInLoop() {
   
   
	bootstrap = new OneOffJobBootstrap(...);
	while (true) {
   
   
		bootstrap.execute();
		TimeUnit.SECONDS.sleep(10L);
	}
}

public static void initializeOnly() {
   
   
	bootstrap = new OneOffJobBootstrap(...);
}

先启动一个进程执行 initializeAndExecuteInLoop() 方法;
然后再启动执行 initializeOnly() 的进程,且在整个过程中频繁增加或减少执行该方法的进程。

经过几轮测试发现,每一次调度都能完成分片且所有分片都能正常执行。也许不是这个问题?

分析 DAG 作业执行器的逻辑特点,猜测与节点初始化有关

根据本人 DAG 作业执行器的实现逻辑,其中有一个关键点:

DAG 中每个节点的 OneOffJobBootstrap 将在首次调度时初始化

回顾场景:

先启动单个进程,该进程运行一段时间(至少完成过一次 DAG 作业)后,再启动新的进程加入集群。

此时集群中,已有进程的 DAG 的节点都已经经过初始化且时刻准备着,新加入的节点需要经过一个初始化的过程(OneOffJobBootstrap 的初始化),初始化完成后就可以监听触发作业的 ZNode。

新的节点在加入过程中会创建实例、分片标记等相关 ZNode,也许在这过程中老节点和新节点发生了什么摩擦?

口说无凭,开始验证。


问题排查过程

环境说明

基本信息:

➜  ~ uname -a
Linux local-wwj-icu 5.4.0-47-generic #51-Ubuntu SMP Fri Sep 4 19:50:52 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
➜  ~ java -version
java version "1.8.0_261"
Java(TM) SE Runtime Environment (build 1.8.0_261-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.261-b12, mixed mode)
➜  ~ docker images zookeeper
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
zookeeper           3.6.1               6ad6cb039dfa        6 weeks ago         252MB

其中,zookeeper 直接在 K8S 上部署单节点。

➜  ~ kubectl get po -n zk zk36-0 -owide 
NAME     READY   STATUS    RESTARTS   AGE   IP             NODE            NOMINATED NODE   READINESS GATES
zk36-0   1/1     Running   10         16d   10.240.0.150   local.wwj.icu   <none>           <none>

以尽量简洁的方式复现问题

复现问题的关键点

  1. 集群中已有就绪的作业实例;
  2. 作业调度的同时有新实例加入。

构造复现代码

由于直接使用 DAG 复现问题在排查上会增加一些麻烦,经过一段时间分析后,本人构造了尽量简单的问题复现代码。

完整代码可以在 TeslaCN/reproduce-blocking 找到。

关于 OneOffJobBootstrap,只要任意一个实例的 execute() 被调用,集群中有相应作业的 OneOffJobBootstrap 实例的节点就会分布式地执行作业。

定义作业执行逻辑。
该作业执行逻辑定义了 OneOffJobBootstrap 实例的初始化逻辑,并且只由分片 0 执行 execute() 方法。

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.shardingsphere.elasticjob.api.JobConfiguration;
import org.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuweijie@apache.org

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值