使用DataWorks进行数仓开发(一)

引言

DataWorks是阿里云的一款大数据开发治理平台,基于阿里云配套的MaxCompute、EMR等分布式计算引擎,针对大数据开发、数据分析、大数据集成、数据建模、湖仓一体五大场景提供解决方案。从本文开始预计会花上三个篇幅,重点介绍和分享在大数据开发场景下使用DataWorks进行数据和机器学习模型开发。DataWorks大数据开发场景下有5个模块:

  1. 数据集成:从外部数据源同步数据到数仓或将数仓数据反向同步到外部数据源。
  2. DataStudio:进行代码开发、节点运行配置、版本管理、冒烟测试、版本发布等工作。
  3. 运维中心:对已发布到生产环境各节点的运行状态进行运维和监控,为各节点配置运行的智能基线、报警规则。
  4. 数据质量:为每个节点输出的MaxCompute表内部数据配置监控规则,保证上下游数据的质量。
  5. 数据服务:基于各节点输出的MaxCompute表生成API对外提供数据服务,通过简单的配置就可以对外提供数据接口,节省开发成本。

在本系列的3个篇幅中,计划第一篇(本文)介绍数据集成DataStudio两个模块,第二篇介绍DataWorks运维中心和数据质量,第三个篇介绍数据服务。

DataWorks基本概念

首先我们要对DataWorks相关名词、术语和使用流程有个基本认识。本系列文章介绍的内容基于数仓分布式计算场景,数据位于MaxCompute数仓表 中。在一个业务流程 下,可以在最上游通过离线同步节点 以特定频率从外部数据源如线上商城MySql业务库、kafka消息队列等,将业务数据同步到数仓的数据引入层 (ODS)。ODS层为原始的业务数据,数据质量不高,需要通过下游计算节点 进行清洗和加工让数据流入数据公共层 (CDM),此时的数据经过ETL之后质量更高,以宽表和维度表的形式存在于CDM层中。在宽表和维度表的基础上可以进行特征提取,新建机器学习模型训练和预测节点对相关业务进行预测,并将结果输出到CDM层的DWS表中。最后根据业务需求对数据进行汇总流入到数据应用层(ADS)对外提供数据服务。

图1 MaxCompute数仓数据加工流程

MaxCompute数仓表以空间 进行隔离,空间可以理解为数据库,每个空间可以分别配置生产和开发两套环境。在空间下可以按具体的业务新建业务流程,每个业务流程下可以按照数据的流向新建各个离线同步和计算节点。所谓的节点实际上是一段按时运行的程序,由计算代码和调度配置 两个部分组成。在调度配置中可以给每个节点配置上游依赖、运行参数、重跑属性、下游输出。节点上线后调度资源组 会按依赖和输出进行调度,使用计算资源组 的资源进行计算,在每个运行周期会生成一个运行实例。每个节点可以配置一个或多个输出,按照调用规范节点和输出最好是一对一,输出与数仓的表进行关联。节点运行成功后实现对数仓表的修改和新增。

数据集成

首先介绍一下数据集成,在数据集成下可以使用离线/实时同步节点将外部数据源数据同步到MaxCompute数仓,或将数仓数据反向同步到外部数据源。数据集成过程相对容易,只需要简单的配置就可以完成,整个过程可以分为以下3步:

  1. 配置数据源;
  2. 在数仓建表并配置同步节点;
  3. 冒烟测试和上线。

下面以Kafka数据同步到数仓和数仓数据同步到MySql数据库两个例子介绍实现细节。

将Kafka数据同步到数仓

配置Kafka数据源

首先进入到数据集成->数据源->新增数据源->选择Kafka数据源:

图2.1 配置Kafka数据源-1

然后配置Kafka数据源属性:

图2.2 配置Kafka数据源-2

在kafka配置选项中有两种模式分别为阿里云实例模式连接串模式,如果使用的是阿里云配套的Kafka集群,使用实例模式配置更方便;如果使用的是第三方Kafka集群,则需要使用连接串模式,在连接串模式下配置Kakfa集群地址。

最后测试连通性:

图2.3 配置Kafka数据源-3

如果无法连通,可以按照下方的注意事项进行排查,多数情况可能是DataWorks空间和数据库没有彼此添加白名单,可以联系具有运维角色的相关人员进行处理

配置同步节点

