本文作者 孔亮 阿里云智能 产品专家
直播视频请点击 直播观看
一、MaxCompute 基础介绍
阿里云数据与分析产品解决方案
MaxCompute作为企业级数据仓库服务,集中存储和管理企业数据资产、面向数据应用处理和分析数据,将数据转换为业务洞察。通过与阿里云内、外部服务灵活组合,可构建丰富的数据应用。全托管的数据与分析解决方案,可简化平台运维、管理投入,提升面向业务的服务能力,加速价值实现。
从下图可以看出 MaxCompute 是处于一个核心位置,首先 MaxCompute 是一个数据仓库,而且是一个基于 Serverless架构超大规模集群产品,具备安全管理能力等企业级能力。之前是偏离线数据处理平台,当前已经具备BI分析能力的企业级数据仓库。
一般企业的离线数据链路,从数据源包含关系型数据库、非结构化存储、大数据存储、消息队列等等,都可以通过数据集成离线的方式,批的方式,进入到数据仓库中。前端的各种分析应用,也可以有一些实时的分析,通过 MaxCompute-Hologres做查询加速,这是离线的场景。实时链路是从消息队列数据源,通过Datahub数据总线,到实时计算Flink,对接到实时数仓Hologres,再对接到前台。中间的数据是通过 Hologres 和 MaxCompute 还有 Flink,做流批一体。MaxCompute在数据仓库的基础上,扩展了数据库的一些联邦查询能力,包括Data Lake、Mysql、Hbase等,数据的集成,包括元数据的同步和查询去同步数据数据湖数据处理的能力。还包含基于数据仓库数据之后,人工智能PAI机器学习的能力。构成了完整的大数据底座。在这之上,DataWorks提供了,一站式的开发治理平台,可以做任务调度、元数据数据数据质量的血缘管理、数据开发等能力。
MaxCompute 大数据计算服务简介
MaxCompute(大数据计算服务)是一款多功能、高性能、易于使用的数据仓库服务。
MaxCompute内建完善的企业级安全及管理功能、支持开放数据生态,以统一平台满足多使用场景(数据仓库/BI、数据湖分析、机器学习)需要,被广泛用于数据化运营、画像及推荐、智能预测等应用场景。
MaxCompute 底层有统一Iass层的存储和计算调度,存储是盘古,调度是伏羲,把存储跟计算资源做了一层封装,用资源池的方式对上层应用无感知的提供使用,上层应用只需要使用这个资源组,而不需要知道具体的任务运行在什么资源上面。MaxCompute 可以提供结构化数仓的存储能力,也可以提供数据湖包括开放格式、半结构化、非结构化等数据处理的能力。对于用户来说,所有使用场景都在项目里面,每个项目之间租户隔离,可以有本项目的资源,也可以共享资源。项目直接通过安全共享的方式,可以同步数据。再上层用统一的访问认证、管理、安全、监控、元数据等能力。
可以总结出以下几点:
- 简单易用的SQL 端到端开发方式,支持Spark,分布式Python(Mars)等开源技术栈,内置完善的企业管理功能和开放的接口,简单易用开放
- 统一元数据、统一数据存储,一份统一的企业数据资产,云原生的多租户系统,最高效的跨业务&跨组织数据连接与共享
- 自适应的按需弹性资源,精准匹配业务规模的变化,避免资源浪费或不足,业务负载隔离,消除业务间资源争抢
- 存储与计算独立伸缩,支持TB到EB级的存储扩展;连接广泛外部数据源,开展联邦计算
- 深度优化,自动数仓,集成多年双11优化能力,智能调优+专家服务支持
MaxCompute 功能介绍
MaxCompute 功能可以分为下图几类。最核心的存储计算能力是不对外开发的。存储主要用的是数据库表,计算资源会在SQL任务或者其他计算模型上使用时体现出来。看SQL计算能力,可以端到端完成整个数仓的数据处理和数据模型管理等能力,包括一些基础的数据类型,内部表做一些分区,外部表处理非机构化数据等。支持流式写入,流式upsert插入数据,删改数据等能力。查询时可以用非常复杂的查询方法,可以看解析计划Explain。UDF侧,MaxCompute支持Java UDF和Python UDF,还包括内容安全UDF。
管理能力,下图深色部分是在专有云里会有独立的增强包来对企业提供额外的计算能力。公共云上是直接Serverless方式提供给用户,只收资源费用。 管理能力也包含计量计费的能力,包括预付费和按量计费。任务有任务管理,查询有查询加速,专有云部署时,大规模跨域计算等企业级能力。MaxCompute除了SQL引擎外,还有向量检索,TensorFlow,Spark,Mars,Hologres,这些都可以基于MaxCompute的底层存储计算资源,用不同的引擎,提供对应场景的能力。合规治理部分,灰色部分有一些是DataWorks提供,包括本身数仓安全管理能力,元数据管理能力,审计能力,数据加密,数据脱敏,数据质量等功能。再通过SDK/API和MaxCompute提供的配套工具来完成数据开发,数据上传下载,还有一些三方应用,二方应用来完成整个数仓的生态构建。
MaxCompute 主要解决方案
企业数据仓库/数据中台
将原始数据整合为可被广泛使用的知识,用于后续消费使用,包括:
•集成存储:收集、存储和集中管理企业内外数据;
•处理分析:清洗、加工、整合多方数据;面向业务需求统计、挖掘;统一的存储和处理可以提供弹性伸缩的存储计算能力,减少成本
•标准化:建立企业数据仓库模型(分层/分主题),建立数据标准,形成可复用数据资产,并且通过数据治理,进行数据生命周期安全、成本治理等、持续保障数据质量和标准化
•数据互通:在企业内流转共享标准数据,打通数据孤岛,让关联的数据发挥更大的价值
数据中台不仅是技术平台,还包含组织和管理流程要求,强调以公共数据产品服务业务,实现”数据业务化”,可认为数据仓库的一种最佳实践。MaxCompute+DataWorks是开箱即用的数据仓库解决方案。
BI分析/数据分析
BI分析并不必然要使用数据仓库,如可直接基于交易数据库分析
数据仓库能够帮助提供BI分析需要的企业视角的全面数据
通过数据仓库的数据资产管理,BI分析人员可更好地检索、理解数据
数据仓库还能够以强大的性能,满足多用户并发、分析不同数据规模需求
MaxCompute提供数据的集中管理、处理分析,可直接对接BI或者将数据同步到外部分析型数据库(OLAP)进行BI分析
预测分析/智能应用
数据仓库与AI集成日益紧密
数据仓库为机器学习进行数据加工、数据准备
机器学习对数据进行模型训练,数据预测,结果可直接固化在数据仓库进行知识共享,如用户画像分析对客户性别、偏好的预测
MaxCompute无缝集成PAI、SparkML,1个平台无需数据移动即可在企业数据之上建设基于机器学习的智能应用,如CTR预估、个性化推荐
二、MaxCompute半结构化数据处理
什么是半结构化数据
本文的主题是MaxCompute半结构化处理能力,我们先看一下什么是半结构化数据。
结构化数据,即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据
非结构化数据,包括所有格式的办公文档、文本、图片、XML、HTML、各类报表、图像和音频/视频信息等等
半结构化数据,就是介于完全结构化数据(如关系型数据库、面向对象数据库中的数据)和完全无结构的数据(如声音、图像文件等)之间的数据,HTML文档就属于半结构化数据。它一般是自描述的,经常变化的,数据的结构和内容混在一起,一般由一个三元组表示,包括标记、类型和对象的值。
通过数据模型比较:
结构化数据:关系型(二维表)
半结构化数据:由一个由节点集合和弧段集合组成的具根有向图结构。(树、图)
非结构化数据:无
最后从wiki带的定义看,半结构化的特点是复杂类型结构,易变,需要从自描述结构中提取数据进行计算。
Semi-structured data[1] is a form of structured data that does not obey the tabular structure of data models associated with relational databases or other forms of data tables, but nonetheless contains tags or other markers to separate semantic elements and enforce hierarchies of records and fields within the data. Therefore, it is also known as self-describing structure.
In semi-structured data, the entities belonging to the same class may have different attributes even though they are grouped together, and the attributes' order is not important.
Semi-structured data are increasingly occurring since the advent of the Internet where full-text documents and databases are not the only forms of data anymore, and different applications need a medium for exchanging information.
半结构化数据应用广泛,因为:
•简洁、简单、体积小等。
•上手容易,高效。
•跨语言,用于Web项目的前后端交互接口,配置文件,文件存储等等。移动端应用的火爆,进一步带动json等半结构化数据的使用(json示例见下图,来源于网络)。这些数据都可以作为数据源,存入数据仓库做分析。
所以半结构化数据处理的能力是数据仓库的一个典型应用场景。
半结构化数据处理
一般的场景,按照数仓的流程来看,从数据源->数据处理->数据存储,这个阶段主要是半结构化处理的主要环节,因为往上层看,可能数据已经加工完成,直接面向应用了,半结构化数据体现的就没有那么明显。
这里的半结构化数据处理,有两种做法,一种是把数据同步到一个字段里面,每次应用时用一些复杂类型,或者是json函数直接提取,就是按需提取,但数据是放到一个字段里面。这种优点是不用考虑半结构化数据结构变化。缺点是性能不佳,每次选用适合的处理函数和方法,开发复杂。不管什么样的数据,都是一个大string存进去,还非常大,比如MaxCompute,一般的情况支持8M,但为了处理这种情况,MaxCompute也可以开到最大256M。 另一种方法是导入时或者批处理时按照json结构拆成一张宽表,再随着json结构修改而修改/重建表结构。这样做的优点是存储和计算都能得到优化。但缺点是表机构经常修改,修改不便。
MaxCompute 半结构化数据处理
MaxCompute提供了以下四方面能力处理半结构化数据
提供复杂类型支持存储半结构化数据
首先提供了复杂数据类型 存储对应的半结构化数据
Schema evolution(表结构演进) 对应半结构化数据结构定义的修改
然后提供了schema evolution的能力,可以修改表和嵌套列,包括:
- 删除列
- 添加列
- 修改列顺序
- 修改列名
- 修改列数据类型(兼容类型)
- 修改复杂类型嵌套结构(与修改表结构相同)
Semi-structured data processing function 用于处理半结构化数据各节点的值
- MaxCompute SQL 为提升复杂数据类型(ARRAY、MAP、STRUCT)数据的处理能力和效率,增加了大量内建函数,可以使用内建函数对输入的复杂数据类型数据进行处理,或经过函数处理输出复杂数据类型数据。
- 同时提供了高阶函数增强复杂数据类型数据的处理能力,相较于普通函数的输入参数只能是数据,高阶函数的输入参数本身可以是一个函数。因此高阶函数可以处理输入的复杂数据类型数据,并使用lambda表达式简化处理逻辑语法表达。
直接使用半结构化数据节点value进行计算
CREATE TABLE evol_t2 (id int, name struct<given: string, family: string>,phones array<struct<type: string, num: string>>) ; insert into table evol_t2 select 1, STRUCT('Bill', 'Gates'), array(STRUCT('work', '1234567890'),STRUCT('cell', '9876543210')); insert into table evol_t2 select 2, STRUCT('Michael', 'Jordan'), array(STRUCT('work', '1111111111'),STRUCT('cell', '9999999999'));
插入结果如下:
select name.given as firstname,c.phones[1].num as phonenum from evol_t2 c where c.phones[1].type = 'cell';
查询结果如下:
MaxCompute 表结构修改
灵活修改表结构,既可以支持半结构化数据源schema的变化,也方便数仓建模模型调整,方便对存量表增补、剔除字段,然后把相同的字段放在一起或修改类型。
语法定义和示例如下:
删除列
ALTER TABLE <table_name> DROP COLUMN <column_name>; create table if not exists evol_t(id bigint,value1 bigint,value2 bigint); ALTER TABLE evol_t DROP COLUMN value2;
添加列
ALTER TABLE <table_name> ADD COLUMNS (col_name1 type1[, col_name2 type2...]); create table if not exists evol_t(id bigint,value1 bigint,value2 bigint); ALTER TABLE evol_t ADD COLUMNS value3 STRING;
说明:添加的新列不支持指定顺序,默认在最后一列。
修改列顺序
ALTER TABLE <table_name> CHANGE COLUMN <original_column_name> <new_column_name> <column_type> AFTER <column_name>; create table if not exists evol_t(id bigint,value1 bigint,value2 bigint); ALTER TABLE evol_t CHANGE COLUMN value2 value3 bigint AFTER id;
说明:目前不支持BEFORE关键词,可以通过AFTER实现,如有必要可以在后续功能中增加。
修改列名
ALTER TABLE <table_name> CHANGE COLUMN <original_column_name> RENAME TO <new_column_name>;
MaxCompute 复杂类型数据结构修改
复杂类型数据的各层嵌套列的schema也支持灵活修改,嵌套列和表结构一样都可以享受列存优化的性能和直接查询的便捷
CREATE TABLE evol_t (id int, point struct<x: double, y: double>) ; ALTER TABLE evol_t ADD COLUMNS (points_map map<string, struct<x: double, y: double>>); ALTER TABLE evol_t ADD COLUMNS (points_arr array<struct<x: double, y: double>>);
因为所有的嵌套列都当作一张嵌套的表处理和识别,那么嵌套列也可以获得如下能力:
- 表的结构可以修改(增、删、改名字、改顺序、改类型)
- 更精细的列存储和压缩
- 针对数据类型的存储和计算优化
- 直接用节点值进行计算
- 更丰富的函数进行半结构化数据处理
MaxCompute 复杂类型数据处理函数
丰富的复杂类型数据处理函数方便直接对半结构化数据进行处理,且更多更易用的函数在不断推出中
函数类别 |
函数 |
功能 |
ARRAY函数 |
判断ARRAY数组中是否所有元素都满足指定条件。 |
|
判断ARRAY数组中是否存在满足指定条件的元素。 |
||
使用给定的值构造ARRAY。 |
||
检测指定的ARRAY中是否包含指定的值。 |
||
去除ARRAY数组中的重复元素。 |
||
找出在ARRAY A中,但不在ARRAY B中的元素,并去掉重复的元素后,以ARRAY形式返回结果。 |
||
计算两个ARRAY数组的交集。 |
||
将ARRAY数组中的元素按照指定字符串进行拼接。 |
||
计算ARRAY数组中的最大值。 |
||
计算ARRAY数组中的最小值。 |
||
计算指定元素在ARRAY数组中第一次出现的位置。 |
||
将ARRAY数组的元素进行聚合。 |
||
在ARRAY数组中删除指定元素。 |
||
返回将指定元素重复指定次数后的ARRAY数组。 |
||
将ARRAY数组的元素进行排序。 |
||
计算两个ARRAY数组的并集并去掉重复元素。 |
||
判断两个ARRAY数组中是否包含相同元素。 |
||
合并多个ARRAY数组。 |
||
将ARRAY数组或字符串连接在一起。 |
||
将一行数据转为多行的UDTF。 |
||
将ARRAY数组中的元素进行过滤。 |
||
返回ARRAY数组指定位置的元素值。 |
||
将指定的ARRAY展开,每个Value一行,每行两列分别对应数组从0开始的下标和数组元素。 |
||
返回指定ARRAY中的元素数目。 |
||
对ARRAY数据切片,返回从指定位置开始、指定长度的数组。 |
||
为指定的数组中的元素排序。 |
||
将ARRAY数组中的元素进行转换。 |
||
将2个ARRAY数组按照位置进行元素级别的合并。 |
函数类别 |
函数 |
功能 |
MAP函数 |
将一行数据转为多行的UDTF。 |
|
返回MAP类型参数中满足指定条件的Value。 |
||
使用指定的Key-Value对建立MAP。 |
||
返回多个MAP的并集。 |
||
将MAP中的Key、Value键值映射转换为STRUCT结构数组。 |
||
将MAP中的元素进行过滤。 |
||
通过给定的ARRAY数组构造MAP。 |
||
通过给定的结构体数组构造MAP。 |
||
将参数MAP中的所有Key作为数组返回。 |
||
将参数MAP中的所有Value作为数组返回。 |
||
对输入的两个MAP进行合并得到一个新MAP。 |
||
返回指定MAP中的K/V对数。 |
||
对MAP进行变换,保持Value不变,根据指定函数计算新的Key。 |
||
对MAP进行变换,保持Key不变,根据指定函数计算新的Value。 |
||
STRUCT函数 |
获取STRUCT中的成员变量的取值。 |
|
将指定的STRUCT数组展开。每个数组元素对应一行,每行每个STRUCT元素对应一列。 |
||
使用给定Value列表建立STRUCT。 |
||
使用给定的Name、Value列表建立STRUCT。 |
||
JSON函数 |
根据给定的JSON字符串和输出格式信息,返回ARRAY、MAP或STRUCT类型。 |
|
在一个标准JSON字符串中,按照指定方式抽取指定的字符串。 |
||
在一个标准的JSON字符串中,按照输入的一组键抽取各个键指定的字符串。 |
||
将指定的复杂类型输出为JSON字符串。 |
MaxCompute 高阶函数支持lambda表达式
复杂类型数据处理函数中高阶函数包括:
ANY_MATCH、ALL_MATCH、ARRAY_REDUCE、ARRAY_SORT、FILTER、TRANSFORM、ZIP_WITH、MAP_FILTER、MAP_ZIP_WITH、TRANSFORM_KEYS、TRANSFORM_VALUES函数
支持lambda表达式语法,简化了对复杂数据类型数据处理的表达。
部分函数的说明和示例如下:
判断ARRAY数组array(1, 2, -10, 100, -30)中是否有元素满足x-> x > 3条件。命令示例如下: --返回true。 selectany_match(array(1, 2, -10, 100, -30), x-> x > 3); 将ARRAY数组a中的元素利用func进行过滤,返回一个新的ARRAY数组。 --返回[2, 3]。 selectfilter(array(1, 2, 3), x -> x > 1); 将ARRAY数组a和b的元素按照位置,使用combiner进行元素级别的合并,返回一个新的ARRAY数组。 --返回[2, 4, 6, NULL]。 selectzip_with(array(1,2,3), array(1,2,3,4), (x,y) -> x + y); 将MAP对象input的元素进行过滤,只保留满足predicate条件的元素。 --返回{-30:100, 20:50}。 selectmap_filter(map(10, -20, 20, 50, -30, 100, 21, null), (k, v) -> (k+v) > 10); 对输入MAP对象input进行变换,保持Key不变,通过func计算新的Value值。 --返回{-30:71, 10:-10, 20:NULL}。 selecttransform_values(map(10, -20, 20, null, -30, 101), (k, v) -> k + v);
三、实操演示
MaxCompute半结构化数据处理和schema evolution
请点击视频查看演示demo
功能说明
列举几个常用的修改复杂类型的节点schema的命令示例:
-- 给 struct 新增一列 ALTER TABLE evol_t ADD COLUMNS (point.z double); -- map 的 value 是 struct,新增一列 ALTER TABLE evol_t ADD COLUMNS (points_map.value.z double); -- array 的元素是 struct,新增一列 ALTER TABLE evol_t ADD COLUMNS (points_arr.element.z double); --示例中的一些用法 --增列 ALTER TABLE evol_t2 ADD COLUMNS (phones.element.type2 string); --删列 ALTER TABLE evol_t2 DROP COLUMNS (phones.element.type); --改名 ALTER TABLE evol_t2 CHANGE COLUMN phones.type2 phones.type0 string; --改顺序 ALTER TABLE evol_t2 CHANGE phones.num phones.num string AFTER type0;
Demo中完整功能演示脚本
DROP table evol_t2; CREATE TABLE evol_t2 (id int, name struct<given: string, family: string>,phones array<struct<type: string, num: string>>) ; insert into table evol_t2 select 1, STRUCT('Bill', 'Gates'), array(STRUCT('work', '1234567890'),STRUCT('cell', '9876543210')); insert into table evol_t2 select 2, STRUCT('Michael', 'Jordan'), array(STRUCT('work', '1111111111'),STRUCT('cell', '9999999999')); select * from evol_t2; ALTER TABLE evol_t2 ADD COLUMNS (position map<string, struct<x: double, y: double>>); insert into table evol_t2 select 3, STRUCT('Michael', 'Jackson'), array(STRUCT('work', '1231231231'),STRUCT('cell', '1231231233')),map('p1',struct(1.1,1.2),'p2',struct(1.5,1.3)); select * from evol_t2; ALTER TABLE evol_t2 ADD COLUMNS (position.value.z double); insert into table evol_t2 select 4, STRUCT('Ming', 'Yao'), array(STRUCT('work', '5555555555'),STRUCT('cell', '6666666666')),map('p1',struct(5.5,1.0,12.0),'p2',struct(6.5,3.0,8.1)); select * from evol_t2; ALTER TABLE evol_t2 DROP COLUMNS (phones.element.type); --删列 select * from evol_t2; ALTER TABLE evol_t2 ADD COLUMNS (phones.element.type2 string); --增列 select * from evol_t2; ALTER TABLE evol_t2 CHANGE COLUMN phones.type2 phones.type0 string; --改名 select * from evol_t2; insert into table evol_t2 select 5, STRUCT('Lei', 'Li'), array(STRUCT('9999999999','work'),STRUCT('8888888888','cell')),map('p1',struct(9.5,6.0,10.0),'p2',struct(5.5,2.0,3.0)); select * from evol_t2; ALTER TABLE evol_t2 CHANGE phones.num phones.num string AFTER type0; --改顺序 select * from evol_t2; select name.given as firstname,c.phones[1].num as phonenum from evol_t2 c where c.phones[1].type0 = 'cell'; select c.name.family||c.name.Given,c.phones[1].num,SQRT(POW(position['p2'].x-position['p1'].x,2)+POW(position['p2'].y-position['p1'].y,2)+POW(position['p2'].z-position['p1'].z,2)) from evol_t2 c where name.given in ('Ming','Lei');
目前支持的数据类型转化关系
近期将会灰度发布修改类型的feature,具体支持的数据类型转化关系如下:
四、演进方向
继续增强的功能和演进方向
- 增加更多的复杂数据类型处理函数
- 更多的数据类型兼容转换
- 自动识别复杂类型数据类型的schema并存储优化
- 更灵活的节点值提取和计算
- 更高性能的列分析能力
- Timetravel对于schema evolution和数据修改的版本管理
- ……
更多关于大数据计算、云数据仓库技术交流,欢迎扫码查看咨询。