异源数据同步 → DataX 同步启动后如何手动终止?
开心一刻
刚刚和老婆吵架,气到不行,想离婚
女儿突然站出来劝解道:难道你们就不能打一顿孩子消消气,非要闹离婚吗?
我和老婆同时看向女儿,各自挽起了衣袖
女儿补充道:弟弟那么小,打他,他又不会记仇

需求背景
项目基于 DataX
来实现异源之间的数据离线同步,我对 Datax 进行了一些梳理与改造
异构数据源同步之数据同步 → datax 改造,有点意思
异构数据源同步之数据同步 → datax 再改造,开始触及源码
异构数据源同步之数据同步 → DataX 使用细节
异构数据源数据同步 → 从源码分析 DataX 敏感信息的加解密
异源数据同步 → DataX 为什么要支持 kafka?
异源数据同步 → 如何获取 DataX 已同步数据量?
本以为离线同步告一段落,不会再有新的需求,可打脸来的非常快,产品经理很快找到我,说了如下一段话
昨天我在测试开发环境试用了一下离线同步功能,很好的实现了我提的需求,给你点赞!
但是使用过程中我遇到个情况,有张的表的数据量很大,一开始我没关注其数据量,所以配置了全量同步,启动同步后迟迟没有同步完成,我才意识到表的数据量非常大,一查才知道 2 亿多条数据,我想终止同步却发现没有地方可以进行终止操作
所以需要加个功能:同步中的任务可以进行终止操作
这话术算是被产品经理给玩明白了,先对我进行肯定,然后指出使用中的痛点,针对该痛点提出新的功能,让我一点反驳的余地都没有;作为一个讲道理的开发人员,面对一个很合理的需求,我们还是很乐意接受的,你们说是不是?
需求一接,问题就来了
如何终止同步
思考这个问题之前,我们先来回顾下 DataX 的启动;还记得我们是怎么集成 DataX 的吗,异构数据源同步之数据同步 → datax 再改造,开始触及源码 中有说明,新增 qsl-datax-hook 模块,该模块中通过命令
Process process = Runtime.getRuntime().exec(realCommand);
realCommand 就是启动 DataX 的 java 命令,类似java -server -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError -Ddatax.home=/datax -classpath /datax/lib/* com.alibaba.datax.core.Engine -mode standalone -jobid -1 -job job.json
来启动 DataX,也就是给 DataX 单独启动一个 java 进程;那么如何停止 DataX,思路是不是就有了?问题是不是就转换成了
如何终止 java 进程
终止进程
如何终止进程,这个我相信你们都会
Linux:kill -9
pid
Win:cmd.exe /c taskkill /PIDpid
/F /T
但这有个前提,需要知道 DataX 的 java 进程的 pid
,而 JDK8 中 Process
的方法如下

是没有提供获取 pid 的方法,在不调整 JDK 版本的情况下,我们如何获取 DataX 进程的 pid?不同的操作系统获取方式不一样,我们分别对 Linux
和 Win
进行实现
Linux
实现就比较简单了,仅仅基于 JDK 就可以实现
Field field = process.getClass().getDeclaredField("pid");
field.setAccessible(true);
int pid = field.getInt(process);
通过反射获取 process 实现类的成员变量
pid
的值;这段代码,你们应该都能看懂吧Win
Win 系统下,则需要依赖第三方工具
oshi
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.6.5</version>
</dependency>
获取 pid 实现如下
Field field = process.getClass().getDeclaredField("handle");
field.setAccessible(true);
long handle = field.getLong(process);
WinNT.HANDLE winntHandle = new WinNT.HANDLE();
winntHandle.setPointer(Pointer.createConstant(handle));
int pid = Kernel32.INSTANCE.GetProcessId(winntHandle);
同样用到了反射,还用到了 oshi 提供的方法
合并起来即得到获取 pid 的方法
/**
* 获取进程ID
* @param process 进程
* @return 进程id,-1表示获取失败
* @author 青石路
*/
public static int getProcessId(Process process) {
int pid = NULL_PROCESS_ID;
Field field;
if (Platform.isWindows()) {
try {
field = process.getClass().getDeclaredField("handle");
field.setAccessible(true);
long handle = field.getLong(process);
WinNT.HANDLE winntHandle = new WinNT.HANDLE();
winntHandle.setPointer(Pointer.createConstant(handle));
pid = Kernel32.INSTANCE.GetProcessId(winntHandle);
} catch (Exception e) {
LOGGER.error("获取进程id失败,异常信息:", e);
}
} else if (Platform.isLinux() || Platform.isAIX()) {
try {
field = process.getClass().getDeclaredField("pid");
field.setAccessible(true);
pid = field.getInt(process);
} catch (Exception e) {
LOGGER.error("获取进程id失败,异常信息:", e);
}
}
LOGGER.info("进程id={}", pid);
return pid;
}
得到的 pid 是不是正确的,我们是不是得验证一下?写个 mainClass
/**
* mainClass
* @author 青石路
*/
public class HookMain {
public static void main(String[] args) throws Exception {
String command = "";
if (Platform.isWindows()) {
command = "ping -n 1000 localhost";
} else if (Platform.isLinux() || Platform.isAIX()) {
command = "ping -c 1000 localhost";
}
Process process = Runtime.getRuntime().exec(command);
int processId = ProcessUtil.getProcessId(process);
System.out.println("ping 进程id = " + processId);
new Thread(() -> {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), System.getProperty("sun.jnu.encoding")))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}).start();
}
}
利用 maven 打包成可执行 jar 包
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.qsl.hook.HookMain</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>target/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
然后执行 jar
java -jar qsl-datax-hook-0.0.1-SNAPSHOT.jar
我们来看下输出结果
Linux
jar 输出日志如下
我们 ps 下进程
ps -ef|grep ping
Win
jar 输出日志如下
我们再看下任务管理器的 ping 进程
可以看出,不管是 Linux 还是 Win,得到的 pid 都是正确的;得到 pid 后,终止进程就简单了
/**
* 终止进程
* @param pid 进程的PID
* @return true:成功,false:失败
*/
public static boolean killProcessByPid(int pid) {
if (NULL_PROCESS_ID == pid) {
LOGGER.error("pid[{}]异常", pid);
return false;
}
String command = "kill -9 " + pid;
boolean result;
if (Platform.isWindows()) {
command = "cmd.exe /c taskkill /PID " + pid + " /F /T ";
}
Process process = null;
try {
process = Runtime.getRuntime().exec(command);
} catch (IOException e) {
LOGGER.error("终止进程[pid={}]异常:", pid, e);
return false;
}
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
//杀掉进程
String line;
while ((line = reader.readLine()) != null) {
LOGGER.info(line);
}
result = true;
} catch (Exception e) {
LOGGER.error("终止进程[pid={}]异常:", pid, e);
result = false;
} finally {
if (!Objects.isNull(process)) {
process.destroy();
}
}
return result;
}
完整流程应该是
使用
Runtime.getRuntime().exec(java命令)
启动 DataX,并获取到Process
java 命令指的是启动 DataX 的 java 命令,例如
java -server -Xms1g -Xmx1g -XX:+HeapDumpOnOutOfMemoryError -Ddatax.home=/datax -classpath /datax/lib/* com.alibaba.datax.core.Engine -mode standalone -jobid -1 -job job.json
通过
ProcessUtil#getProcessId
获取 Process 的pid
,并与同步任务信息绑定进行持久化通过任务id 可以查询到对应的 pid
触发任务
终止
,通过任务id找到对应的 pid,通过ProcessUtil#killProcessByPid
终止进程终止了进程也就终止了同步任务
如果 qsl-datax-hook
是单节点,上述处理方案是没有问题的,但生产环境下,qsl-datax-hook 不可能是单节点,肯定是集群部署,那么上述方案就行不通了,为什么呢?我举个例子
假设 qsl-datax-hook 有 2 个节点:A、B,在 A 节点上启动 DataX 同步任务(taskId = 666)并得到对应的 pid = 1488,终止任务 666 的请求被负载均衡到 B 节点,会发生什么情况
- B 节点上没有 pid = 1488 进程,那么终止失败,A、B 节点都不受影响
- B 节点上有 pid = 1488 进程,这个进程可能是 DataX 同步任务进程,也可能是其他进程,那么这个终止操作就会产生可轻可重的故障了!
然而需要终止的同步任务却还在 A 节点上安然无恙的执行着
所以集群模式下,我们不仅需要将 pid 与任务进行绑定,还需要将任务执行的节点信息也绑定进来,节点信息可以是 节点ID
,也可以是 节点IP
,只要能唯一标识节点就行;具体实现方案,需要结合具体的负载均衡组件来做设计,由负载均衡组件将任务终止请求分发到正确的节点上,而不能采用常规的负载均衡策略进行分发了;因为负载均衡组件很多,所以实现方案没法统一设计,需要你们结合自己的项目去实现,我相信对你们来说很简单

总结
- 任务的启动方式不同,终止方式也会有所不同,如何优雅的终止,是我们需要考虑的重点
- 直接杀进程的方式,简单粗暴,但不够优雅,一旦错杀,问题可大可小,如果有其他方式,不建议选择该方式
- 适用单节点的终止方式不一定适用于集群,大家设计方案的时候一定要做全方位的考虑
- 示例代码:qsl-datax-hook
异源数据同步 → DataX 同步启动后如何手动终止?的更多相关文章
- 数据同步Datax与Datax_web的部署以及使用说明
一.DataX3.0概述 DataX 是一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL.Oracle等).HDFS.Hive.ODPS.HBase.FTP等各种异构数据源之间稳定高 ...
- 使用DataX同步MaxCompute数据到TableStore(原OTS)优化指南
概述 现在越来越多的技术架构下会组合使用MaxCompute和TableStore,用MaxCompute作大数据分析,计算的结果会导出到TableStore提供在线访问.MaxCompute提供海量 ...
- MYSQL数据库主主同步实战
MYSQL支持单向.异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器.主服务器将更新写入二进制日志文件,并维护日志文件的一个索引以跟踪日志循环.当一个从服务器连接到主服务 ...
- rsync+sersync实现数据文件实时同步
一.简介 sersync是基于Inotify开发的,类似于Inotify-tools的工具: sersync可以记录下被监听目录中发生变化的(包括增加.删除.修改)具体某一个文件或某一个目录的名字: ...
- 【NFS项目实战二】NFS共享数据的时时同步推送备份
[NFS项目实战二]NFS共享数据的时时同步推送备份 标签(空格分隔): Linux服务搭建-陈思齐 ---本教学笔记是本人学习和工作生涯中的摘记整理而成,此为初稿(尚有诸多不完善之处),为原创作品, ...
- MySQL数据实时增量同步到Kafka - Flume
转载自:https://www.cnblogs.com/yucy/p/7845105.html MySQL数据实时增量同步到Kafka - Flume 写在前面的话 需求,将MySQL里的数据实时 ...
- MongoDB 数据迁移和同步
MongoDB 数据迁移和同步 MongoDB的数据同步 复制 mongodb的复制至少需要两个实例.其中一个是主节点master,负责处理客户端请求,其余的都是slave,负责从master上复制数 ...
- Mysql 到 Hbase 数据如何实时同步,强大的 Streamsets 告诉你
很多情况大数据集群需要获取业务数据,用于分析.通常有两种方式: 业务直接或间接写入的方式 业务的关系型数据库同步到大数据集群的方式 第一种可以是在业务中编写代码,将觉得需要发送的数据发送到消息队列,最 ...
- Sersync+Rsync实现数据文件实时同步
rsync+inotify-tools与rsync+sersync架构的区别1,rsync+inotify-tools只能记录下被监听的目录发生的变化(增删改)并没有把具体变化的文件或目录记录下来在同 ...
- Rsync+sersync(inotify)实现数据实时双向同步
目录 Rsync+Sersync数据实时同步(双向) 服务介绍 节点声明 编译环境配置 安装Rsync 编辑Rsync配置文件 配置文件解析 配置密码文件 启动rsync验证 安装sersync服务 ...
随机推荐
- 通过 C# 将数据写入到Excel表格
Excel 是一款广泛应用于数据处理.分析和报告制作的电子表格软件.在商业.学术和日常生活中,Excel 的使用极为普遍.本文将详细介绍如何使用免费.NET库将数据写入到 Excel 中,包括文本.数 ...
- Win32 自绘控件按钮类
今天学了控件的自绘,初步偿试了下,蹂躏的不行不行的,查了好多的资料,头都弄大了, 有好多还是没弄明白,只是初步实现一个按钮的基本功能,好难呀, 先看下效果: 按下状态 弹起状态 按钮2按下状态 按钮2 ...
- 自制 ShareLaTeX 镜像
Overleaf 官方的 sharelatex 镜像的 TeX Live 版本可能较旧,无法安装最新的宏包,并且往往只包含了少量的基础宏包.为了方便使用,我们可以自己构建一个使用最新 TeX Live ...
- Mac 使用远程 Ubuntu 机器进行时间备份
设置 SMB 服务 首先在 Ubuntu 中配置 SMB 服务.可以参考 Ubuntu 设置 SMB 服务. 创建 APFS 磁盘映像 我们在 Ubuntu 上创建出的 SMB 共享文件夹可以用来存放 ...
- CM3和ARM7的差异
此文章由文心一言生成,引用请标注作者:文心一言CM3通常指的是Cortex-M3,它是ARM公司设计的一种基于ARMv7-M架构的32位处理器内核,主要用于嵌入式系统.而ARM7则是ARM公司早期设计 ...
- 【YashanDB数据库】YAS-00413 wait for receive timeout
[问题分类]错误码处理 [关键字]yasql,00413 [问题描述]使用工具设置不同并发迁移数据的过程中,导致yasql登录报错:YAS-00413 wait for receive timeout ...
- Openstack-Train( 一)基础环境
openStack-train 搭建部署 当面对KVM集群的时候,我们对KVM的管理以及宿主机的管理就会遇到很大的难度,例如: 查看每一个宿主机有多少台KVM虚拟机? 查看每一个宿主机资源信息,每一个 ...
- 单 log 实现 区间加减,查询区间 gcd
主要是查询,要将 log 个区间拿出来依次求 gcd,当然如果是 O(1) gcd 的话可以直接求就是了.
- GPUStack 0.2:开箱即用的分布式推理、CPU推理和调度策略
GPUStack 是一个专为运行大语言模型(LLM)设计的开源 GPU 集群管理器,旨在支持基于任何品牌的异构 GPU 构建统一管理的算力集群,无论这些 GPU 运行在 Apple Mac.Windo ...
- 线段树 transformation——hdu 4578
问题描述: 给定一个数列,数列中所有元素都初始化为0,对其执行多种区间操作 操作1:add修改:对区间[L,R]内的所有数加c 操作2:multi修改:对区间[L,R]内所有数乘以c 操作3:chan ...