Kafka数据源配置成功后,下面介绍如何配置Kafka离线同步节点: 首先在DataStudio中新建一个业务流程,并在业务流程下新建一张表,这里的表并不是真正意义上的数仓表,而是DataWorks图形界面下的一种虚拟的逻辑表,在表中设好所需的字段和分区。

图3 新建数据集成同步节点

然后再在数据集成中新建离线同步节点,可选配置模式有两种:向导模式脚本模式,以下示例采用脚本模式,节点实际上是一个配置文件,配置两个step和对应的order:

json 复制代码
{
    "type": "job",
    "version": "2.0",
    "steps": [
        {
            "stepType": "kafka",
            "parameter": {
                "server": "myIP1, myIP2, myIP3",
                "kafkaConfig": {
                    "group.id": "myGroupID"
                },
                "skipExceedRecord": "true",
                "valueType": "ByteArray",
                "column": [
                    "__key__",
                    "__value__",
                    "__partition__",
                    "__timestamp__",
                    "__offset__"
                ],
                "topic": "myTopicName",
                "beginDateTime": "${busi_date_last}000000",
                "keyType": "ByteArray",
                "endDateTime": "${busi_date}000000",
                "waitTime": "10"
            },
            "name": "Reader",
            "category": "reader"
        },
        {
            "stepType": "odps",
            "parameter": {
                "partition": "ptdd=${busi_date_last}",
                "truncate": true,
                "datasource": "odps_first",
                "column": [
                    "kfk_key",
                    "kfk_value",
                    "kfk_partition",
                    "kfk_timestamp",
                    "kfk_offset"
                ],
                "table": "myTableName"
            },
            "name": "Writer",
            "category": "writer"
        }
    ],
    "setting": {
        "errorLimit": {
            "record": "4"
        },
        "locale": "zh_CN",
        "speed": {
            "throttle": false,
            "concurrent": 2
        }
    },
    "order": {
        "hops": [
            {
                "from": "Reader",
                "to": "Writer"
            }
        ]
    }
}

其中4~29配置了kafka源数据step,包括第8行kafka集群IP、第10行groupID、第21行TopicName、22 ~ 24行单个周期消费消息对应的时间区间,{busi_date_last}和{busi_date}是节点配置的两个运行参数;28行设置step的类型是从kafka读取数据。

30到47配置数仓接收属性,包括43行接收表名、46行step类型为写入。59~64制定step的顺序,这样就实现了每个周期从kafka中消费一个时间区间的消息,然后将数据写入数仓表的单个分区中

接下来在调度配置中设置节点的参数{busi_date_last}和{busi_date}、 重跑属性、依赖和输出,由于本节点是最上游节点,需要依赖空间的根节点。最后提交进行冒烟测试,测试通过后发布上线。

将数仓数据反向同步到MySql

下面介绍如何将数仓计算好的数据反向同步 到外部MySql数据库供业务使用。基本流程与前面介绍的正向同步类似,先新建一个MySql数据源测试连通,然后在数据集成下配置一个离线同步节点,这个示例下采用向导模式进行配置。

首先配置数据来源和去向:

图4.1 向导模式配置同步节点-1

然后配置字段映射关系:

图4.2 向导模式配置同步节点-2

最后同样配置调度,提交进行冒烟测试后发布上线。

数据开发

上一章数据集成介绍了MaxCompute数仓与外部数据源进行数据同步的方法,外部数据在落到数仓后可进行相应的数据应用开发,产出各类数据报表和数据服务。数据开发占据了数仓90%以上的工作。本章我们将介绍在DataWorks平台上进行MaxCompute数仓开发和机器学习模型开发的方法。数仓的程序运行在各个计算节点上,根据程序实现方式的不同,DataWorks数仓计算节点可以分为ODPS SQL、PyODPS、ODPS Spark、ODPS MR四类(ODPS是MaxCompute的旧名),其中ODPS MR平时使用较少,本文中主要介绍前三类节点。

图5 DataWorks数据开发中几类计算节点

ODPS SQL

这是最简单的也是使用频率最高的一类节点,表数据计算逻辑通过MaxCompute Sql实现,结合DML语句(INSERT)对数仓表进行操作。只需要写一段Sql、配置调度依赖即可实现,在这就不过多的进行介绍。

PyODPS

