0、物流数仓架构
这里的数据采集的架构就是:在业务数据进入MySQL之后,部分表通过DataX全量同步到HDFS,部分表通过Flink-CDC的增量同步方式同步到Kafka,再通过Flume将数据从Kafka同步到HDFS;这两部分在HDFS的数据用于后续的数仓搭建。
数据采集用到的组件:MySQL+DataX+Hadoop+Flink+Kafka+Zookeeper+Flume
1、业务数据同步概述
1.1、数据同步策略概述
业务数据是数据仓库的重要数据来源,我们需要每日定时从业务数据库中抽取数据,传输到数据仓库中,之后再对数据进行分析统计。
为保证统计结果的正确性,需要保证数据仓库中的数据与业务数据库是同步的,离线数仓的计算周期通常为天,所以数据同步周期也通常为天,即每天同步一次即可。
数据的同步策略有全量同步和增量同步。
全量同步,就是每天都将业务数据库中的全部数据同步一份到数据仓库,这是保证两侧数据同步的最简单的方式。
业务数据库中不同日期的数据库中的数据
日期:2020-01-10 | 日期:2020-01-11 | 日期:2020-01-12 | |||||
Id | Name | Id | Name | Id | Name | ||
1 | 张三 | 1 | 张三 | 1 | 张三 | ||
2 | 李四 | 2 | 李小四 | 2 | 李小四 | ||
3 | 王五 | 3 | 王五 | ||||
4 | 赵六 | ||||||
5 | 田七 |
数据仓库增量同步到的数据
2020-01-10 | 2020-01-11 | 2020-01-12 | |||||
Id | Name | Id | Name | Id | Name | ||
1 | 张三 | 2 | 李小四 | 4 | 赵六 | ||
2 | 李四 | 3 | 王五 | 5 | 田七 | ||
1.2、数据同步策略选择
两种策略都能保证数据仓库和业务数据库的数据同步,那应该如何选择呢?下面对两种策略进行简要对比。
同步策略 | 优点 | 缺点 |
全量同步 | 逻辑简单 | 在某些情况下效率较低。例如某张表数据量较大,但是每天数据的变化比例很低,若对其采用每日全量同步,则会重复同步和存储大量相同的数据。 |
增量同步 | 效率高,无需同步和存储重复数据 | 逻辑复杂,需要将每日的新增及变化数据同原来的数据进行整合,才能使用 |
根据上述对比,可以得出以下结论:
若业务表数据量比较大,且每天数据变化的比例比较低,这时应采用增量同步,否则可采用全量同步。
下图为各表同步策略:这里可以看出左边的是维度表,基本不会有什么变化而且数据量小,右边的是事实表,数据量都比较大。
1.3、数据同步工具概述
数据同步工具种类繁多,大致可分为两类,一类是以DataX、Sqoop为代表的基于Select查询的离线、批量同步工具,另一类是以Maxwell、Canal、Flink-CDC为代表的基于数据库数据变更日志(例如MySQL的binlog,其会实时记录所有的insert、update以及delete操作)的实时流式同步工具。
全量同步通常使用DataX、Sqoop等基于查询的离线同步工具。而增量同步既可以使用DataX、Sqoop等工具,也可使用Maxwell、Canal、Flink-CDC等工具,下面对增量同步不同方案进行简要对比。
增量同步方案 | DataX/Sqoop | Maxwell/Canal/Flink-CDC |
对数据库的要求 | 原理是基于查询,故若想通过select查询获取新增及变化数据,就要求数据表中存在create_time、update_time等字段,然后根据这些字段获取变更数据。 | 要求数据库记录变更操作,例如MySQL需开启binlog。 |
数据的中间状态 | 由于是离线批量同步,故若一条数据在一天中变化多次,该方案只能获取最后一个状态,中间状态无法获取。 | 由于是实时获取所有的数据变更操作,所以可以获取变更数据的所有中间状态。 |
本项目中,全量同步采用DataX,增量同步采用Flink-CDC。
1.4、数据同步工具部署
1.4.1、DataX
DataX 是阿里巴巴开源的一个异构数据源离线同步工具,致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。
源码地址:GitHub - alibaba/DataX: DataX是阿里云DataWorks数据集成的开源版本。
Reader和Writer的具体参数可参考官方文档,地址如下:
https://github.com/alibaba/DataX/blob/master/README.md
DataX部署:略。
1.4.2、Flink-CDC
1、什么是CDC
CDC是Change Data Capture(变更数据获取)的简称。核心思想是,监测并捕获数据库的变动(包括数据或数据表的插入、更新以及删除等),将这些变更按发生的顺序完整记录下来,写入到消息中间件中以供其他服务进行订阅及消费。
2、CDC的种类
CDC主要分为基于查询和基于Binlog两种方式,我们主要了解一下这两种之间的区别:
基于查询的CDC | 基于Binlog的CDC | |
开源产品 | Sqoop、Kafka JDBC Source | Canal、Maxwell、Debezium |
执行模式 | Batch | Streaming |
是否可以捕获所有数据变化 | 否 | 是 |
延迟性 | 高延迟 | 低延迟 |
是否增加数据库压力 | 是 | 否 |
3、Flink-CDC
Flink社区开发了 flink-cdc-connectors 组件,这是一个可以直接从 MySQL、PostgreSQL 等数据库直接读取全量数据和增量变更数据的 source 组件。目前也已开源。
Flink-CDC相关的介绍:基于 Flink SQL CDC的实时数据同步方案
开源地址:https://github.com/ververica/flink-cdc-connectors
4、部署
就是写Flink代码,其中引入了CDC的依赖,然后打成jar包提交到服务器的Flink上运行。
<dependency>
<groupId>com.ververica</groupId>
<artifactId>flink-connector-mysql-cdc</artifactId>
<version>${flink-cdc.version}</version>
</dependency>
2、全量表数据同步
2.1、数据通道
全量表数据由DataX从MySQL业务数据库直接同步到HDFS,具体数据流向如下图所示。
注:目标路径中表名须包含后缀full,表示该表为全量同步目标路径中包含一层日期,用以对不同天的数据进行区分
2.2、DataX配置文件
我们需要为每张全量表编写一个DataX的json配置文件,此处以 base_organ为例,配置文件内容如下:
{
"job": {
"content": [
{
"reader": {
"name": "mysqlreader",
"parameter": {
"column": [
"id",
"name",
"region_id",
"area_code",
"iso_code",
"iso_3166_2"
],
"connection": [
{
"jdbcUrl": [
"jdbc:mysql://hadoop102:3306/tms?useUnicode=true&allowPublicKeyRetrieval=true&characterEncoding=utf-8"
],
"table": [
"base_organ"
]
}
],
"password": "000000",
"splitPk": "",
"username": "root"
}
},
"writer": {
"name": "hdfswriter",
"parameter": {
"column": [
{
"name": "id",
"type": "bigint"
},
{
"name": "org_name",
"type": "string"
},
{
"name": "org_level",
"type": "bigint"
},
{
"name": "region_id",
"type": "bigint"
},
{
"name": "org_parent_id",
"type": "bigint"
},
{
"name": "points",
"type": "string"
},
{
"name": "create_time",
"type": "string"
},
{
"name": "update_time",
"type": "string"
},
{
"name": "is_deleted",
"type": "string"
}
],
"compress": "gzip",
"defaultFS": "hdfs://mycluster",
"hadoopConfig":{
"dfs.nameservices":"mycluster",
"dfs.namenode.rpc-address.mycluster.nn2":"hadoop103:8020",
"dfs.namenode.rpc-address.mycluster.nn1":"hadoop102:8020",
"dfs.client.failover.proxy.provider.mycluster":"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider",
"dfs.ha.namenodes.mycluster":"nn1,nn2"
}
"fieldDelimiter": "\t",
"fileName": "base_organ",
"fileType": "text",
"path": "${targetdir}",
"writeMode": "truncate"
}
}
}
],
"setting": {
"speed": {
"channel": 1
}
}
}
}
注:由于目标路径包含一层日期,用于对不同天的数据加以区分,故path参数并未写死,需在提交任务时通过参数动态传入,参数名称为targetdir。
2.3、DataX配置文件生成
当MySQL中有多张业务数据表时,不可能花时间对每一张表都写一个对应的DataX的JSON配置文件,而且容易有个别配置文件出错,不易排查,直接用代码批量生成 DataX 的JSON配置文件最好。
就是写java代码,从MySQL数据库中读取对应的表名字段等信息,然后拼接成上方的DataX的JSON配置文件。将程序打成jar包,然后就可以通过jar包批量生成DataX配置文件。
代码:略
2.4、测试生成的DataX配置文件
以base_organ表为例,测试用脚本生成的配置文件是否可用。
1)创建目标路径
由于DataX同步任务要求目标路径提前存在,故需手动创建路径,当前base_organ表的目标路径应为/origin_data/tms/base_organ_full/2023-01-01。
[admin@hadoop102 bin]$ hadoop fs -mkdir -p /origin_data/tms/base_organ_full/2023-01-01
2)执行DataX同步命令
[admin@hadoop102 bin]$ python /opt/module/datax/bin/datax.py -p"-Dtargetdir=/origin_data/tms/base_organ_full/2023-01-01" /opt/module/datax/job/import/tms.base_organ.json
3)观察同步结果
观察HFDS目标路径是否出现数据。
2.5、全量表数据同步脚本
为方便使用以及后续的任务调度,此处编写一个全量表数据同步脚本。
1)在~/bin目录创建mysql_to_hdfs_full.sh
[admin@hadoop102 bin]$ vim ~/bin/mysql_to_hdfs_full.sh
脚本内容如下
#!/bin/bash
DATAX_HOME=/opt/module/datax
DATAX_DATA=/opt/module/datax/job
#清理脏数据
handle_targetdir() {
hadoop fs -rm -r $1 >/dev/null 2>&1
hadoop fs -mkdir -p $1
}
#数据同步
import_data() {
local datax_config=$1
local target_dir=$2
handle_targetdir "$target_dir"
echo "正在处理$1"
python $DATAX_HOME/bin/datax.py -p"-Dtargetdir=$target_dir" $datax_config >/tmp/datax_run.log 2>&1
if [ $? -ne 0 ]
then
echo "处理失败, 日志如下:"
cat /tmp/datax_run.log
fi
}
#接收表名变量
tab=$1
# 如果传入日期则do_date等于传入的日期,否则等于前一天日期
if [ -n "$2" ] ;then
do_date=$2
else
do_date=$(date -d "-1 day" +%F)
fi
case ${tab} in
base_complex | base_dic | base_region_info | base_organ | express_courier | express_courier_complex | employee_info | line_base_shift | line_base_info | truck_driver | truck_info | truck_model | truck_team)
import_data $DATAX_DATA/import/tms01.${tab}.json /origin_data/tms/${tab}_full/$do_date
;;
"all")
for tmp in base_complex base_dic base_region_info base_organ express_courier express_courier_complex employee_info line_base_shift line_base_info truck_driver truck_info truck_model truck_team
do
import_data $DATAX_DATA/import/tms01.${tmp}.json /origin_data/tms/${tmp}_full/$do_date
done
;;
esac
2)为mysql_to_hdfs_full.sh增加执行权限
[admin@hadoop102 bin]$ chmod +x ~/bin/mysql_to_hdfs_full.sh
3)测试同步脚本
[admin@hadoop102 bin]$ mysql_to_hdfs_full.sh all 2023-01-01
4)检查同步结果
查看HDFS目表路径是否出现全量表数据,全量表共12张。
2.6、全量表同步总结
全量表同步逻辑比较简单,只需每日执行全量表数据同步脚本mysql_to_hdfs_full.sh即可。
3、增量表数据同步
3.1、数据通道
3.2、Flink-CDC配置
通常,企业在搭建离线数仓后会搭建配套的实时数仓,本项目后续也会有配套的实时数仓。我们采用的是lambda架构,该架构下离线实时共用一套采集系统,采集程序作为ODS层App存在,因此,此处的项目结构与物流实时数仓一致。
Lambda架构总共由三层系统组成: 批处理层(Batch Layer), 速度处理层(Speed Layer),以及用于响应查询的 服务层(Serving Layer)。
Flink-CDC介绍和配置(查看其中的 2、Flink-CDC配置):
3.3、Flume配置
1)Flume配置概述
Flume需要将Kafka中各topic的数据传输到HDFS,因此选用KafkaSource以及HDFSSink。对于安全性要求高的数据(不允许丢失)选用FileChannel,允许部分丢失的数据如日志可以选用MemoryChannel以追求更高的效率。此处采集的是业务数据,不允许丢失,选用FileChannel,生产环境根据实际情况选择合适的组件。
KafkaSource订阅Kafka的 tms_ods主题的数据,HDFSSink将不同的数据写入不同路径,路径中应包含表名及日期,前者用于区分来源于不同业务表的数据,后者按天对数据进行划分。关键配置如下:
就是通过Flume的拦截器将body数据中的ts和tablename字段提取出来放到 head 中,用于后面将数据放到不同的HDFS路径。
具体数据示例如下:
2)Flume配置实操
(1)创建Flume配置文件
在hadoop104节点的Flume家目录下创建job目录,在job下创建tms_kafka_to_hdfs.conf。
[admin@hadoop104 flume]$ mkdir job
[admin@hadoop104 flume]$ cd job/
[admin@hadoop104 job]$ vim tms_kafka_to_hdfs.conf
(2)配置文件内容如下
a1.sources = r1
a1.channels = c1
a1.sinks = k1
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
a1.sources.r1.batchSize = 5000
a1.sources.r1.batchDurationMillis = 2000
a1.sources.r1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
a1.sources.r1.kafka.topics = tms_ods
a1.sources.r1.kafka.consumer.group.id = tms-flume
# 拦截器
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = com.admin.tms.flume.interceptors.TimestampAndTableNameInterceptor$Builder
a1.channels.c1.type = file
a1.channels.c1.checkpointDir = /opt/module/flume/checkpoint/tms
a1.channels.c1.dataDirs = /opt/module/flume/data/tms
a1.channels.c1.maxFileSize = 2146435071
a1.channels.c1.capacity = 1000000
a1.channels.c1.keep-alive = 6
## sink1
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = /origin_data/tms/%{tableName}_inc/%Y-%m-%d
a1.sinks.k1.hdfs.filePrefix = db
a1.sinks.k1.hdfs.round = false
a1.sinks.k1.hdfs.rollInterval = 10
a1.sinks.k1.hdfs.rollSize = 134217728
a1.sinks.k1.hdfs.rollCount = 0
a1.sinks.k1.hdfs.fileType = CompressedStream
a1.sinks.k1.hdfs.codeC = gzip
## 拼装
a1.sources.r1.channels = c1
a1.sinks.k1.channel= c1
(3)编写Flume拦截器
① 创建名为tms-flume-interceptor的项目
② 在pom文件中添加如下内容。
<dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.10.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
③ 在com.admin.tms.flume.interceptors包下创建TimestampAndTableNameInterceptor类
package com.admin.tms.flume.interceptors;
import com.alibaba.fastjson.JSONObject;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class TimestampAndTableNameInterceptor implements Interceptor {
@Override
public void initialize() {
}
@Override
public Event intercept(Event event) {
Map<String, String> headers = event.getHeaders();
String log = new String(event.getBody(), StandardCharsets.UTF_8);
JSONObject jsonObject = JSONObject.parseObject(log);
Long ts = jsonObject.getLong("ts");
String tableName = jsonObject.
getJSONObject("source").getString("table");
headers.put("timestamp", ts + "");
headers.put("tableName", tableName);
return event;
}
@Override
public List<Event> intercept(List<Event> events) {
for (Event event : events) {
intercept(event);
}
return events;
}
@Override
public void close() {
}
public static class Builder implements Interceptor.Builder {
@Override
public Interceptor build() {
return new TimestampAndTableNameInterceptor();
}
@Override
public void configure(Context context) {
}
}
}
④ 打包
⑤ 将打好的包放入到hadoop104的/opt/module/flume/lib文件夹下
[admin@hadoop104 lib]$ ls | grep tms-flume-interceptor
tms-flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar
3)通道测试
(1)启动Zookeeper、Kafka集群
(2)启动hadoop104的Flume
[admin@hadoop104 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/tms_kafka_to_hdfs.conf -Dflume.root.logger=INFO,console
(3)执行模拟数据jar包,生成数据
按照上文说明修改application.yml中的配置项后执行jar包。
[admin@hadoop102 bin]$ java -jar tms-mock-2023-01-06.jar
(4)观察HDFS上的目标路径,如下。
增量表目录已生成。
(5)观察数据日期
与Flink-CDC Job启动时指定的日期相同。
4)编写Flume启停脚本
为方便使用,此处编写一个Flume的启停脚本
(1)在hadoop102节点的/home/admin/bin目录下创建脚本tms-f1.sh
[admin@hadoop102 bin]$ vim tms-f1.sh
在脚本中填写如下内容
#!/bin/bash
case $1 in
"start")
echo " --------启动 hadoop104 业务数据flume-------"
ssh hadoop104 "nohup /opt/module/flume/bin/flume-ng agent -n a1 -c /opt/module/flume/conf -f /opt/module/flume/job/tms_kafka_to_hdfs.conf > /opt/module/flume/tms-f1.log 2>&1 &"
;;
"stop")
echo " --------停止 hadoop104 业务数据flume-------"
ssh hadoop104 "ps -ef | grep tms_kafka_to_hdfs | grep -v grep |awk '{print \$2}' | xargs -n1 kill"
;;
esac
(2)增加脚本执行权限
[admin@hadoop102 bin]$ chmod +x tms-f1.sh
3.4、增量表同步总结
Flink-CDC Job在启动后会先执行一次全表扫描,然后从binlog的最新位置读取。生产环境下全表扫描的数据会依据扫描时间进入对应日期的目录,从binlog同步到的数据会依据binlog中记录的数据变更时间进入对应目录,因此在数仓上线首日启动增量采集通道即可,如果没有特殊情况后续无须重启,且不需要通过mock_date指定数据日期。
本地环境下,即自己用程序模拟业务数据时,首次启动时,生成数据的时间mock_date应为假定的数仓上线首日,模拟每日数据前需要重启Job,并将日期更换为模拟数据的日期,修改application.yml中的日期,待通道重启完毕后再生成模拟数据。
4、总结
这个物流数仓的数据采集模块是尚硅谷的教学视频。
在B站的视频:01_项目概述_哔哩哔哩_bilibili
这里介绍了物流数仓的数据采集的流程和实现的详细方式,中间的组件部署的流程就自行查找吧,不然太冗长了。
数据采集也是数仓的一大知识点,也是必须掌握的能力。
有帮助的话请点个赞吧!