数据仓库-业务数据采集(基于物流数仓)

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配置):

Flink-CDC增量数据同步-CSDN博客

 

 

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

        这里介绍了物流数仓的数据采集的流程和实现的详细方式,中间的组件部署的流程就自行查找吧,不然太冗长了。

        数据采集也是数仓的一大知识点,也是必须掌握的能力。

        有帮助的话请点个赞吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值