PyODPS节点主要用于MaxCompute数仓机器学习模型开发场景,PyODPS可以看作是Python主程序的入口。在进行PyODPS开发时建议使用阿里云官方的ODPSMars API ,这是因为PyODPS是运行在MaxCompute引擎调度资源 组上的,ODPS和Mars为官方开发的分布式计算框架,运行主要依赖计算资源。新手小白喜欢使用第三方库如Pandas、Sklearn等单机版的框架进行开发,由于PyODPS程序在调度资源组上运行,这样开发实际执行过程往往是:先从数仓拉数到调度资源组,然后在调度资源组完成绝大部分Python计算过程。这种过程至少会带来两个方面的隐患:

  1. 将数仓数据拉入调度资源组IO开销较大;
  2. 大量的占用了调度资源组的内存资源,会导致其他节点的调度任务被拖住,而本应该负责计算的计算资源组资源没有得到好的应用。

与Spark等分布式计算框架类似,ODPS和Mars API是缓执行的,计算过程发生在集群的计算资源组上。需要触发算子进行触发才能调动计算任务的执行。Mars API在设计上与Numpy、Pandas、Sklearn非常一致,对应关系为:

mars.tensor => numpy

mars.dataframe => pandas

mars.learn => sklearn

此外mars还能与tensorflow进行集成,通过run_tensorflow_script()参数可以在集群上运行tensorflow脚本:

Python 复制代码
from mars.learn.contrib.tensorflow import run_tensorflow_script
run_tensorflow_script('tf_test.py', n_workers=2)

当节点上计算逻辑较为复杂时,将Python代码全集中在节点文件中会导致程序难以维护,需要按照一定的设计模式将代码分别封装在不同的.py文件或包中,具体实现途径有两种:

  1. 在DataWorks下新建.py文件,引入到PYODPS工作空间进行使用;
  2. 在本地将使用的文件打包后上传到DataWorks资源,然后在PYODPS中进行调用。

在第1种方法使用resource_reference{}和sys.path.append()将.py文件引入到工作空间,然后在PYODPS节点中进行调用。

python 复制代码
# 假如有a.py 和 b.py两个资源文件

##@resource_reference{"a.py"}
##@resource_reference{"b.py"}
import sys
sys.path.append(os.path.dirname(os.path.abspath("a.py")))
sys.path.append(os.path.dirname(os.path.abspath("b.py")))
from a import *

特别要注意的是,DataWorks资源没有多级目录的结构,在同一个空间下不同业务流程中资源名要唯一

第2种方法 需要在本地Python环境中安装0.11.3及以上版本的Pyodps,使用!pyodps-pack命令将python代码或第三方库打包。

python 复制代码
# docker模式打包第三方库
!pyodps-pack pandas

# 无docker模式打包第三方库
!pyodps-pack --without-docker pandas

打包完成后会在python当前运行目录下生成一个packages.tar.gz文件,可以使用-o, --output xxx参数制定输出包名。

!pyodps pack命令分为docker模式和无docker模式,强烈建议在本地安装docker,使用docker模式进行打包。打包用户自定义的python包时可以在包所在的目录下添加一个pyproject.toml文件,添加自定义包所需的第三方依赖,假定自定义包的目录结构如下:

markdown 复制代码
my_package_root
├── utils
│   ├── __init__.py
│   ├── connect.py
│   └── subutils
│       ├── __init__.py
│       └── add.py
└── pyproject.toml

pyproject.toml文件内容可以为

ini 复制代码
[project]
name = "utils"
description = "pyodps-pack utils package"
version = "0.1.0"
dependencies = [
    "pandas>=1.1.2"
]

然后使用pyodps-pack命令打包自定义包:

bash 复制代码
!pyodps-pack /<package_path>/my_package_root

同时也可以在本地使用pyodps-pack命令打包远程的git仓库代码:

perl 复制代码
!pyodps-pack git+https://github.com/xxx/xxx.git

接着将打好的包上传至DataWorks Archive资源:

图6-1 使用DataWorks图形界面上传Archive包

如果包的大小超过50M,可以使用odpscmd的jar add命令先将包上传至MaxCompute,再从MaxCompute上传至DataWorks数据开发

图6-2 使用odpscmd命令上传包

图6-3 在DataWorks上将MaxCompute资源添加到数据开发

资源上传提交之后,在PYODPS节点中使用以下方式全局配置第三方包。

Python 复制代码
# 全局配置第三方包
from odps import options
options.df.libraries = ['packages.tar.gz', 'xxx.whl']

然后在PyODPS节点中进行调用:

Python 复制代码
from odps import options
options.df.libraries = ['xx.tar.gz', 'xxx.whl']
def func(x):
    # 在函数内部 import,防止二进制包结构差异报错
    from xx import func
    return func(x)
