本人水平及经验有限,内容如有错误或表述不当,请各位大佬批评指正!
分布式调度解决方案 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>
以尽量简洁的方式复现问题
复现问题的关键点
- 集群中已有就绪的作业实例;
- 作业调度的同时有新实例加入。
构造复现代码
由于直接使用 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.

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

被折叠的 条评论
为什么被折叠?



