技术干货|如何利用 ChunJun 实现数据离线同步?
ChunJun 是⼀款稳定、易⽤、⾼效、批流⼀体的数据集成框架,基于计算引擎 Flink 实现多种异构数据源之间的数据同步与计算。ChunJun 可以把不同来源、格式、特点性质的数据在逻辑上或物理上有机地集中,从⽽为企业提供全⾯的数据共享,目前已在上千家公司部署且稳定运⾏。
在之前,我们曾经为大家介绍过如何利用 ChunJun 实现数据实时同步(点击看正文),本篇将为大家介绍姊妹篇,如何利⽤ ChunJun 实现数据的离线同步。
ChunJun 离线同步案例
离线同步是 ChunJun 的⼀个重要特性,下⾯以最通⽤的 mysql -> hive 的同步任务来介绍离线同步。
配置环境
找⼀个空⽬录,接下来要配置 Flink 和 ChunJun 的环境,下⾯以 /root/chunjun_demo/ 为例⼦。
● 配置 Flink
下载 Flink
wget "http://archive.apache.org/dist/flink/flink-1.12.7/flink-1.12.7-bin-scala_2.12.tgz"
tar -zxvf chunjun-dist.tar.gz
● 配置 ChunJun
#下载 chunjun, 内部依赖 flink 1.12.7
wget https://github.com/DTStack/chunjun/releases/download/v1.12.8/chunjun-dist-1.12-SNAPSHOT.tar.gz
#新创建⼀个⽬录
mkdir chunjun && cd chunjun
#解压到指定⽬录
tar -zxvf chunjun-dist-1.12-SNAPSHOT.tar.gz
解压好的 ChunJun 有如下⽬录:
bin
chunjun-dist
chunjun-examples
lib
● 配置环境变量
#配置 Flink 环境变量
echo "FLINK_HOME=/root/chunjun_demo/flink-1.12.7" >> /etc/profile.d/sh.local
#配置 Chunjun 的环境变量
echo "CHUNJUN_DIST=/root/chunjun_demo/chunjun/chunjun-dist" >> /etc/profile.d/sh.local
#刷新换新变量
. /etc/profile.d/sh.local
● 在 Yarn 上⾯启动 Flink Session
#启动 Flink Session
bash $FLINK_HOME/bin/yarn-session.sh -t $CHUNJUN_DIST -d
输出如下:
echo "stop" | $FLINK_HOME/bin/yarn-session.sh -id application_1683599622970_0270
If this should not be possible, then you can also kill Flink via YARN's web interface or via:
yarn application -kill application_1683599622970_0270
下⾯提交任务会⽤到 Flink Session 这个 Yarn Application Id (application_1683599622970_0270)。
● 其他配置
如果⽤ parquet 格式,需要把 flink-parquet_2.12-1.12.7.jar 放⼊到 flink/lib 下⾯, 在上⾯的例⼦中,需要放到 $FLINK_HOME/lib ⾥⾯。
提交任务
● 在 MySQL 准备数据
-- 创建⼀个名为ecommerce_db的数据库,⽤于存储电商⽹站的数据
CREATE DATABASE IF NOT EXISTS chunjun;
USE chunjun;
-- 创建⼀个名为orders的表,⽤于存储订单信息
CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY, -- ⾃增主键
order_id VARCHAR(50) NOT NULL, -- 订单编号,不能为空
user_id INT NOT NULL, -- ⽤户ID,不能为空
product_id INT NOT NULL, -- 产品ID,不能为空
quantity INT NOT NULL, -- 订购数量,不能为空
order_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
-- 订单⽇期,默认值为当前时间戳,不能为空
);
-- 插⼊⼀些测试数据到orders表
INSERT INTO orders (order_id, user_id, product_id, quantity)
VALUES ('ORD123', 1, 101, 2),
('ORD124', 2, 102, 1),
('ORD125', 3, 103, 3),
('ORD126', 1, 104, 1),
('ORD127', 2, 105, 5);
select * from chunjun.orders;
如果没有 MySQL 的话,可以⽤ docker 快速创建⼀个。
docker pull mysql:8.0.12
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0.12
● 创建 Hive 表
CREATE DATABASE IF NOT EXISTS chunjun;
USE chunjun;
-- 创建⼀个名为orders的表,⽤于存储订单信息
CREATE TABLE IF NOT EXISTS chunjun.orders (
id INT,
order_id VARCHAR(50),
user_id INT,
product_id INT,
quantity INT,
order_date TIMESTAMP
)
STORED AS PARQUET;
-- 查看 hive 表,底层的 HDFS ⽂件位置,下⾯的 SQL 结果⾥⾯ Location 字段,就是 HDFS ⽂件的位置。
desc formatted chunjun.orders;
-- Location: hdfs://ns1/dtInsight/hive/warehouse/chunjun.db/orders
-- ⼀会配置同步任务的时候会⽤到 hdfs://ns1/dtInsight/hive/warehouse/chunjun.db/orders
● 在当前⽬录( /root/chunjun_demo/ ) 配置⼀个任务 mysql_hdfs.json
vim mysql_hdfs.json 输⼊如下内容:
{
"job": {
"content": [
{
"reader": {
"parameter": {
"connection": [
{
"schema": "chunjun",
"jdbcUrl": [ "jdbc:mysql://172.16.85.200:3306/chunjun" ],
"table": [ "orders" ]
}
],
"username": "root",
"password": "123456",
"column": [
{ "name": "id", "type": "INT" },
{ "name": "order_id", "type": "VARCHAR" },
{ "name": "user_id", "type": "INT" },
{ "name": "product_id", "type": "INT" },
{ "name": "quantity", "type": "INT" },
{ "name": "order_date", "type": "TIMESTAMP" }
]
},
"name": "mysqlreader"
},
"writer": {
"parameter": {
"path": "hdfs://ns1/dtInsight/hive/warehouse/chunjun.db/orders",
"defaultFS": "hdfs://ns1",
"hadoopConfig": {
"dfs.nameservices": "ns1",
"dfs.ha.namenodes.ns1": "nn1,nn2",
"dfs.namenode.rpc-address.ns1.nn1": "172.16.85.194:9000",
"dfs.namenode.rpc-address.ns1.nn2": "172.16.85.200:9000",
"dfs.client.failover.proxy.provider.ns1":
"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
},
"column": [
{ "name": "id", "type": "INT" },
{ "name": "order_id", "type": "VARCHAR" },
{ "name": "user_id", "type": "INT" },
{ "name": "product_id", "type": "INT" },
{ "name": "quantity", "type": "INT" },
{ "name": "order_date", "type": "TIMESTAMP" }
],
"writeMode": "overwrite",
"encoding": "utf-8",
"fileType": "parquet",
"fullColumnName":
[ "id", "order_id", "user_id", "product_id", "quantity", "order_date"],
"fullColumnType":
[ "INT", "VARCHAR", "INT", "INT", "INT", "TIMESTAMP" ]
},
"name": "hdfswriter"
}
}
],
"setting": {
"errorLimit": {
"record": 0
},
"speed": {
"bytes": 0,
"channel": 1
}
}
}
}
因为我们要将 MySQL 同步到 Hive ⾥⾯,但是如果直接同步 Hive 的话,内部会⽤ jdbc,⽽ jdbc 的效率不⾼,因此我们可以直接把数据同步到 Hive 底层的 HDFS 上⾯,所以 writer ⽤到了 hdfswriter。脚本解析如下:
{
"job": {
"content": [
{
"reader": {
"parameter": {
"connectionComment": "数据库链接, 数据库, 表, 账号, 密码",
"connection": [
{
"schema": "chunjun",
"jdbcUrl": [ "jdbc:mysql://172.16.85.200:3306/chunjun" ],
"table": [ "orders" ]
}
],
"username": "root",
"password": "123456",
"columnComment": "要同步的列选择, 可以选择部分列",
"column": [
{ "name": "id", "type": "INT" },
{ "name": "order_id", "type": "VARCHAR" },
{ "name": "user_id", "type": "INT" },
{ "name": "product_id", "type": "INT" },
{ "name": "quantity", "type": "INT" },
{ "name": "order_date", "type": "TIMESTAMP" }
]
},
"nameComment" : "source 是 mysql",
"name": "mysqlreader"
},
"writer": {
"parameter": {
"pathComment": "HDFS 上⾯的路径, 通过 hive 语句的 desc formatted 查看",
"path": "hdfs://ns1/dtInsight/hive/warehouse/chunjun.db/orders",
"defaultFS": "hdfs://ns1",
"hadoopConfigComment": "是 hdfs ⾼可⽤最基本的配置, 在 Hadoop 配置⽂件 hdfs-site.xml 可以找到",
"hadoopConfig": {
"dfs.nameservices": "ns1",
"dfs.ha.namenodes.ns1": "nn1,nn2",
"dfs.namenode.rpc-address.ns1.nn1": "172.16.85.194:9000",
"dfs.namenode.rpc-address.ns1.nn2": "172.16.85.200:9000",
"dfs.client.failover.proxy.provider.ns1":
"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"
},
"columnComment": "要同步的列选择, 可以选择部分列",
"column": [
{ "name": "id", "type": "INT" },
{ "name": "order_id", "type": "VARCHAR" },
{ "name": "user_id", "type": "INT" },
{ "name": "product_id", "type": "INT" },
{ "name": "quantity", "type": "INT" },
{ "name": "order_date", "type": "TIMESTAMP" }
],
"writeModeComment": "覆盖写⼊到 hdfs 上⾯的⽂件, 可选 overwrite, append(默认模式)",
"writeMode": "overwrite",
"encoding": "utf-8",
"fileTypeComment": "可选 orc, parquet, text",
"fileType": "parquet",
"fullColumnNameComment": "全部字段,有时候 column ⾥⾯同步部分字段,但是⼜需要有全部字段的格式,例如 fileType : text ",
"fullColumnName": [ "id", "order_id", "user_id", "product_id", "quantity", "order_date"],
"fullColumnTypeComment": "全部字段的类型",
"fullColumnType": [ "INT", "VARCHAR", "INT", "INT", "INT", "TIMESTAMP" ]
},
"nameComment" : "sink 是 hdfs",
"name": "hdfswriter"
}
}
],
"setting": {
"errorLimit": {
"record": 0
},
"speed": {
"bytes": 0,
"channel": 1
}
}
}
}
● 提交任务
bash chunjun/bin/chunjun-yarn-session.sh -job mysql_hdfs.json -confProp
{\"yarn.application.id\":\"application_1683599622970_0270\"}
● 查看任务
任务同步完成, 可以看⼀下 HDFS 上⾯的数据。
查看⼀下 Hive 表的数据。
注意, 如果是分区的 Hive 表,需要⼿动刷新⼀下 Hive 的元数据, 使⽤ MSCK 命令。(MSCK 是 Hive 中的⼀个命令,⽤于检查表中的分区,并将其添加到 Hive 元数据中)
MSCK REPAIR TABLE my_table;
ChunJun 离线同步原理解析
HDFS 文件同步原理
· 对于⽂件系统,同步的时候会先把⽂件写⼊到 path + [filename] ⽬录⾥⾯的 .data 的⽂件⾥⾯,如果任务失败,那么 .data ⾥⾯的⽂件不会⽣效。
· 在 TaskManager 上⾯所有 task 任务结束的时候,会在 JobManager 执⾏ FinalizeOnMaster 的 finalizeGlobal ⽅法, 最终会调⽤到 moveAllTmpDataFileToDir , 把 .data ⾥⾯的⽂件移除到 .data 的上⼀层。
public interface FinalizeOnMaster {
/**
The method is invoked on the master (JobManager) after all (parallel) instances of an OutputFormat finished.
Params:parallelism – The parallelism with which the format or functions was run.
Throws:IOException – The finalization may throw exceptions, which may cause the job to abort.
*/
void finalizeGlobal(int parallelism) throws IOException;
}
// 在 JobManager 执⾏
@Override
protected void moveAllTmpDataFileToDir() {
if (fs == null) {
openSource();
}
String currentFilePath = "";
try {
Path dir = new Path(outputFilePath);
Path tmpDir = new Path(tmpPath);
FileStatus[] dataFiles = fs.listStatus(tmpDir);
for (FileStatus dataFile : dataFiles) {
currentFilePath = dataFile.getPath().getName();
fs.rename(dataFile.getPath(), dir);
LOG.info("move temp file:{} to dir:{}", dataFile.getPath(), dir);
}
fs.delete(tmpDir, true);
} catch (IOException e) {
throw new ChunJunRuntimeException(
String.format(
"can't move file:[%s] to dir:[%s]", currentFilePath, outputFilePath),
e);
}
}
增量同步
增量同步主要针对某些只有 Insert 操作的表,随着业务增⻓,表内数据越来越多。如果每次都同步整表的话,消耗的时间和资源会⽐较多。因此需要⼀个增量同步的功能,每次只读取增加部分的数据。
● 实现原理
其实现原理实际上就是配合增量键在查询的 sql 语句中拼接过滤条件,⽐如 where id > ? ,将之前已经读取过的数据过滤出去。
增量同步是针对于两个及以上的同步作业来说的。对于初次执⾏增量同步的作业⽽⾔,实际上是整表同步,不同于其他作业的在于增量同步作业会在作业执⾏完成后记录⼀个 endLocation 指标,并将这个指标上传到 prometheus 以供后续使⽤。
除第⼀次作业外,后续的所有增量同步作业都会取上⼀次作业的 endLocation 做为本次作业的过滤依据(startLocation)。⽐如第⼀次作业执⾏完后,endLocation 为10,那么下⼀个作业就会构建出例如 SELECT id,name,age from table where id > 10 的 SQL 语句,达到增量读取的⽬的。
● 使用限制
· 只有 RDB 的 Reader 插件可以使⽤
· 通过构建SQL过滤语句实现,因此只能⽤于RDB插件
· 增量同步只关⼼读,不关⼼写,因此只与Reader插件有关
· 增量字段只能为数值类型和时间类型
· 指标需要上传到 prometheus,⽽ prometheus 不⽀持字符串类型,因此只⽀持数据类型和时间类型,时间类型会转换成时间戳后上传
· 增量键的值可以重复,但必须递增
· 由于使⽤ '>' 的缘故,要求字段必须递增
断点续传
断点续传是为了在离线同步的时候,针对⻓时间同步任务如超过1天,如果在同步过程中由于某些原因导致任务失败,从头再来的话成本⾮常⼤,因此需要⼀个断点续传的功能从任务失败的地⽅继续。
● 实现原理
· 基于 Flink 的 checkpoint,在 checkpoint 的时候 会存储 source 端最后⼀条数据的某个字段值,sink 端插件执⾏事务提交。
· 在任务失败,后续通过 checkpoint 重新运⾏时,source 端在⽣成 select 语句的时候将 state ⾥的值作为条件拼接进⾏数据的过滤,达到从上次失败位点进⾏恢复。
· jdbcInputFormat 在拼接读取 SQL 时,如果从 checkpoint 恢复的 state 不为空且 restoreColumn 不为空,则此时会将 checkpoint ⾥的 state 作为起点开始读取数据。
● 适用场景
通过上述原理我们可以知道 source 端必须是 RDB 类型插件,因为是通过 select 语句拼接 where 条件进⾏数据过滤达到断点续传的,同时断点续传需要指定⼀个字段作为过滤条件,且此字段要求是递增的。
· 任务需要开启 checkpoint
· reader 为 RDB 的插件均⽀持且 writer ⽀持事务的插件(如 rdb filesystem 等),如果下游是幂等性则 writer 插件也不需要⽀持事务
· 作为断点续传的字段在源表⾥的数据是递增的,因为过滤条件是 >
《数栈产品白皮书》:https://www.dtstack.com/resources/1004?src=szsm
《数据治理行业实践白皮书》下载地址:https://www.dtstack.com/resources/1001?src=szsm
想了解或咨询更多有关袋鼠云大数据产品、行业解决方案、客户案例的朋友,浏览袋鼠云官网:https://www.dtstack.com/?src=szbky
同时,欢迎对大数据开源项目有兴趣的同学加入「袋鼠云开源框架钉钉技术qun」,交流最新开源技术信息,qun号码:30537511,项目地址:https://github.com/DTStack
技术干货|如何利用 ChunJun 实现数据离线同步?的更多相关文章
- 【Bugly 技术干货】Android开发必备知识:为什么说Kotlin值得一试
1.Hello, Kotlin Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 1. ...
- 机器学习实战 - 读书笔记(14) - 利用SVD简化数据
前言 最近在看Peter Harrington写的"机器学习实战",这是我的学习心得,这次是第14章 - 利用SVD简化数据. 这里介绍,机器学习中的降维技术,可简化样品数据. 基 ...
- 【机器学习实战】第14章 利用SVD简化数据
第14章 利用SVD简化数据 SVD 概述 奇异值分解(SVD, Singular Value Decomposition): 提取信息的一种方法,可以把 SVD 看成是从噪声数据中抽取相关特征.从生 ...
- H5技术干货
H5技术干货 meta标签相关知识 H5页面窗口自动调整到设备宽度,并禁止用户缩放页面 <meta name="viewport" content="width=d ...
- 50篇经典珍藏 | Docker、Mesos、微服务、云原生技术干货
概念篇 全方位探(tian)索(keng)Mesos各种存储处理方式 老肖有话说@Mesos User Group第四次约会 技术实践 | Mesos 全方位“烹饪”指南 回顾 JAVA 发展轨迹,看 ...
- Git-【技术干货】工作中Git的使用实践
Git-[技术干货]工作中Git的使用实践 置顶 2019-09-17 21:02:16 web洋仔 阅读数 11444更多 分类专栏: Git 版权声明:本文为博主原创文章,遵循CC 4.0 B ...
- 分享iOS开发常用(三方类库,工具,高仿APP,实用网站,技术干货)
一 . JSONModel (三方类库会有更新,建议大家在线下载) http://pan.baidu.com/s/1i5ybP1z 二.AFNetworkiong http://pan.baidu. ...
- 【Bugly技术干货】那些年我们用过的显示性能指标
Bugly 技术干货系列内容主要涉及移动开发方向,是由 Bugly 邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 前言: 注:Google 在自己文 ...
- WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用
原文:WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经> ...
- 技术干货:实时视频直播首屏耗时400ms内的优化实践
本文由“逆流的鱼yuiop”原创分享于“何俊林”公众号,感谢作者的无私分享. 1.引言 直播行业的竞争越来越激烈,进过2018年这波洗牌后,已经度过了蛮荒暴力期,剩下的都是在不断追求体验.最近正好在做 ...
随机推荐
- Linux下如何重启Oracle
操作步骤 切换到oracle用户 su – oracle 通过sqlplus以管理员身份登录 sqlplus / as sysdba 然后执行 shutdown immediate 退出sqlplus ...
- webpack3使用additionalData和prependData都不管用
10.css相关配置 utils.js sass: generateLoaders('sass', { indentedSyntax: true, implementation: require('n ...
- 网络编程-Netty-writeAndFlush方法原理分析 以及 close以后是否还能写入数据?
前言 在上一讲网络编程-关闭连接(2)-Java的NIO在关闭socket时,究竟用了哪个系统调用函数?中,我们做了个实验,研究了java nio的close函数究竟调用了哪个系统调用,答案是clos ...
- APEX实战第3篇:如何完善项目基础功能
上一篇<APEX实战第2篇:构建自己第一个APEX程序>虽然有了程序,但实在是太单薄! 本篇将会介绍一些数据库的基础知识,演示如何通过函数.触发器.存储过程.视图等来完善项目的一些基础功能 ...
- Scanner的进阶使用——数字的输入
1.用Scanner输入数字(整数和小数) 1.定义一个整数变量 2.建立扫描器 3.使用if 4.建立电脑接收数据 5.设置else(那么)语法 6.关闭Scanner
- SLAM在机器人中的应用
SLAM在机器人中的应用 伴随着人工智能.机器人.无人驾驶等技术的蓬勃发展,越来越多的相关智能产品出现在了我们的日常生活中,作为底层技术基石之一的SLAM也逐渐被大家所熟知.下面通过"机器 ...
- 太赞了!两个技巧帮你记住复杂 Linux 命令!
Linux 经历这么多个年头了,其中命令越来越多,又加上参数的多种多样,就算是实打实的高手也没有十足的把握能把各种命令运用得炉火纯青,就别说那些初学者了. 面对这些复杂难记的命令,网上的一些工具如 K ...
- AIR780E引脚复用笔记
1.应用场景: 使用AIR780E模块驱动TM1637数码管驱动芯片,原有方案是AIR724UG+TM1637.为了降低成本,按照官方方案进行代码迁移. 伴随着代码迁移,硬件引脚也需要做相应调 ...
- 🎀dubbo QOS介绍及命令
简介 在Dubbo中,QoS(Quality of Service)功能是一个非常重要的特性,用于提供对运行时服务的查询和控制能力. QoS的概念源自网络设备中的服务质量保障机制,但在Dubbo中,它 ...
- 【踩坑系列】使用Comparator.comparing对中文字符串排序结果不对
1. 踩坑经历 假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为: 先根据订单量倒序排列,再根据城市名称正序排列. 示例代码: import lombok.Getter; import ...