# 假如packages中
df['col1'].map(func)

ODPS Spark

上一节中介绍的PyODPS计算节点使用有一定的局限性,代码开发和执行并未完全分离。其中目录和文件操作相对较麻烦,制作第三方包在PyODPS节点进行调用也很繁琐。这种模式在较小的机器学习项目上还行,当项目比较大时就难以适用。DataWorks和MaxCompute支持使用Spark进行开发,使用Java或Scala进行开发相对来说自由度更高,而且本地开发完成之后将整个项目编译打成一个Jar包,环境相对来说更可控也更友好。

Spark on MaxCompute在原装Spark上加入了对MaxCompute数仓和OSS对象存储的支持。目前有1.6.3、2.3.0等几个版本。平常使用较多的是2.3.0的版本,首先在本地安装好JDK 8和Spark On MaxCompute 2.3.0,使用maven配置如下依赖环境:

xml 复制代码
<properties>
    <spark.version>2.3.0</spark.version>
    <cupid.sdk.version>3.3.8-public</cupid.sdk.version>
    <scala.version>2.11.8</scala.version>
    <scala.binary.version>2.11</scala.binary.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <scope>provided</scope>
        <exclusions>
            <exclusion>
                <groupId>org.scala-lang</groupId>
                <artifactId>scala-library</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.scala-lang</groupId>
                <artifactId>scalap</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <scope>provided</scope>
    </dependency>
    
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-mllib_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <scope>provided</scope>
    </dependency>
    
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-streaming_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.aliyun.odps</groupId>
        <artifactId>cupid-sdk</artifactId>
        <version>${cupid.sdk.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>com.aliyun.odps</groupId>
        <artifactId>hadoop-fs-oss</artifactId>
        <version>${cupid.sdk.version}</version>
    </dependency>

    <dependency>
        <groupId>com.aliyun.odps</groupId>
        <artifactId>odps-spark-datasource_${scala.binary.version}</artifactId>
        <version>${cupid.sdk.version}</version>
    </dependency>

    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
        <version>${scala.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-actors</artifactId>
        <version>${scala.version}</version>
    </dependency>
    
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-xml</artifactId>
        <version>2.11.0-M4</version>
    </dependency>
    <!-- datahub streaming依赖 -->
    <dependency>
        <groupId>com.aliyun.emr</groupId>
        <artifactId>emr-datahub_${scala.binary.version}</artifactId>
        <version>1.6.0</version>
    </dependency>

    <dependency>
        <groupId>com.aliyun.datahub</groupId>
        <artifactId>aliyun-sdk-datahub</artifactId>
        <version>2.9.4-public</version>
        <exclusions>
            <exclusion>
                <groupId>net.jpountz.lz4</groupId>
                <artifactId>lz4</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-streaming-kafka-0-10_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <exclusions>
            <exclusion>
                <groupId>net.jpountz.lz4</groupId>
                <artifactId>lz4</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql-kafka-0-10_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
        <exclusions>
            <exclusion>
                <groupId>net.jpountz.lz4</groupId>
                <artifactId>lz4</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>com.aliyun.odps</groupId>
        <artifactId>streaming-lib</artifactId>
        <version>3.3.8-public</version>
        <exclusions>
            <exclusion>
                <groupId>net.jpountz.lz4</groupId>
                <artifactId>lz4</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.scala-lang</groupId>
                <artifactId>scala-library</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.scala-lang</groupId>
                <artifactId>scalap</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>org.apache.zeppelin</groupId>
        <artifactId>spark-interpreter</artifactId>
        <version>0.8.1</version>
    </dependency>

    <dependency>
        <groupId>com.aliyun.oss</groupId>
        <artifactId>aliyun-sdk-oss</artifactId>
        <version>3.10.2</version>
    </dependency>

    <dependency>
        <groupId>net.sourceforge.dynamicreports</groupId>
        <artifactId>dynamicreports-core</artifactId>
        <version>3.0.4</version>
    </dependency>
    
    <dependency>
        <groupId>org.jetbrains</groupId>
        <artifactId>annotations</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>
    
    <dependency>
        <groupId>org.jetbrains</groupId>
        <artifactId>annotations</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>

    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.2</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>com.aliyun.odps</groupId>
        <artifactId>cupid-sdk</artifactId>
        <version>${cupid.sdk.version}</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>net.sourceforge.dynamicreports</groupId>
        <artifactId>dynamicreports-core</artifactId>
        <version>3.0.4</version>
    </dependency>

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.15</version>
        <scope>provided</scope>
    </dependency>

    <dependency>
        <groupId>org.jpmml</groupId>
        <artifactId>jpmml-evaluator-spark</artifactId>
        <version>1.3.0</version>

    </dependency>

    <dependency>
        <groupId>ml.dmlc</groupId>
        <artifactId>xgboost4j-spark</artifactId>
        <version>0.81</version>
    </dependency>


</dependencies>

相比于官方提供的2.3.0标准环境,这个环境在212~223行添加了pmml和xgboost依赖,对应的版本能与Spark-2.3.0兼容。其中jpmml用于在jvm环境进行跨平台机器学习模型调用;xgboost4j-spark用于在Spark中使用xgboost。

使用上述环境首先在本地完成ODPS Spark程序的开发,先切少量数据确保程序在local模式下运行正常。然后使用maven将整个项目打成jar包,接下来可以在本地使用yarn-cluster模式先提交进行测试:在terminal中使用spark-submit命令将target目录下的xxx-1.0-SNAPSHOT-shaded.jar提交测试。运行日志如下:

图7-1 本地yarn-cluster模式运行日志

在本地使用yarn-cluster提交测试通过后,接着使用odpscmd将xxx-1.0-SNAPSHOT-shaded.jar包上传至DataWorks数据开发,新建ODPS Spark节点并配置如下的通用参数:

图8-1 新建ODPS Spark节点-1

图8-2 新建ODPS Spark节点-2

其中与Spark运行相关的参数中可以在代码中SparkSession创建环节进行配置,在节点中配置先生效,代码中定义的参数后生效,后生效的参数会对之前生效的参数进行覆盖

和其他类型节点一样,在配置好调度参数后提交节点,然后进行冒烟测试,根据冒烟测试日志中的logview url可以进入到Spark运行日志页面,在Spark运行日志页面可以查看执行图、程序输出、Spark UI等信息。

图9-1 ODPS Spark运行日志-1

点击下方worker和master的StdOut可以查看worker和master节点的输出,程序中如下类似的代码输出均在此打印。

java 复制代码
df.show();
df.printSchema();

system.out.println(Arrays.toString(arr));

图9-2 ODPS Spark运行日志-2

点击上方的Summary可以看到Spark UI的地址,进入Spark UI可以实时监测各job和task的运行状况。

图9-3 ODPS Spark运行日志-3

冒烟测试无误后,将节点和对应资源发布至生产环境。

小结

我们计划用三个篇幅分享在DataWorks中进行大数据开发和机器学习模型搭建的经验。本文介绍了离线同步DataStudio数据开发 两个模块,其中离线同步内容相对比较简单,数据开发部分有ODPS SQL、PYODPS、ODPS Spark三种类型的计算节点,对应3种不同的开发方式,简单的表格计算建议使用ODPS SQL、小型的机器学习模型建议使用PYODPS、大型的机器学习项目建议使用ODPS Spark (优先使用Java或Scala开发)。下一篇将为大家介绍DataWorks运维中心数据质量模块,这两个模块主要是一些配置,相对来说更简单。

相关推荐
张先shen2 小时前
Elasticsearch深度分页解决方案:search_after原理剖析
大数据·elasticsearch·搜索引擎
泊浮目2 小时前
生产级Rust代码品鉴(一)RisingWave一条SQL到运行的流程
大数据·后端·rust
vivo互联网技术3 小时前
vivo Pulsar 万亿级消息处理实践(3)-KoP指标异常修复
java·大数据·服务器·后端·kafka·消息队列·pulsar
武子康3 小时前
大数据-36 HBase 增删改查 列族详解 实测
大数据·后端·hbase
rui锐rui4 小时前
大数据学习1:Hadoop单机版环境搭建
大数据
Fireworkitte4 小时前
ES 压缩包安装
大数据·elasticsearch
UI设计和前端开发从业者4 小时前
大数据时代UI前端的智能化转型之路:以数据为驱动的产品创新
大数据·前端·ui
智海观潮15 小时前
Flink CDC支持Oracle RAC架构CDB+PDB模式的实时数据同步吗,可以上生产环境吗
大数据·oracle·flink·flink cdc·数据同步
企企通采购云平台15 小时前
「天元宠物」×企企通,加速数智化升级,“链”接萌宠消费新蓝海
大数据·人工智能·宠物