攻城狮无远
IP:
0关注数
0粉丝数
0获得的赞
工作年
编辑资料
链接我:

创作·62

全部
问答
动态
项目
学习
专栏
攻城狮无远

【Databend】数据类型

数据类型列表Databend 作为一款开源、弹性、低成本,基于对象存储也可以做实时分析的新式数仓,有必要清楚支持的数据类型有哪些,通过学习收集,常见类型如下:数据大类数据类型别名字节大小类型描述整数类型tinyintint81 byte范围从-128至127整数类型smallintint162 bytes范围从-32768至32767整数类型intint322 bytes范围从-2147483648至2147483647整数类型bigintint642 bytes范围从-9223372036854775808至9223372036854775807浮点数类型float-4 byte单精度浮点数浮点数类型double-8 byte双精度浮点数浮点数类型decimal-可变用于精确十进制数值的定点类型,可以指定精度和小数点位置字符串类型varcharstring可变存储可变长度的字符串日期时间类型date-4 byte存储范围从’1000-01-01’至’9999-12-31’的日期,格式为’YYYY-MM-DD’日期时间类型timestamp-8 byte存储日期时间,格式为’YYYY-MM-DD HH:SS.ffffff’布尔类型booleanbool1 byte用于存储布尔类型0或1其他数据类型array-可变如[1, 2, 3, 4],相同数据类型的值的集合,通过其索引访问其他数据类型tuple-可变如(‘2023-02-14’,‘Valentine’),不同数据类型的值的有序集合,通过其索引访问其他数据类型map-可变如{“a”:1, “b”:2, “c”:3},一组键值对,其中每个键都是唯一的,并映射到一个值其他数据类型variantjson可变如[1,{“a”:1,“b”:{“c”:2}}],收集不同数据类型的元素,包括array和object其他数据类型bitmap-可变如0101010101,一种二进制数据类型,表示一组值,其中每个位表示值的存在或缺失通过以上表格,可以看到和 Mysql 数据类型还是存在一些差异,不过不影响,有此表格希望对你建表过程中有参考依据。转换数据类型为什么要数据类型转换,不知在使用 Databend 过程中是否遇到过以下问题。select 39 > '301';-- 报错 select concat(39,'a');-- unable to unify `UInt8` with `String`发生报错的原因都是因为类型没有统一,可能在 Mysql 没有统一也能进行比较而不会报错,但是对于 Databend 是不允许的!!!在使用其他数据库时,由于数据类型不一致也能计算,导致我们也会忽略它带来的影响,如:select 39 > 301;-- 0 select '39' > '301';-- 1字符串比较是根据每一位字符对应的的ASCII值大小进行比较,由于’9’字符比’0’大,导致 select '39' > '301';得出的结果为真。可见,当没有对数据设置正确的类型时,进行比较大小将返回错误的结果,那如果又是将比较用在了排名排序上,得出的结果也是有问题的。因此,特别需要对数据类型转换,常见的转换函数如下:函数语法函数描述数据样例cast(expr as data_type)将数据转换另一种类型cast(1 as varchar),输出’1’expr::data_type将数据转换另一种类型,cast 的别名1::varchar,输出’1’try_cast(expr as data_type)将数据转换另一种类型,错误时返回NULL1::varchar,输出’1’数据类型扩展有了数据类型列表,但是在实际应用过程中还是会有些扩展或疑惑。整数类型对于整数类型,可能会遇到“无符号整数”,我们可以进一步使用unsigned,示例如下:create table test_numeric ( tiny tinyint, tiny_unsigned tinyint unsigned, small smallint, small_unsigned smallint unsigned, i int, i_unsigned int unsigned, bigi bigint, bigi_unsigned bigint unsigned );布尔类型布尔类型,常见的就是 true 和 false,但也存在隐式转换。数值类型和字符串类型转换:0 转换为 false。任何非 0 数值都转换为 true。字符串为’true’会被转化为:true字符串为’false’会被转化为:false而且转换不区分大小写。所有其他非'TRUE'和'FALSE'文本字符串无法转换为布尔值,它将获得Code: 1010错误。浮点数类型十进制类型对于需要精确的十进制表示的应用程序非常有用,例如财务计算或科学计算。使用decimal(p, s)来指示十进制类型。p是精度,即数字中的总位数,其范围为[1,76]。s是刻度,即小数点右侧的位数,它的范围是[0,p]。如果您有decimal(10, 2),您可以存储最多 10 digits 值,小数点右侧为 2 digits。最小值为-9999999.99,最大值为9999999.99。此外,decimal 有一套复杂的精确推理规则。不同的规则将适用于不同的表达式,以推断精度。算术运算加法/减法:decimal(a, b) + decimal(x, y) -> decimal(max(a - b, x - y) + max(b, y) + 1, max(b, y))这意味着整数和十进制部分都使用两个操作数的较大值。乘法:decimal(a, b) * d(x, y) -> decimal(a + x, b + y)分部:decimal(a, b) / decimal(x, y) -> decimal(a + y, b)比较操作十进制可以与其他数字类型进行比较。十进制可以与其他十进制类型进行比较。聚合操作sum:sum(decimal(a, b)) -> decimal(max, b)avg:arg(decimal(a, b)) -> decimal(max, max(b, 4))字符串类型varchar 在 Mysql 中是可变字符串,通常会给定变长数值,但是在 Databend 上不需要给定,如下:create table string_table(text varchar);日期时间类型在 Databend 中只有两种类型 date 和 timestamp ,而且 timestamp 格式为’YYYY-MM-DD HH:SS.ffffff’,这与 Mysql 有些不同,需要注意。如要了解更多,可查看 Databend 日期时间类型。其它数据类型array 数据类型可以定义可变的,这种数据类型类似于 Python 列表,但 Python 中的列表索引是从0开始的,而 Databend 是从1开始。create table array_int64_table(arr array(int64)); select array_value[0],array_value[4],array_value[6] from (select [1, 2, 3, 4] as array_value) as t1; +----------------+----------------+----------------+ | array_value[0] | array_value[4] | array_value[6] +----------------+----------------+----------------+ | NULL | 4 | NULL +----------------+----------------+----------------+Databend对数组使用基于1的编号约定。由n个元素组成的数组以数组[1]开始,以数组[n]结束。tuple 元组是有序、不可变和异构元素的集合,在大多数编程语言中用括号()表示。换句话说,元组是不同数据类型元素的有限有序列表,一旦创建,其元素就无法更改或修改,这点和 Python 元组 类似。示例如下:create table t_table(event tuple(timestamp, varchar)); insert into t_table values(('2023-02-14 08:00:00','valentines day')); select event,event[1] from t_table; +------------------------------------------+---------------------+ | event |. event[1] | +------------------------------------------+---------------------+ | ('2023-02-14 08:00:00','valentines day') | 2023-02-14 08:00:00 | +------------------------------------------+---------------------+tuple 与 array 有点不同是如果索引不在范围内,则直接报错。map 数据结构用于保存一组Key:Value键值对,类似于 Python 字典。Key具有指定的基本数据类型,包括布尔值、数字、十进制、字符串、日期或时间戳。Key的值不能为空,也不允许重复。Value可以是任何数据类型,包括嵌套数组、元组等。select map_value1,map_value2, map_value1['k1'] from (select {'k1': 1, 'k2': 2} map_value1, map([1, 2], ['v1', 'v2']) as map_value2) as t1; +-----------------+-----------------+----------------+ | map_value1 | map_value2 | map_value1['k1'] +-----------------+-----------------+----------------+ | {'k1':1,'k2':2} | {1:'v1',2:'v2'} | 1 +-----------------+-----------------+----------------+总结通过本文对 Databend 数据类型介绍可知,合理正确使用数据类型至关重要,所以在设计表结构过程中,一定要多次甄别,如果数据类型设计或用错,可及时参照本文进行更正!!参考资料:Databend Data Type :https://databend.rs/sql/sql-reference/data-types/
0
0
0
浏览量526
攻城狮无远

Doris-01-Doris的简介和安装

Doris简介概述Apache Doris 由百度大数据部研发(之前叫百度 Palo,2018 年贡献到 Apache 社区后,更名为 Doris ),在百度内部,有超过 200 个产品线在使用,部署机器超过 1000 台,单一业务最大可达到上百 TB。Apache Doris 是一个现代化的 MPP(Massively Parallel Processing,即大规模并行处理)分析型数据库产品。仅需亚秒级响应时间即可获得查询结果,有效地支持实时数据分析。Apache Doris 的分布式架构非常简洁,易于运维,并且可以支持 10PB 以上的超大数据集。Apache Doris 可以满足多种数据分析需求,例如固定历史报表,实时数据分析,交互式数据分析和探索式数据分析等。核心优势简单易用:部署只需两个进程,不依赖其他系统;在线集群扩缩容,自动副本修复;兼容 MySQL 协议,并且使用标准 SQL;高性能:依托列式存储引擎、现代的 MPP 架构、向量化查询引擎、预聚合物化视图、数据索引的实现,在低延迟和高吞吐查询上, 都达到了极速性能;统一数仓:单一系统,可以同时支持实时数据服务、交互数据分析和离线数据处理场景;联邦查询:支持对 Hive、Iceberg、Hudi 等数据湖和 MySQL、Elasticsearch 等数据库的联邦查询分析;多种导入:支持从 HDFS/S3 等批量拉取导入和 MySQL Binlog/Kafka 等流式拉取导入;支持通过HTTP接口进行微批量推送写入和 JDBC 中使用 Insert 实时推送写入;生态丰富:Spark 利用 Spark Doris Connector 读取和写入 Doris;Flink Doris Connector 配合 Flink CDC 实现数据 Exactly Once 写入 Doris;利用 DBT Doris Adapter,可以很容易的在 Doris 中完成数据转化。使用场景如下图所示,数据源经过各种数据集成和加工处理后,通常会入库到实时数仓 Doris 和离线湖仓(Hive, Iceberg, Hudi 中),Apache Doris 被广泛应用在以下场景中。报表分析实时看板 (Dashboards)面向企业内部分析师和管理者的报表面向用户或者客户的高并发报表分析(Customer Facing Analytics)。比如面向网站主的站点分析、面向广告主的广告报表,并发通常要求成千上万的 QPS ,查询延时要求毫秒级响应。著名的电商公司京东在广告报表中使用 Apache Doris ,每天写入 100 亿行数据,查询并发 QPS 上万,99 分位的查询延时 150ms。即席查询(Ad-hoc Query):面向分析师的自助分析,查询模式不固定,要求较高的吞吐。小米公司基于 Doris 构建了增长分析平台(Growing Analytics,GA),利用用户行为数据对业务进行增长分析,平均查询延时 10s,95 分位的查询延时 30s 以内,每天的 SQL 查询量为数万条。统一数仓构建 :一个平台满足统一的数据仓库建设需求,简化繁琐的大数据软件栈。海底捞基于 Doris 构建的统一数仓,替换了原来由 Spark、Hive、Kudu、Hbase、Phoenix 组成的旧架构,架构大大简化。数据湖联邦查询:通过外表的方式联邦分析位于 Hive、Iceberg、Hudi 中的数据,在避免数据拷贝的前提下,查询性能大幅提升。架构Doris 的架构很简洁,只设 FE(Frontend)、BE(Backend)两种角色、两个进程,不依赖于外部组件,方便部署和运维,FE、BE 都可线性扩展。FE(Frontend):存储、维护集群元数据;负责接收、解析查询请求,规划查询计划,调度查询执行,返回查询结果。主要有三个角色:Leader 和 Follower:主要是用来达到元数据的高可用,保证单节点宕机的情况下,元数据能够实时地在线恢复,而不影响整个服务。Observer:用来扩展查询节点,同时起到元数据备份的作用。如果在发现集群压力非常大的情况下,需要去扩展整个查询的能力,那么可以加 observer 的节点。observer 不参与任何的写入,只参与读取。BE(Backend):负责物理数据的存储和计算;依据 FE 生成的物理计划,分布式地执行查询。数据的可靠性由 BE 保证,BE 会对整个数据存储多副本或者是三副本。副本数可根据需求动态调整。这两类进程都是可以横向扩展的,单集群可以支持到数百台机器,数十 PB 的存储容量。并且这两类进程通过一致性协议来保证服务的高可用和数据的高可靠。这种高度集成的架构设计极大的降低了一款分布式系统的运维成本。MySQL Client:Doris 借助 MySQL 协议,用户使用任意 MySQL 的 ODBC/JDBC 以及 MySQL 的客户端,都可以直接访问 Doris。Broker:Broker 为一个独立的无状态进程。封装了文件系统接口,提供 Doris 读取远端存储系统中文件的能力,包括 HDFS,S3,BOS 等技术概述在使用接口方面,Doris 采用 MySQL 协议,高度兼容 MySQL 语法,支持标准 SQL,用户可以通过各类客户端工具来访问 Doris,并支持与 BI 工具的无缝对接。在存储引擎方面,Doris 采用列式存储,按列进行数据的编码压缩和读取,能够实现极高的压缩比,同时减少大量非相关数据的扫描,从而更加有效利用 IO 和 CPU 资源。Doris 也支持比较丰富的索引结构,来减少数据的扫描:Sorted Compound Key Index,可以最多指定三个列组成复合排序键,通过该索引,能够有效进行数据裁剪,从而能够更好支持高并发的报表场景Z-order Index :使用 Z-order 索引,可以高效对数据模型中的任意字段组合进行范围查询Min/Max :有效过滤数值类型的等值和范围查询Bloom Filter :对高基数列的等值过滤裁剪非常有效Invert Index :能够对任意字段实现快速检索在存储模型方面,Doris 支持多种存储模型,针对不同的场景做了针对性的优化:Aggregate Key 模型:相同 Key 的 Value 列合并,通过提前聚合大幅提升性能Unique Key 模型:Key 唯一,相同 Key 的数据覆盖,实现行级别数据更新Duplicate Key 模型:明细数据模型,满足事实表的明细存储在查询引擎方面,Doris 采用 MPP 的模型,节点间和节点内都并行执行,也支持多个大表的分布式 Shuffle Join,从而能够更好应对复杂查询。Doris 查询引擎是向量化的查询引擎,所有的内存结构能够按照列式布局,能够达到大幅减少虚函数调用、提升 Cache 命中率,高效利用 SIMD 指令的效果。在宽表聚合场景下性能是非向量化引擎的 5-10 倍。Doris 采用了 Adaptive Query Execution 技术, 可以根据 Runtime Statistics 来动态调整执行计划,比如通过 Runtime Filter 技术能够在运行时生成生成 Filter 推到 Probe 侧,并且能够将 Filter 自动穿透到 Probe 侧最底层的 Scan 节点,从而大幅减少 Probe 的数据量,加速 Join 性能。Doris 的 Runtime Filter 支持 In/Min/Max/Bloom Filter。在优化器方面 Doris 使用 CBO 和 RBO 结合的优化策略,RBO 支持常量折叠、子查询改写、谓词下推等,CBO 支持 Join Reorder。目前 CBO 还在持续优化中,主要集中在更加精准的统计信息收集和推导,更加精准的代价模型预估等方面。编译与安装安装 Doris,需要先通过源码编译,主要有两种方式:使用 Docker 开发镜像编译(推荐)、直接编译。直接编译的方式,可以参考官网:https://doris.apache.org/zh-CN/installing/compilation.html使用 Docker 开发镜像编译(1)下载源码并解压wget https://dist.apache.org/repos/dist/dev/incubator/doris/0.15/0.15.0-rc04/apache-doris-0.15.0-incubating-src.tar.gz解压到/opt/software/:tar -zxvf apache-doris-0.15.0-incubating-src.tar.gz -C /opt/software(2)下载 Docker 镜像docker pull apache/incubator-doris:build-env-for-0.15.0可以通过以下命令查看镜像是否下载完成。docker images(3)挂载本地目录运行镜像以挂载本地 Doris 源码目录的方式运行镜像,这样编译的产出二进制文件会存储在宿主机中,不会因为镜像退出而消失。同时将镜像中 maven 的 .m2 目录挂载到宿主机目录,以防止每次启动镜像编译时,重复下载 maven 的依赖库。docker run -it \ -v /opt/software/.m2:/root/.m2 \ -v /opt/software/apache-doris-0.15.0-incubating-src/:/root/apache-doris-0.15.0-incubating-src/ \ apache/incubator-doris:build-env-for-0.15.0(4)切换到 JDK 8alternatives --set java java-1.8.0-openjdk.x86_64 alternatives --set javac java-1.8.0-openjdk.x86_64 export JAVA_HOME=/usr/lib/jvm/java-1.8.0(5)准备 Maven 依赖编译过程会下载很多依赖,可以将我们准备好的 doris-repo.tar.gz 解压到 Docker 挂载的对应目录,来避免下载依赖的过程,加速编译。tar -zxvf doris-repo.tar.gz -C /opt/software也可以通过指定阿里云镜像仓库来加速下载:vim /opt/software/apache-doris-0.15.0-incubating-src/fe/pom.xml 在<repositories>标签下添加: <repository> <id>aliyun</id> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> </repository> vim /opt/software/apache-doris-0.15.0-incubating-src/be/pom.xml 在<repositories>标签下添加: <repository> <id>aliyun</id> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> </repository>(6)编译 Dorissh build.sh如果是第一次使用 build-env-for-0.15.0 或之后的版本,第一次编译的时候要使用如下命令:sh build.sh --clean --be --fe --ui因为 build-env-for-0.15.0 版本镜像升级了 thrift(0.9 -> 0.13),需要通过–clean 命令强制使用新版本的 thrift 生成代码文件,否则会出现不兼容的代码。安装要求软硬件需求(1)Linux 操作系统要求Linux 系统版本CentOS7.1 及以上Ubuntu16.04 及以上(2)软件需求软件版本Java1.8 及以上GCC4.8.2 及以上(3)开发测试环境模块CPU内存磁盘网络实例数量Frontend8核+8GB+SSD 或 SATA,10GB+ *千兆网卡1Backend8核+16GB+SSD 或 SATA,50GB+ *千兆网卡1-3 *(4)生产环境模块CPU内存磁盘网络实例数量(最低要求)Frontend16核+64GB+SSD 或 RAID 卡,100GB+ *万兆网卡1-5 *Backend16核+64GB+SSD 或 SATA,100G+ *万兆网卡10-100 *注1:FE 的磁盘空间主要用于存储元数据,包括日志和 image。通常从几百 MB 到几个 GB 不等。BE 的磁盘空间主要用于存放用户数据,总磁盘空间按用户总数据量 * 3(3副本)计算,然后再预留额外 40% 的空间用作后台 compaction 以及一些中间数据的存放。一台机器上可以部署多个 BE 实例,但是只能部署一个 FE。如果需要 3 副本数据,那么至少需要 3 台机器各部署一个 BE 实例(而不是1台机器部署3个BE实例)。生产环境建议 FE 和 BE 分开。 多个FE所在服务器的时钟必须保持一致(允许最多5秒的时钟偏差)测试环境也可以仅适用一个 BE 进行测试。实际生产环境,BE 实例数量直接决定了整体查询延迟。所有部署节点关闭 Swap。注2:FE 节点的数量FE 角色分为 Follower 和 Observer,(Leader 为 Follower 组中选举出来的一种角色,以下统称 Follower)。FE 节点数据至少为1(1 个 Follower)。当部署 1 个 Follower 和 1 个 Observer 时,可以实现读高可用。当部署 3 个 Follower 时,可以实现读写高可用(HA)。Follower 的数量必须为奇数,Observer 数量随意。根据以往经验,当集群可用性要求很高时(比如提供在线业务),可以部署 3 个 Follower 和 1-3 个 Observer。如果是离线业务,建议部署 1 个 Follower 和 1-3 个 Observer。通常我们建议 10 ~ 100 台左右的机器,来充分发挥 Doris 的性能(其中 3 台部署 FE(HA),剩余的部署 BE)当然,Doris的性能与节点数量及配置正相关。在最少4台机器(一台 FE,三台 BE,其中一台 BE 混部一个 Observer FE 提供元数据备份),以及较低配置的情况下,依然可以平稳的运行 Doris。如果 FE 和 BE 混部,需注意资源竞争问题,并保证元数据目录和数据目录分属不同磁盘。端口需求实例名称端口名称默认端口通讯方向说明BEbe_port9060FE --> BEBE 上 thrift server 的端口,用于接收来自 FE 的请求BEwebserver_port8040BE <–> BEBE 上的 http server 的端口BEheartbeat_service_port9050FE --> BEBE 上心跳服务端口(thrift),用于接收来自 FE 的心跳BEbrpc_port8060FE <–> BE, BE <–> BEBE 上的 brpc 端口,用于 BE 之间通讯FEhttp_port8030FE <–> FE,用户 <–> FEFE 上的 http server 端口FErpc_port9020BE --> FE, FE <–> FEFE 上的 thrift server 端口,每个fe的配置需要保持一致FEquery_port9030用户 <–> FEFE 上的 mysql server 端口FEedit_log_port9010FE <–> FEFE 上的 bdbje 之间通信用的端口Brokerbroker_ipc_port8000FE --> Broker, BE --> BrokerBroker 上的 thrift server,用于接收请求注:当部署多个 FE 实例时,要保证 FE 的 http_port 配置相同。部署前请确保各个端口在应有方向上的访问权限。集群部署主机1主机2主机3FE(LEADER)FE(FOLLOWER)FE(OBSERVER)BEBEBEBROKERBROKERBROKER生产环境建议 FE 和 BE 分开。(1) 创建目录并拷贝编译后的文件创建目录并拷贝编译后的文件mkdir /opt/module/apache-doris-0.15.0 cp -r /opt/software/apache-doris-0.15.0-incubating-src/output /opt/module/apache-doris-0.15.0 修改可打开文件数(每个节点)sudo vim /etc/security/limits.conf * soft nofile 65535 * hard nofile 65535 * soft nproc 65535 * hard nproc 65535 重启永久生效,也可以用 ulimit -n 65535 临时生效。(2)部署 FE 节点创建 fe 元数据存储的目录mkdir /opt/module/apache-doris-0.15.0/doris-meta 1修改 fe 的配置文件vim /opt/module/apache-doris-0.15.0/fe/conf/fe.conf #配置文件中指定元数据路径: meta_dir = /opt/module/apache-doris-0.15.0/doris-meta #修改绑定 ip(每台机器修改成自己的 ip) priority_networks = 192.168.8.101/24 注意:生产环境强烈建议单独指定目录不要放在 Doris 安装目录下,最好是单独的磁盘(如果有 SSD 最好)。如果机器有多个 ip, 比如内网外网, 虚拟机 docker 等, 需要进行 ip 绑定,才能正确识别。JAVA_OPTS 默认 java 最大堆内存为 4GB,建议生产环境调整至 8G 以上。启动 hadoop1 的 FE/opt/module/apache-doris-0.15.0/fe/bin/start_fe.sh --daemon (3)配置 BE 节点分发 BEscp -r /opt/module/apache-doris-0.15.0/be hadoop2:/opt/module scp -r /opt/module/apache-doris-0.15.0/be hadoop3:/opt/module 创建 BE 数据存放目录(每个节点)mkdir /opt/module/apache-doris-0.15.0/doris-storage1 mkdir /opt/module/apache-doris-0.15.0/doris-storage2 修改 BE 的配置文件(每个节点)vim /opt/module/apache-doris-0.15.0/be/conf/be.conf #配置文件中指定数据存放路径: storage_root_path = /opt/module/apache-doris-0.15.0/doris-storage1;/opt/module/apache-doris-0.15.0/doris-storage2 #修改绑定 ip(每台机器修改成自己的 ip) priority_networks = 192.168.8.101/24 注意:storage_root_path 默认在 be/storage 下,需要手动创建该目录。多个路径之间使用英文状态的分号;分隔(最后一个目录后不要加)。可以通过路径区别存储目录的介质,HDD 或 SSD。可以添加容量限制在每个路径的末尾,通过英文状态逗号,隔开,如:说明:/home/disk1/doris.HDD,50,表示存储限制为 50GB,HDD;/home/disk2/doris.SSD,10,存储限制为 10GB,SSD;/home/disk2/doris,存储限制为磁盘最大容量,默认为 HDD如果机器有多个 IP, 比如内网外网, 虚拟机 docker 等, 需要进行 IP 绑定,才能正确识别。(4)在 FE 中添加所有 BE 节点BE 节点需要先在 FE 中添加,才可加入集群。可以使用 mysql-client 连接到 FE。安装 MySQL Client:创建目录:mkdir /opt/software/mysql-client/ 上传相关以下三个 rpm 包到/opt/software/mysql-client/:mysql-community-client-5.7.28-1.el7.x86_64.rpmmysql-community-common-5.7.28-1.el7.x86_64.rpmmysql-community-libs-5.7.28-1.el7.x86_64.rpm检查当前系统是否安装过 MySQLsudo rpm -qa|grep mariadb #如果存在,先卸载 sudo rpm -e --nodeps mariadb mariadb-libs mariadb-server 安装rpm -ivh /opt/software/mysql-client/* 使用 MySQL Client 连接 FE:mysql -h hadoop1 -P 9030 -uroot默认 root 无密码,通过以下命令修改 root 密码。SET PASSWORD FOR 'root' = PASSWORD('000000');添加 BEALTER SYSTEM ADD BACKEND "hadoop1:9050"; ALTER SYSTEM ADD BACKEND "hadoop2:9050"; ALTER SYSTEM ADD BACKEND "hadoop3:9050";查看 BE 状态SHOW PROC '/backends';(5)启动 BE启动 BE(每个节点)/opt/module/apache-doris-0.15.0/be/bin/start_be.sh --daemon查看 BE 状态mysql -h hadoop1 -P 9030 -uroot -p SHOW PROC '/backends';Alive 为 true 表示该 BE 节点存活。(6)部署 FS_Broker(可选)Broker 以插件的形式,独立于 Doris 部署。如果需要从第三方存储系统导入数据,需要部署相应的 Broker,默认提供了读取 HDFS、百度云 BOS 及 Amazon S3 的 fs_broker。fs_broker 是无状态的,建议每一个 FE 和 BE 节点都部署一个 Broker。编译 FS_BROKER 并拷贝文件进入源码目录下的 fs_brokers 目录,使用 sh build.sh 进行编译拷贝源码 fs_broker 的 output 目录下的相应 Broker 目录到需要部署的所有节点上,改名为: apache_hdfs_broker。建议和 BE 或者 FE 目录保持同级。启动 Broker/opt/module/apache-doris-0.15.0/apache_hdfs_broker/bin/start_broker.sh --daemon添加 Broker:要让 Doris 的 FE 和 BE 知道 Broker 在哪些节点上,通过 sql 命令添加 Broker 节点列表。使用 mysql-client 连接启动的 FE,执行以下命令:mysql -h hadoop1 -P 9030 -uroot -p ALTER SYSTEM ADD BROKER broker_name "hadoop1:8000","hadoop2:8000","hadoop3:8000";其中 broker_host 为 Broker 所在节点 ip;broker_ipc_port 在 Broker 配置文件中的conf/apache_hdfs_broker.conf。查看 Broker 状态使用 mysql-client 连接任一已启动的 FE,执行以下命令查看 Broker 状态:SHOW PROC "/brokers";注:在生产环境中,所有实例都应使用守护进程启动,以保证进程退出后,会被自动拉起,如 Supervisor(opens new window)。如需使用守护进程启动,在 0.9.0 及之前版本中,需要修改各个 start_xx.sh 脚本,去掉最后的 & 符号。从 0.10.0 版本开始,直接调用 sh start_xx.sh 启动即可。扩容和缩容Doris 可以很方便的扩容和缩容 FE、BE、Broker 实例。FE 扩容和缩容可以通过将 FE 扩容至 3 个以上节点来实现 FE 的高可用。(1)使用 MySQL 登录客户端后,可以使用 sql 命令查看 FE 状态,目前就一台 FEmysql -h hadoop1 -P 9030 -uroot -p SHOW PROC '/frontends';也可以通过页面访问进行监控,访问 8030,账户为 root,密码默认为空不用填写。(2)增加 FE 节点FE 分为 Leader,Follower 和 Observer 三种角色。 默认一个集群,只能有一个 Leader,可以有多个 Follower 和 Observer。其中 Leader 和 Follower 组成一个 Paxos 选择组,如果Leader 宕机,则剩下的 Follower 会自动选出新的 Leader,保证写入高可用。Observer 同步Leader 的数据,但是不参加选举。如果只部署一个 FE,则 FE 默认就是 Leader。在此基础上,可以添加若干 Follower 和Observer。ALTER SYSTEM ADD FOLLOWER "hadoop2:9010"; ALTER SYSTEM ADD OBSERVER "hadoop3:9010";(3)配置及启动 Follower 和 Observer第一次启动时,启动命令需要添加参–helper leader 主机: edit_log_port:分发 FE,修改 FE 的配置:scp -r /opt/module/apache-doris-0.15.0/fe hadoop2:/opt/module/apache-doris-0.15.0 scp -r /opt/module/apache-doris-0.15.0/fe hadoop3:/opt/module/apache-doris-0.15.0 在 hadoop2 启动 Follower:/opt/module/apache-doris-0.15.0/fe/bin/start_fe.sh --helper hadoop1:9010 --daem在 hadoop3 启动 Observer/opt/module/apache-doris-0.15.0/fe/bin/start_fe.sh --helper hadoop1:9010 --daemon(4)查看运行状态使用 mysql-client 连接到任一已启动的 FE。SHOW PROC '/frontends';(5)删除 FE 节点命令ALTER SYSTEM DROP FOLLOWER[OBSERVER] "fe_host:edit_log_port";注意:删除 Follower FE 时,确保最终剩余的 Follower(包括 Leader)节点为奇数。BE 扩容和缩容(1)增加 BE 节点在 MySQL 客户端,通过 ALTER SYSTEM ADD BACKEND 命令增加 BE 节点。(2)DROP 方式删除 BE 节点(不推荐)ALTER SYSTEM DROP BACKEND "be_host:be_heartbeat_service_port";注意:DROP BACKEND 会直接删除该 BE,并且其上的数据将不能再恢复!!!所以我们强烈不推荐使用 DROP BACKEND 这种方式删除 BE 节点。当你使用这个语句时,会有对应的防误操作提示。(3)DECOMMISSION 方式删除 BE 节点(推荐)ALTER SYSTEM DECOMMISSION BACKEND "be_host:be_heartbeat_service_port";该命令用于安全删除 BE 节点。命令下发后,Doris 会尝试将该 BE 上的数据向其他 BE 节点迁移,当所有数据都迁移完成后,Doris 会自动删除该节点。该命令是一个异步操作。执行后,可以通过 SHOW PROC ‘/backends’; 看到该 BE 节点的 isDecommission 状态为 true。表示该节点正在进行下线。该命令不一定执行成功。比如剩余 BE 存储空间不足以容纳下线 BE 上的数据,或者剩余机器数量不满足最小副本数时,该命令都无法完成,并且 BE 会一直处于isDecommission 为 true 的状态。DECOMMISSION 的进度,可以通过 SHOW PROC ‘/backends’; 中的 TabletNum 查看,如果正在进行,TabletNum 将不断减少。该操作可以通过如下命令取消:CANCEL DECOMMISSION BACKEND “be_host:be_heartbeat_service_port”;取消后,该 BE 上的数据将维持当前剩余的数据量。后续 Doris 重新进行负载均衡。Broker 扩容缩容Broker 实例的数量没有硬性要求。通常每台物理机部署一个即可。Broker 的添加和删除可以通过以下命令完成:ALTER SYSTEM ADD BROKER broker_name "broker_host:broker_ipc_port"; ALTER SYSTEM DROP BROKER broker_name "broker_host:broker_ipc_port"; ALTER SYSTEM DROP ALL BROKER broker_name;Broker 是无状态的进程,可以随意启停。当然,停止后,正在其上运行的作业会失败,重试即可。
0
0
0
浏览量558
攻城狮无远

【Databend】merge 的用法

概述merge 关键字它能使用指定来源的数据,对目标表中的行执行 insert 、update 或 delete 操作,所有这些都符合语句中指定的条件和匹配标准。基础语法:merge into target_table using source_table on condition when matched [and <condition>] then operation when not matched [and <condition>] then operation;其中最后语句分号不可以省略,且源表既可以是一个表也可以是一个子查询语句。merge 语句通常包含 matched 或 not matched 子句,对应如何处理匹配和不匹配的场景。对于匹配子句,您可以选择在目标表上执行更新或删除操作之间进行选择。相反,在不匹配子句的情况下,可用的选择是 insert 。因此,应用场景有数据同步和基于源表对目标表做 insert 、update 或 delete 操作。当源表和目标表不匹配时:若数据是源表有目标表没有,则进行插入操作;若数据是源表没有而目标表有,则进行更新或者删除数据操作当源表和目标表匹配时:进行更新操作或者删除操作。merge无法多次更新同一行,也无法更新和删除同一行。merge 还有一些使用限制:在 merge matched 操作中,只能允许执行 update 或者 delete 语句,并且只能出现一次,否则报错。在 merge not matched 操作中,只允许执行 insert 语句。数据准备准备以下三个表用于测试。drop table if exists employees; create table if not exists employees ( employee_id int, employee_name varchar(255), department varchar(255) ); drop table if exists salaries; create table if not exists salaries ( employee_id int, salary decimal(10, 2) ); drop table if exists target_table; create table if not exists target_table ( id int, name varchar(50), age int, city varchar(50) ); insert into employees values (1, 'Alice', 'HR'), (2, 'Bob', 'IT'), (3, 'Charlie', 'Finance'), (4, 'David', 'HR'); insert into salaries values (1, 50000.00), (2, 60000.00); insert into target_table (id, name, age, city) values (1, 'Alice', 25, 'Toronto'), (2, 'Bob', 30, 'Vancouver'), (3, 'Carol', 28, 'Montreal'); -- 开启 merge 功能 set enable_experimental_merge_into = 1;应用示例需求是将“员工”的员工数据同步到“工资”,允许根据指定标准插入和更新工资信息merge into salaries using (select * from employees) as employees on salaries.employee_id = employees.employee_id when matched and employees.department = 'HR' then update set salaries.salary = salaries.salary + 1000.00 when matched then update set salaries.salary = salaries.salary + 500.00 when not matched then insert (employee_id, salary) values (employees.employee_id, 55000.00);同步数据create table if not exists target_table like target_table; -- 同步全量数据 merge into target_table as t using (select * from source_table) as s on t.id = s.id when matched then update * when not matched then insert *;总结merge 用法功能比较丰富,这里只是简单应用,如果想了解更多,可以搜索手动实践。在我们要对表做多种操作时,这种写法不仅可以节省代码,而且有时候还可以提高执行效率。参考资料:Databend DML MERGE:https://docs.databend.com/sql/sql-commands/dml/dml-merge
0
0
0
浏览量514
攻城狮无远

Doris-06-监控、报警以及Doris的优化

监控和报警Doris 可以使用 Prometheus 和 Grafana 进行监控和采集,官网下载最新版即可。Prometheus 官网下载:https://prometheus.io/download/Grafana 官网下载:https://grafana.com/grafana/downloadDoris 的监控数据通过 FE 和 BE 的 http 接口向外暴露。监控数据以 key-value 的文本形式对外展现。每个 key 还可能有不同的 Label 加以区分。当用户搭建好 Doris 后,可以在浏览器,通过以下接口访问监控数据.Frontend: fe_host:fe_http_port/metrics,如 http://hadoop1:8030/metricsBackend: be_host:be_web_server_port/metrics,如 http://hadoop1:8040/metrics整个监控架构如下图:prometheus的配置修改:配置两个 targets 分别配置 FE 和 BE,并且定义 labels 和 groups 指定组。如果有多个集群则再加 -job_name 标签,进行相同配置:vim /opt/module/prometheus-2.26.0/prometheus.yml # my global config global: scrape_interval: 15s # 全局的采集间隔,默认是 1m,这里设置为 15s evaluation_interval: 15s # 全局的规则触发间隔,默认是 1m,这里设置 15s # Alertmanager configuration alerting: alertmanagers: - static_configs: - targets: # - alertmanager:9093 # A scrape configuration containing exactly one endpoint to scrape: # Here it's Prometheus itself. scrape_configs: # The job name is added as a label `job=<job_name>` to any timeseries scraped from this config. - job_name: 'PALO_CLUSTER' # 每一个 Doris 集群,我们称为一个 job。这里可以给 job 取一个名字,作为 Doris 集群在监控系统中的名字。 metrics_path: '/metrics' # 这里指定获取监控项的 restful api。配合下面的 targets 中的 host:port,Prometheus 最终会通过 host:port/metrics_path 来采集监控项。 static_configs: # 这里开始分别配置 FE 和 BE 的目标地址。所有的 FE 和 BE 都分别写入各自的 group 中。 - targets: ['fe_host1:8030', 'fe_host2:8030', 'fe_host3:8030'] labels: group: fe # 这里配置了 fe 的 group,该 group 中包含了 3 个 Frontends - targets: ['be_host1:8040', 'be_host2:8040', 'be_host3:8040'] labels: group: be # 这里配置了 be 的 group,该 group 中包含了 3 个 Backends - job_name: 'PALO_CLUSTER_2' # 我们可以在一个 Prometheus 中监控多个 Doris 集群,这里开始另一个 Doris 集群的配置。配置同上,以下略。 metrics_path: '/metrics' static_configs: - targets: ['fe_host1:8030', 'fe_host2:8030', 'fe_host3:8030'] labels: group: fe - targets: ['be_host1:8040', 'be_host2:8040', 'be_host3:8040'] labels: group: be 之后可以启动Prometheus和Grafana进行监控查看。优化查看 QueryProfile利用查询执行的统计结果,可以更好的帮助我们了解 Doris 的执行情况,并有针对性的进行相应 Debug 与调优工作。FE 将查询计划拆分成为 Fragment 下发到 BE 进行任务执行。BE 在执行 Fragment 时记录了运行状态时的统计值,并将 Fragment 执行的统计信息输出到日志之中。 FE 也可以通过开关将各个 Fragment 记录的这些统计值进行搜集,并在 FE 的 Web 页面上打印结果。使用方式:开启 profile:set enable_profile=true; 执行一个查询:SELECT t1 FROM test JOIN test2 where test.t1 = test2.t2; 通过 FE 的 UI 查看:http://hadoop1:8030/QueryProfile/参数说明:(1)FragmentAverageThreadTokens: 执行Fragment使用线程数目,不包含线程池的使用情况Buffer Pool PeakReservation: Buffer Pool使用的内存的峰值MemoryLimit: 查询时的内存限制PeakMemoryUsage: 整个Instance在查询时内存使用的峰值RowsProduced: 处理列的行数(2)BlockMgrBlocksCreated: BlockMgr创建的Blocks数目BlocksRecycled: 重用的Blocks数目BytesWritten: 总的落盘写数据量MaxBlockSize: 单个Block的大小TotalReadBlockTime: 读Block的总耗时(3)DataStreamSenderBytesSent: 发送的总数据量 = 接受者 * 发送数据量IgnoreRows: 过滤的行数LocalBytesSent: 数据在Exchange过程中,记录本机节点的自发自收数据量OverallThroughput: 总的吞吐量 = BytesSent / 时间SerializeBatchTime: 发送数据序列化消耗的时间UncompressedRowBatchSize: 发送数据压缩前的RowBatch的大小(4)ODBC_TABLE_SINKNumSentRows: 写入外表的总行数TupleConvertTime: 发送数据序列化为Insert语句的耗时ResultSendTime: 通过ODBC Driver写入的耗时(5)EXCHANGE_NODEBytesReceived: 通过网络接收的数据量大小MergeGetNext: 当下层节点存在排序时,会在EXCHANGE NODE进行统一的归并排序,输出有序结果。该指标记录了Merge排序的总耗时,包含了MergeGetNextBatch耗时。MergeGetNextBatch:Merge节点取数据的耗时,如果为单层Merge排序,则取数据的对象为网络队列。若为多层Merge排序取数据对象为Child Merger。ChildMergeGetNext: 当下层的发送数据的Sender过多时,单线程的Merge会成为性能瓶颈,Doris会启动多个Child Merge线程并行归并排序。记录了Child Merge的排序耗时 该数值是多个线程的累加值。ChildMergeGetNextBatch: Child Merge节点从取数据的耗时,如果耗时过大,可能的瓶颈为下层的数据发送节点。DataArrivalWaitTime: 等待Sender发送数据的总时间FirstBatchArrivalWaitTime: 等待第一个batch从Sender获取的时间DeserializeRowBatchTimer: 反序列化网络数据的耗时SendersBlockedTotalTimer(*): DataStreamRecv的队列的内存被打满,Sender端等待的耗时ConvertRowBatchTime: 接收数据转为RowBatch的耗时RowsReturned: 接收行的数目RowsReturnedRate: 接收行的速率(6)SORT_NODEInMemorySortTime: 内存之中的排序耗时InitialRunsCreated: 初始化排序的趟数(如果内存排序的话,该数为1)SortDataSize: 总的排序数据量MergeGetNext: MergeSort从多个sort_run获取下一个batch的耗时 (仅在落盘时计时)MergeGetNextBatch: MergeSort提取下一个sort_run的batch的耗时 (仅在落盘时计时)TotalMergesPerformed: 进行外排merge的次数(7)AGGREGATION_NODEPartitionsCreated: 聚合查询拆分成Partition的个数GetResultsTime: 从各个partition之中获取聚合结果的时间HTResizeTime: HashTable进行resize消耗的时间HTResize: HashTable进行resize的次数HashBuckets: HashTable中Buckets的个数HashBucketsWithDuplicate: HashTable有DuplicateNode的Buckets的个数HashCollisions: HashTable产生哈希冲突的次数HashDuplicateNodes: HashTable出现Buckets相同DuplicateNode的个数HashFailedProbe: HashTable Probe操作失败的次数HashFilledBuckets: HashTable填入数据的Buckets数目HashProbe: HashTable查询的次数HashTravelLength: HashTable查询时移动的步数(8)HASH_JOIN_NODEExecOption: 对右孩子构造HashTable的方式(同步or异步),Join中右孩子可能是表或子查询,左孩子同理BuildBuckets: HashTable中Buckets的个数BuildRows: HashTable的行数BuildTime: 构造HashTable的耗时LoadFactor: HashTable的负载因子(即非空Buckets的数量)ProbeRows: 遍历左孩子进行Hash Probe的行数ProbeTime: 遍历左孩子进行Hash Probe的耗时,不包括对左孩子RowBatch调用GetNext的耗时PushDownComputeTime: 谓词下推条件计算耗时PushDownTime: 谓词下推的总耗时,Join时对满足要求的右孩子,转为左孩子的in查询(9)CROSS_JOIN_NODEExecOption: 对右孩子构造RowBatchList的方式(同步or异步)BuildRows: RowBatchList的行数(即右孩子的行数)BuildTime: 构造RowBatchList的耗时LeftChildRows: 左孩子的行数LeftChildTime: 遍历左孩子,和右孩子求笛卡尔积的耗时,不包括对左孩子RowBatch调用GetNext的耗时(10)UNION_NODEMaterializeExprsEvaluateTime: Union两端字段类型不一致时,类型转换表达式计算及物化结果的耗时(11)ANALYTIC_EVAL_NODEEvaluationTime: 分析函数(窗口函数)计算总耗时GetNewBlockTime: 初始化时申请一个新的Block的耗时,Block用来缓存Rows窗口或整个分区,用于分析函数计算PinTime: 后续申请新的Block或将写入磁盘的Block重新读取回内存的耗时UnpinTime: 对暂不需要使用的Block或当前操作符内存压力大时,将Block的数据刷入磁盘的耗时(12)OLAP_SCAN_NODEOLAP_SCAN_NODE 节点负责具体的数据扫描任务。一个 OLAP_SCAN_NODE 会生成一个或多个 OlapScanner 。每个 Scanner 线程负责扫描部分数据。查询中的部分或全部谓词条件会推送给 OLAP_SCAN_NODE。这些谓词条件中一部分会继续下推给存储引擎,以便利用存储引擎的索引进行数据过滤。另一部分会保留在 OLAP_SCAN_NODE 中,用于过滤从存储引擎中返回的数据。OLAP_SCAN_NODE 节点的 Profile 通常用于分析数据扫描的效率,依据调用关系分为 OLAP_SCAN_NODE、OlapScanner、SegmentIterator 三层。OLAP_SCAN_NODE (id=0):(Active: 1.2ms, % non-child: 0.00%) - BytesRead: 265.00 B # 从数据文件中读取到的数据量。假设读取到了是10个32位整型,则数据量为 10 * 4B = 40 Bytes。这个数据仅表示数据在内存中全展开的大小,并不代表实际的 IO 大小。 - NumDiskAccess: 1 # 该 ScanNode 节点涉及到的磁盘数量。 - NumScanners: 20 # 该 ScanNode 生成的 Scanner 数量。 - PeakMemoryUsage: 0.00 # 查询时内存使用的峰值,暂未使用 - RowsRead: 7 # 从存储引擎返回到 Scanner 的行数,不包括经 Scanner 过滤的行数。 - RowsReturned: 7 # 从 ScanNode 返回给上层节点的行数。 - RowsReturnedRate: 6.979K /sec # RowsReturned/ActiveTime - TabletCount : 20 # 该 ScanNode 涉及的 Tablet 数量。 - TotalReadThroughput: 74.70 KB/sec # BytesRead除以该节点运行的总时间(从Open到Close),对于IO受限的查询,接近磁盘的总吞吐量。 - ScannerBatchWaitTime: 426.886us # 用于统计transfer 线程等待scaner 线程返回rowbatch的时间。 - ScannerWorkerWaitTime: 17.745us # 用于统计scanner thread 等待线程池中可用工作线程的时间。 OlapScanner: - BlockConvertTime: 8.941us # 将向量化Block转换为行结构的 RowBlock 的耗时。向量化 Block 在 V1 中为 VectorizedRowBatch,V2中为 RowBlockV2。 - BlockFetchTime: 468.974us # Rowset Reader 获取 Block 的时间。 - ReaderInitTime: 5.475ms # OlapScanner 初始化 Reader 的时间。V1 中包括组建 MergeHeap 的时间。V2 中包括生成各级 Iterator 并读取第一组Block的时间。 - RowsDelFiltered: 0 # 包括根据 Tablet 中存在的 Delete 信息过滤掉的行数,以及 unique key 模型下对被标记的删除行过滤的行数。 - RowsPushedCondFiltered: 0 # 根据传递下推的谓词过滤掉的条件,比如 Join 计算中从 BuildTable 传递给 ProbeTable 的条件。该数值不准确,因为如果过滤效果差,就不再过滤了。 - ScanTime: 39.24us # 从 ScanNode 返回给上层节点的时间。 - ShowHintsTime_V1: 0ns # V2 中无意义。V1 中读取部分数据来进行 ScanRange 的切分。 SegmentIterator: - BitmapIndexFilterTimer: 779ns # 利用 bitmap 索引过滤数据的耗时。 - BlockLoadTime: 415.925us # SegmentReader(V1) 或 SegmentIterator(V2) 获取 block 的时间。 - BlockSeekCount: 12 # 读取 Segment 时进行 block seek 的次数。 - BlockSeekTime: 222.556us # 读取 Segment 时进行 block seek 的耗时。 - BlocksLoad: 6 # 读取 Block 的数量 - CachedPagesNum: 30 # 仅 V2 中,当开启 PageCache 后,命中 Cache 的 Page 数量。 - CompressedBytesRead: 0.00 # V1 中,从文件中读取的解压前的数据大小。V2 中,读取到的没有命中 PageCache 的 Page 的压缩前的大小。 - DecompressorTimer: 0ns # 数据解压耗时。 - IOTimer: 0ns # 实际从操作系统读取数据的 IO 时间。 - IndexLoadTime_V1: 0ns # 仅 V1 中,读取 Index Stream 的耗时。 - NumSegmentFiltered: 0 # 在生成 Segment Iterator 时,通过列统计信息和查询条件,完全过滤掉的 Segment 数量。 - NumSegmentTotal: 6 # 查询涉及的所有 Segment 数量。 - RawRowsRead: 7 # 存储引擎中读取的原始行数。详情见下文。 - RowsBitmapIndexFiltered: 0 # 仅 V2 中,通过 Bitmap 索引过滤掉的行数。 - RowsBloomFilterFiltered: 0 # 仅 V2 中,通过 BloomFilter 索引过滤掉的行数。 - RowsKeyRangeFiltered: 0 # 仅 V2 中,通过 SortkeyIndex 索引过滤掉的行数。 - RowsStatsFiltered: 0 # V2 中,通过 ZoneMap 索引过滤掉的行数,包含删除条件。V1 中还包含通过 BloomFilter 过滤掉的行数。 - RowsConditionsFiltered: 0 # 仅 V2 中,通过各种列索引过滤掉的行数。 - RowsVectorPredFiltered: 0 # 通过向量化条件过滤操作过滤掉的行数。 - TotalPagesNum: 30 # 仅 V2 中,读取的总 Page 数量。 - UncompressedBytesRead: 0.00 # V1 中为读取的数据文件解压后的大小(如果文件无需解压,则直接统计文件大小)。V2 中,仅统计未命中 PageCache 的 Page 解压后的大小(如果Page无需解压,直接统计Page大小) - VectorPredEvalTime: 0ns # 向量化条件过滤操作的耗时。 - ShortPredEvalTime: 0ns # 短路谓词过滤操作的耗时。 - PredColumnReadTime: 0ns # 谓词列读取的耗时。 - LazyReadTime: 0ns # 非谓词列读取的耗时。 - OutputColumnTime: 0ns # 物化列的耗时。(13)Buffer poolAllocTime: 内存分配耗时CumulativeAllocationBytes: 累计内存分配的量CumulativeAllocations: 累计的内存分配次数PeakReservation: Reservation的峰值PeakUnpinnedBytes: unpin的内存数据量PeakUsedReservation: Reservation的内存使用量ReservationLimit: BufferPool的Reservation的限制量合理设置分桶分区数一个表的 Tablet 总数量等于 (Partition num * Bucket num)。一个表的 Tablet 数量,在不考虑扩容的情况下,推荐略多于整个集群的磁盘数量。单个 Tablet 的数据量理论上没有上下界,但建议在 1G - 10G 的范围内。如果单个 Tablet 数据量过小,则数据的聚合效果不佳,且元数据管理压力大。如果数据量过大,则不利于副本的迁移、补齐,且会增加 Schema Change 或者 Rollup 操作失败重试的代价(这些操作失败重试的粒度是 Tablet)。当 Tablet 的数据量原则和数量原则冲突时,建议优先考虑数据量原则。在建表时,每个分区的 Bucket 数量统一指定。但是在动态增加分区时(ADD PARTITION),可以单独指定新分区的 Bucket 数量。可以利用这个功能方便的应对数据缩小或膨胀。一个 Partition 的 Bucket 数量一旦指定,不可更改。所以在确定 Bucket 数量时,需要预先考虑集群扩容的情况。比如当前只有 3 台 host,每台 host 有 1 块盘。如果Bucket 的数量只设置为 3 或更小,那么后期即使再增加机器,也不能提高并发度。举一些例子:假设在有 10 台 BE,每台 BE 一块磁盘的情况下。如果一个表总大小为 500MB,则可以考虑 4-8 个分片。5GB:8-16 个。50GB:32 个。500GB:建议分区,每个分区大小在 50GB 左右,每个分区 16-32 个分片。5TB:建议分区,每个分区大小在50GB 左右,每个分区 16-32 个分片。注:表的数据量可以通过 show data 命令查看,结果除以副本数,即表的数据量。向量化执行引擎过去 Apache Doris 的 SQL 执行引擎是基于行式内存格式以及基于传统的火山模型进行设计的,在进行 SQL 算子与函数运算时存在非必要的开销,导致 Apache Doris 执行引擎的效率受限,并不适应现代 CPU 的体系结构。向量化执行引擎的目标是替换 Apache Doris 当前的行式 SQL 执行引擎,充分释放现代 CPU 的计算能力,突破在 SQL 执行引擎上的性能限制,发挥出极致的性能表现。基于现代 CPU 的特点与火山模型的执行特点,向量化执行引擎重新设计了在列式存储系统的 SQL 执行引擎:重新组织内存的数据结构,用 Column 替换 Tuple,提高了计算时 Cache 亲和度,分支预测与预取内存的友好度分批进行类型判断,在本次批次中都使用类型判断时确定的类型,将每一行类型判断的虚函数开销分摊到批量级别。通过批级别的类型判断,消除了虚函数的调用,让编译器有函数内联以及 SIMD 优化的机会从而大大提高了 CPU 在 SQL 执行时的效率,提升了 SQL 查询的性能。(1)使用方式set enable_vectorized_engine = true; set batch_size = 4096;batch_size 代表了 SQL 算子每次进行批量计算的行数。Doris 默认的配置为 1024,这个配置的行数会影响向量化执行引擎的性能与 CPU 缓存预取的行为。官方推荐配置为 4096。(2)准备测试表CREATE TABLE IF NOT EXISTS test_db.user ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `username` VARCHAR(50) NOT NULL COMMENT "用户昵称", `city` VARCHAR(20) NOT NULL COMMENT "用户所在城市", `age` SMALLINT NOT NULL COMMENT "用户年龄", `sex` TINYINT NOT NULL COMMENT "用户性别", `phone` LARGEINT NOT NULL COMMENT "用户电话", `address` VARCHAR(500) NOT NULL COMMENT "用户地址", `register_time` DATETIME NOxT NULL COMMENT "用户注册时间" ) UNIQUE KEY(`user_id`, `username`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10 PROPERTIES("replication_num" = "1"); insert into test_db.user values\ (10000,'wuyanzu',' 北 京 ',18,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\ (20000,'wuyanzu',' 北 京 ',19,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\ (30000,'zhangsan','北京',20,0,12345678910,'北京海淀区','2017-11-15 06:10:20');(3)查看效果explain select name from user where user_id > 20000开启了向量化执行引擎之后,在 SQL 的执行计划之中会在 SQL 算子前添加一个 V 的标识。(4)注意事项NULL 值由于 NULL 值在向量化执行引擎中会导致性能劣化。所以在建表时,将对应的列设置为 NULL 通常会影响向量化执行引擎的性能。这里推荐使用一些特殊的列值表示 NULL 值,并在建表时设置列为 NOT NULL 以充分发挥向量化执行引擎的性能。与行存执行引擎的部分差异在绝大多数场景之中,用户只需要默认打开 session 变量的开关,就可以透明地使用向量化执行引擎,并且使 SQL 执行的性能得到提升。但是,目前的向量化执行引擎在下面一些微小的细节上与原先的行存执行引擎存在不同,需要使用者知晓。这部分区别分为两类。a 类 :行存执行引擎需要被废弃和不推荐使用或依赖的功能Float 与 Double 类型计算可能产生精度误差,仅影响小数点后 5 位之后的数字。如果对计算精度有特殊要求,请使用 Decimal 类型。DateTime 类型不支持秒级别以下的计算或 format 等各种操作,向量化引擎会直接丢弃秒级别以下毫秒的计算结果。同时也不支持 microseconds_add 等,对毫秒计算的函数。有符号类型进行编码时,0 与-0 在 SQL 执行中被认为是相等的。这可能会影响distinct,group by 等计算的结果。bitmap/hll 类型在向量化执行引擎中:输入均为 NULL,则输出的结果为 NULL 而不是 0。b 类: 短期没有在向量化执行引擎上得到支持,但后续会得到开发支持的功能不支持原有行存执行引擎的 UDF 与 UDAF。string/text 类型最大长度支持为 1MB,而不是默认的 2GB。即当开启向量化引擎后,将无法查询或导入大于 1MB 的字符串。但如果关闭向量化引擎,则依然可以正常查询和导入。不支持 select … into outfile 的导出方式。不支持 external broker 外表。
0
0
0
浏览量517
攻城狮无远

【Databend】行列转化:一行变多行和简单分列

数据准备和需求行列转化在实际工作中很常见,其中最常见的有一行变多行,有下面一份数据:drop table if exists fact_suject_data; create table if not exists fact_suject_data ( student_id int null comment '编号', subject_level varchar null comment '科目等级', subject_level_json variant null comment '科目等级json数据' ); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (12,'china e,english d,math e','{"china": "e","english": "d","math": "e"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (2,'china b,english b','{"china": "b","english": "b"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (3,'english a,math c','{"english": "a","math": "c"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (4,'china c,math a','{"china": "c","math": "a"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (5,'china d,english a,math c','{"china": "d","english": "a","math": "c"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (6,'china c,english a,math d','{"china": "c","english": "a","math": "d"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (7,'china a,english e,math b','{"china": "a","english": "e","math": "b"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (8,'china d,english e,math e','{"china": "d","english": "e","math": "e"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (9,'china c,english e,math c','{"china": "c","english": "e","math": "c"}');需求是将学生学科等级和等级分隔成多行,效果如下:生成序列和分隔函数Databend 生成序列有专门的函数 generate_series(, [, <step_interval>]),生成从指定点开始,在另一个指定点结束的数据集,并且可以选择增量值。适用的数据类型有 整数、日期和时间戳。select generate_series as n from generate(1, 10); select generate_series as n from generate(1, 10, 2); +---+ | n | +---+ | 1 | +---+ | 3 | +---+ | 5 | +---+ | 7 | +---+ | 9 | +---+ select generate_series as n from generate_series('2024-01-01'::date, '2024-01-07'::date); +---------------+ | calendar_date | +---------------+ | 2024-01-01 | +---------------+ | 2024-01-02 | +---------------+ | 2024-01-03 | +---------------+ | 2024-01-04 | +---------------+ | 2024-01-05 | +---------------+ | 2024-01-06 | +---------------+ | 2024-01-07 | +---------------+split(<input_string>,):使用指定的分隔符拆分字符串,并将结果部分作为数组返回。split_part(<input_string>,, ):使用指定的分隔符拆分字符串并返回指定的部分。unnest(array):将数组拆分成多行。select subject_level , split(subject_level, ',') as split_char , split_part(subject_level, ',', 1) as part1 from (select 'china e,english d,math e' as subject_level) as a +--------------------------+----------------------------------+------------+ | subject_level | split_char | part1 | +--------------------------+----------------------------------+------------+ | china e,english d,math e | ['china e','english d','math e'] | china e | +--------------------------+----------------------------------+------------+ select subject_level , unnest(split(subject_level, ',')) as unne_char from (select 'china e,english d,math e' as subject_level) as a; +--------------------------+------------+ | subject_level | unne_char | +--------------------------+------------+ | china e,english d,math e | china e | +--------------------------+------------+split_part() 函数与 Mysql 中的 substring_index() 类似。根据分隔符变多行根据上面函数讲解,方法一:我们可以使用 split(<input_string>,) 和 unnest(array) 函数实现。select t1.student_id,t1.subject_level ,unnest(split(t1.subject_level,',')) as subject_level1 from fact_suject_data as t1 order by t1.student_id;方法二:也可以使用 split_part(<input_string>,, ) 单独实现。select t1.student_id , t1.subject_level , t2.n , split_part(t1.subject_level, ',', t2.n) as subject_level1 from fact_suject_data as t1 left join (select generate_series as n from generate_series(1, 30)) t2 on t2.n <= (length(t1.subject_level) - length(replace(t1.subject_level, ',', '')) + 1) order by t1.student_id;通过 generate_series() 生成的序列数值作为 split_part() 的分隔参数即可实现,与 Mysql 行列变换《你想要的都有》中分隔原理一致。JSON 数据简单分列对于 subject_level_json 列数据,我们可以使用 json 独有的函数实现分列透视的效果。select subject_level_json , replace(json_path_query(subject_level_json, '$.china'), '"', '') as china , get(subject_level_json, 'math') as math , get(subject_level_json, 'english') as english from fact_suject_data as t1 order by t1.student_id;总结数据分列和一行变多行的应用非常常见,通过本文的学习,相信基本上能处理类似问题,遇到面试相关问题也能完美解决,赶紧动手实操看看效果吧!!!参考资料:Databend Array Functions:https://docs.databend.com/sql/sql-functions/array-funct
0
0
0
浏览量516
攻城狮无远

Doris-05-集成Spark、Flink、Datax,以及数据湖分析

集成其他系统准备表和数据:CREATE TABLE table1 ( siteid INT DEFAULT '10', citycode SMALLINT, username VARCHAR(32) DEFAULT '', pv BIGINT SUM DEFAULT '0' ) AGGREGATE KEY(siteid, citycode, username) DISTRIBUTED BY HASH(siteid) BUCKETS 10 PROPERTIES("replication_num" = "1"); insert into table1 values (1,1,'jim',2), (2,1,'grace',2), (3,2,'tom',2), (4,3,'bush',3), (5,3,'helen',3);Spark 读写 DorisSpark Doris Connector 可以支持通过 Spark 读取 Doris 中存储的数据,也支持通过Spark写入数据到Doris。代码库地址:https://github.com/apache/incubator-doris-spark-connector支持从Doris中读取数据支持Spark DataFrame批量/流式 写入Doris可以将Doris表映射为DataFrame或者RDD,推荐使用DataFrame。支持在Doris端完成数据过滤,减少数据传输量。ConnectorSparkDorisJavaScala2.3.4-2.11.xx2.x0.12+82.113.1.2-2.12.xx3.x0.12.+82.123.2.0-2.12.xx3.2.x0.12.+82.12准备 Spark 环境创建 maven 工程,编写 pom.xml 文件:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu.doris</groupId> <artifactId>spark-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <scala.binary.version>2.12</scala.binary.version> <spark.version>3.0.0</spark.version> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <!-- Spark 的依赖引入 --> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-core_${scala.binary.version}</artifactId> <scope>provided</scope> <version>${spark.version}</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-sql_${scala.binary.version}</artifactId> <scope>provided</scope> <version>${spark.version}</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-hive_${scala.binary.version}</artifactId> <scope>provided</scope> <version>${spark.version}</version> </dependency> <!-- 引入 Scala --> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>2.12.10</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.47</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <!--spark-doris-connector--> <dependency> <groupId>org.apache.doris</groupId> <artifactId>spark-doris-connector-3.1_2.12</artifactId> <!--<artifactId>spark-doris-connector- 2.3_2.11</artifactId>--> <version>1.0.1</version> </dependency> </dependencies> <build> <plugins> <!--编译 scala 所需插件--> <plugin> <groupId>org.scala-tools</groupId> <artifactId>maven-scala-plugin</artifactId> <version>2.15.1</version> <executions> <execution> <id>compile-scala</id> <goals> <goal>add-source</goal> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile-scala</id> <goals> <goal>add-source</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.2</version> <executions> <execution> <!-- 声明绑定到 maven 的 compile 阶段 --> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> </execution> </executions> </plugin> <!-- assembly 打包插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.0.0</version> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> <configuration> <archive> <manifest> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> </plugin> <!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.6.1</version> <!– 所有的编译都依照 JDK1.8 –> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin>--> </plugins> </build> </project>使用 Spark Doris ConnectorSpark Doris Connector 可以支持通过 Spark 读取 Doris 中存储的数据,也支持通过Spark 写入数据到 Doris。(1)SQL 方式读写数据import org.apache.spark.SparkConf import org.apache.spark.sql.SparkSession /** * TODO * * @version 1.0 * @author cjp */ object SQLDemo { def main( args: Array[String] ): Unit = { val sparkConf = new SparkConf().setAppName("SQLDemo") .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉 val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate() sparkSession.sql( """ |CREATE TEMPORARY VIEW spark_doris |USING doris |OPTIONS( | "table.identifier"="test_db.table1", | "fenodes"="hadoop1:8030", | "user"="test", | "password"="test" |); """.stripMargin) //读取数据 // sparkSession.sql("select * from spark_doris").show() //写入数据 sparkSession.sql("insert into spark_doris values(99,99,'haha',5)") } }(2)DataFrame 方式读写数据(batch)import org.apache.spark.SparkConf import org.apache.spark.sql.SparkSession /** * TODO * * @version 1.0 * @author cjp */ object DataFrameDemo { def main( args: Array[String] ): Unit = { val sparkConf = new SparkConf().setAppName("DataFrameDemo") .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉 val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate() // 读取数据 // val dorisSparkDF = sparkSession.read.format("doris") // .option("doris.table.identifier", "test_db.table1") // .option("doris.fenodes", "hadoop1:8030") // .option("user", "test") // .option("password", "test") // .load() // dorisSparkDF.show() // 写入数据 import sparkSession.implicits._ val mockDataDF = List( (11,23, "haha", 8), (11, 3, "hehe", 9), (11, 3, "heihei", 10) ).toDF("siteid", "citycode", "username","pv") mockDataDF.show(5) mockDataDF.write.format("doris") .option("doris.table.identifier", "test_db.table1") .option("doris.fenodes", "hadoop1:8030") .option("user", "test") .option("password", "test") //指定你要写入的字段 // .option("doris.write.fields", "user") .save() } }(3)RDD 方式读取数据import org.apache.spark.{SparkConf, SparkContext} import org.apache.spark.sql.SparkSession /** * TODO * * @version 1.0 * @author cjp */ object RDDDemo { def main( args: Array[String] ): Unit = { val sparkConf = new SparkConf().setAppName("RDDDemo") .setMaster("local[*]") //TODO 要打包提交集群执行,注释掉 val sc = new SparkContext(sparkConf) import org.apache.doris.spark._ val dorisSparkRDD = sc.dorisRDD( tableIdentifier = Some("test_db.table1"), cfg = Some(Map( "doris.fenodes" -> "hadoop1:8030", "doris.request.auth.user" -> "test", "doris.request.auth.password" -> "test" )) ) dorisSparkRDD.collect().foreach(println) } }(4)配置和字段类型映射通用配置项KeyDefault ValueCommentdoris.fenodes–Doris FE http 地址,支持多个地址,使用逗号分隔doris.table.identifier–Doris 表名,如:db1.tbl1doris.request.retries3向Doris发送请求的重试次数doris.request.connect.timeout.ms30000向Doris发送请求的连接超时时间doris.request.read.timeout.ms 30000 向Doris发送请求的读取超时时间doris.request.query.timeout.s 3600 查询doris的超时时间,默认值为1小时,-1表示无超时限制doris.request.tablet.sizeInteger.MAX_VALUE一个RDD Partition对应的Doris Tablet个数。 此数值设置越小,则会生成越多的Partition。从而提升Spark侧的并行度,但同时会对Doris造成更大的压力。doris.batch.size1024一次从BE读取数据的最大行数。增大此数值可减少Spark与Doris之间建立连接的次数。 从而减轻网络延迟所带来的额外时间开销。doris.exec.mem.limit2147483648单个查询的内存限制。默认为 2GB,单位为字节doris.deserialize.arrow.asyncfalse是否支持异步转换Arrow格式到spark-doris-connector迭代所需的RowBatchdoris.deserialize.queue.size64异步转换Arrow格式的内部处理队列,当doris.deserialize.arrow.async为true时生效doris.write.fields–指定写入Doris表的字段或者字段顺序,多列之间使用逗号分隔。 默认写入时要按照Doris表字段顺序写入全部字段。sink.batch.size10000单次写BE的最大行数sink.max-retries1写BE失败之后的重试次数SQL 和 Dataframe 专有配置KeyDefault ValueCommentuser–访问Doris的用户名password–访问Doris的密码doris.filter.query.in.max.count 100谓词下推中,in表达式value列表元素最大数量。超过此数量,则in表达式条件过滤在Spark侧处理。RDD 专有配置KeyDefault ValueCommentdoris.request.auth.user–访问Doris的用户名doris.request.auth.password–访问Doris的密码doris.read.field–读取Doris表的列名列表,多列之间使用逗号分隔doris.filter.query Doris 和 Spark 列类型映射关系:Doris TypeSpark TypeNULL_TYPEDataTypes.NullTypeBOOLEANDataTypes.BooleanTypeTINYINTDataTypes.ByteTypeSMALLINTDataTypes.ShortTypeINTDataTypes.IntegerTypeBIGINTDataTypes.LongTypeFLOATDataTypes.FloatTypeDOUBLEDataTypes.DoubleTypeDATEDataTypes.StringType1DATETIMEDataTypes.StringType1BINARYDataTypes.BinaryTypeDECIMALDecimalTypeCHARDataTypes.StringTypeLARGEINTDataTypes.StringTypeVARCHARDataTypes.StringTypeDECIMALV2DecimalTypeTIMEDataTypes.DoubleTypeHLLUnsupported datatype注:Connector中,将DATE和DATETIME映射为String。由于Doris底层存储引擎处理逻辑,直接使用时间类型时,覆盖的时间范围无法满足需求。所以使用 String 类型直接返回对应的时间可读文本。Flink Doris ConnectorFlink Doris Connector 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。Flink Doris Connector Sink 的内部实现是通过 Stream load 服务向 Doris 写入数据, 同时也支持 Stream load 请求参数的配置设定。版本兼容如下:ConnectorFlinkDorisJavaScala1.14_2.11-1.1.01.14.x1.0+82.111.14_2.12-1.1.01.14.x1.0+82.12准备Flink环境创建 maven 工程,编写 pom.xml 文件:<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.atguigu.doris</groupId> <artifactId>flink-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> <flink.version>1.13.1</flink.version> <java.version>1.8</java.version> <scala.binary.version>2.12</scala.binary.version> <slf4j.version>1.7.30</slf4j.version> </properties> <dependencies> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-java</artifactId> <version>${flink.version}</version> <scope>provided</scope> <!--不会打包到依赖中,只参与编译,不 参与运行 --> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-streamingjava_${scala.binary.version}</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flinkclients_${scala.binary.version}</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-table-plannerblink_${scala.binary.version}</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency> <!----> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-runtimeweb_${scala.binary.version}</artifactId> <version>${flink.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-to-slf4j</artifactId> <version>2.14.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-statebackendrocksdb_${scala.binary.version}</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-sequence-file</artifactId> <version>${flink.version}</version> </dependency> <dependency> <groupId>com.ververica</groupId> <artifactId>flink-connector-mysql-cdc</artifactId> <version>2.0.0</version> </dependency> <!--flink-doris-connector--> <dependency> <groupId>org.apache.doris</groupId> <!--<artifactId>flink-doris-connector- 1.14_2.12</artifactId>--> <artifactId>flink-doris-connector-1.13_2.12</artifactId> <!--<artifactId>flink-doris-connector- 1.12_2.12</artifactId>--> <!--<artifactId>flink-doris-connector- 1.11_2.12</artifactId>--> <version>1.0.3</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <artifactSet> <excludes> <exclude>com.google.code.findbugs:jsr305</exclude> <exclude>org.slf4j:*</exclude> <exclude>log4j:*</exclude> <exclude>org.apache.hadoop:*</exclude> </excludes> </artifactSet> <filters> <filter> <!-- Do not copy the signatures in the META-INF folder. Otherwise, this might cause SecurityExceptions when using the JAR. --> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <transformers combine.children="append"> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesR esourceTransformer"> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> </project> 使用Flink Doris ConnectorFlink Doris Connector 可以支持通过 Flink 操作(读取、插入、修改、删除) Doris 中存储的数据。(1)SQL方式读写import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; /** * TODO * * @author cjp * @version 1.0 */ public class SQLDemo { public static void main(String[] args) { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); StreamTableEnvironment tableEnv = StreamTableEnvironment.create(env); tableEnv.executeSql("CREATE TABLE flink_doris (\n" + " siteid INT,\n" + " citycode SMALLINT,\n" + " username STRING,\n" + " pv BIGINT\n" + " ) \n" + " WITH (\n" + " 'connector' = 'doris',\n" + " 'fenodes' = 'hadoop1:8030',\n" + " 'table.identifier' = 'test_db.table1',\n" + " 'username' = 'test',\n" + " 'password' = 'test'\n" + ")\n"); // 读取数据 // tableEnv.executeSql("select * from flink_doris").print(); // 写入数据 tableEnv.executeSql("insert into flink_doris(siteid,username,pv) values(22,'wuyanzu',3)"); } } (2)DataStream 读写sourceimport org.apache.doris.flink.cfg.DorisStreamOptions; import org.apache.doris.flink.datastream.DorisSourceFunction; import org.apache.doris.flink.deserialization.SimpleListDeserializationSchema; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.api.bridge.java.StreamTableEnvironment; import java.util.Properties; /** * TODO * * @author cjp * @version 1.0 */ public class DataStreamSourceDemo { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); Properties properties = new Properties(); properties.put("fenodes","hadoop1:8030"); properties.put("username","test"); properties.put("password","test"); properties.put("table.identifier","test_db.table1"); env.addSource(new DorisSourceFunction( new DorisStreamOptions(properties), new SimpleListDeserializationSchema() ) ).print(); env.execute(); } } SinkJson 数据流:import org.apache.doris.flink.cfg.*; import org.apache.doris.flink.datastream.DorisSourceFunction; import org.apache.doris.flink.deserialization.SimpleListDeserializationS chema; import org.apache.flink.streaming.api.environment.StreamExecutionEnviron ment; import java.util.Properties; /** * TODO * * @author cjp * @version 1.0 */ public class DataStreamJsonSinkDemo { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); Properties pro = new Properties(); pro.setProperty("format", "json"); pro.setProperty("strip_outer_array", "true"); env.fromElements( "{\"longitude\": \"116.405419\", \"city\": \" 北京\", \"latitude\": \"39.916927\"}" ) .addSink( DorisSink.sink( DorisReadOptions.builder().build(), DorisExecutionOptions.builder() .setBatchSize(3) .setBatchIntervalMs(0L) .setMaxRetries(3) .setStreamLoadProp(pro).build(), DorisOptions.builder() .setFenodes("FE_IP:8030") .setTableIdentifier("db.table") .setUsername("root") .setPassword("").build() )); // .addSink( // DorisSink.sink( // DorisOptions.builder() // .setFenodes("FE_IP:8030") // .setTableIdentifier("db.table") // .setUsername("root") // .setPassword("").build() // )); env.execute(); } } RowData 数据流:import org.apache.doris.flink.cfg.DorisExecutionOptions; import org.apache.doris.flink.cfg.DorisOptions; import org.apache.doris.flink.cfg.DorisReadOptions; import org.apache.doris.flink.cfg.DorisSink; import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.data.GenericRowData; import org.apache.flink.table.data.RowData; import org.apache.flink.table.data.StringData; import org.apache.flink.table.types.logical.*; /** * TODO * @author cjp * @version 1.0 */ public class DataStreamRowDataSinkDemo { public static void main(String[] args) throws Exception { StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); DataStream<RowData> source = env.fromElements("") .map(new MapFunction<String, RowData>() { @Override public RowData map(String value) throws Exception { GenericRowData genericRowData = new GenericRowData(4); genericRowData.setField(0, 33); genericRowData.setField(1, new Short("3")); genericRowData.setField(2, StringData.fromString("flink-stream")); genericRowData.setField(3, 3L); return genericRowData; } }); LogicalType[] types = {new IntType(), new SmallIntType(), new VarCharType(32), new BigIntType()}; String[] fields = {"siteid", "citycode", "username", "pv"}; source.addSink( DorisSink.sink( fields, types, DorisReadOptions.builder().build(), DorisExecutionOptions.builder() .setBatchSize(3) .setBatchIntervalMs(0L) .setMaxRetries(3) .build(), DorisOptions.builder() .setFenodes("hadoop1:8030") .setTableIdentifier("test_db.table1") .setUsername("test") .setPassword("test").build() )); env.execute(); } } (3)通用配置项和字段类型映射通用配置项:KeyDefault ValueRequiredCommentfenodes–YDoris FE http 地址table.identifier–YDoris 表名,如:db.tblusername–Y访问 Doris 的用户名password–Y访问 Doris 的密码doris.request.retries3N向 Doris 发送请求的重试次数doris.request.connect.timeout.ms30000N向 Doris 发送请求的连接超时时间doris.request.read.timeout.ms30000N向 Doris 发送请求的读取超时时间doris.request.query.timeout.s3600N查询 Doris 的超时时间,默认值为1小时,-1表示无超时限制doris.request.tablet.sizeInteger. MAX_VALUEN一个 Partition 对应的 Doris Tablet 个数。 此数值设置越小,则会生成越多的 Partition。从而提升 Flink 侧的并行度,但同时会对 Doris 造成更大的压力。doris.batch.size1024N一次从 BE 读取数据的最大行数。增大此数值可减少 Flink 与 Doris 之间建立连接的次数。 从而减轻网络延迟所带来的额外时间开销。doris.exec.mem.limit2147483648 N单个查询的内存限制。默认为 2GB,单位为字节doris.deserialize.arrow.asyncFALSEN是否支持异步转换 Arrow 格式到 flink-doris-connector 迭代所需的 RowBatchdoris.deserialize.queue.size64N异步转换 Arrow 格式的内部处理队列,当 doris.deserialize.arrow.async 为 true 时生效doris.read.field–N读取 Doris 表的列名列表,多列之间使用逗号分隔doris.filter.query–N过滤读取数据的表达式,此表达式透传给 Doris。Doris 使用此表达式完成源端数据过滤。sink.label-prefix–YStream load导入使用的label前缀。2pc场景下要求全局唯一 ,用来保证Flink的EOS语义。sink.properties.*–NStream Load 的导入参数。 例如: ‘sink.properties.column_separator’ = ‘, ’ 定义列分隔符, ‘sink.properties.escape_delimiters’ = ‘true’ 特殊字符作为分隔符,’\x01’会被转换为二进制的0x01 JSON格式导入 ‘sink.properties.format’ = ‘json’ ‘sink.properties.read_json_by_line’ = ‘true’sink.enable-deleteTRUEN是否启用删除。此选项需要 Doris 表开启批量删除功能(Doris0.15+版本默认开启),只支持 Unique 模型。sink.enable-2pcTRUEN是否开启两阶段提交(2pc),默认为true,保证Exactly-Once语义。关于两阶段提交可参考这里。Doris 和 Flink 列类型映射关系:Doris TypeFlink TypeNULL_TYPENULLBOOLEANBOOLEANTINYINTTINYINTSMALLINTSMALLINTINTINTBIGINTBIGINTFLOATFLOATDOUBLEDOUBLEDATEDATEDATETIMETIMESTAMPDECIMALDECIMALCHARSTRINGLARGEINTSTRINGVARCHARSTRINGDECIMALV2DECIMALTIMEDOUBLEHLLUnsupported datatypeDataX doriswriterDorisWriter 支持将大批量数据写入 Doris 中。DorisWriter 通过 Doris 原生支持 Stream load 方式导入数据,DorisWriter 会将 reader 读取的数据进行缓存在内存中,拼接成 Json 文本,然后批量导入至 Doris。DorisWriter需要进行编译后得到插件添加到Datax中,才能进行使用。可以自己编译,也可以直接使用编译好的包:(1)进入之前的容器环境docker run -it \ -v /opt/software/.m2:/root/.m2 \ -v /opt/software/apache-doris-0.15.0-incubating-src/:/root/apache-doris-0.15.0-incubating-src/ \ apache/incubator-doris:build-env-for-0.15.0(2)运行 init-env.shcd /root/apache-doris-0.15.0-incubating-src/extension/DataX sh init-env.sh主要做了下面几件事,减少了繁杂的操作:将 DataX 代码库 clone 到本地。将 doriswriter/ 目录软链到 DataX/doriswriter 目录。这个目录是 doriswriter 插件的代码目录。这个目录中的所有代码,都托管在 Apache Doris 的代码库中。在 DataX/pom.xml 文件中添加 doriswriter 模块。将 DataX/core/pom.xml 文件中的 httpclient 版本从 4.5 改为 4.5.13(因为有bug)(3)手动上传依赖alibaba-datax-maven-m2-20210928.tar.gz在编译的时候如果没有这个依赖,可能汇报错:Could not find artifact com.alibaba.datax:datax-all:pom:0.0.1-SNAPSHOT ...可尝试以下方式解决:下载 alibaba-datax-maven-m2-20210928.tar.gz,并上传;解压:tar -zxvf alibaba-datax-maven-m2-20210928.tar.gz -C /opt/software拷贝解压后的文件到 maven 仓库:sudo cp -r /opt/software/alibaba/datax/ /opt/software/.m2/repository/com/alibaba/ (4)编译 doriswriter单独编译 doriswriter 插件:cd /root/apache-doris-0.15.0-incubating-src/extension/DataX/DataX mvn clean install -pl plugin-rdbms-util,doriswriter -DskipTests 编译整个 DataX 项目:cd /root/apache-doris-0.15.0-incubating-src/extension/DataX/DataX mvn package assembly:assembly -Dmaven.test.skip=true 产出在 target/datax/datax/.hdfsreader, hdfswriter and oscarwriter 这三个插件需要额外的 jar 包。如果你并不需要这些插件,可以在 DataX/pom.xml 中删除这些插件的模块。(5)拷贝编译好的插件到 DataXSudo cp -r /opt/software/apache-doris-0.15.0-incubating-src/extension/DataX/doriswriter/target/datax/plugin/writer/dorisw riter /opt/module/datax/plugin/writer使用:# MySQL 建表、插入测试数据 CREATE TABLE `sensor` ( `id` varchar(255) NOT NULL, `ts` bigint(255) DEFAULT NULL, `vc` int(255) DEFAULT NULL, PRIMARY KEY (`id`) ) insert into sensor values('s_2',3,3),('s_9',9,9); # Doris 建表 CREATE TABLE `sensor` ( `id` varchar(255) NOT NULL, `ts` bigint(255) DEFAULT NULL, `vc` int(255) DEFAULT NULL ) DISTRIBUTED BY HASH(`id`) BUCKETS 10; vim mysql2doris.json { "job": { "setting": { "speed": { "channel": 1 }, "errorLimit": { "record": 0, "percentage": 0 } }, "content": [ { "reader": { "name": "mysqlreader", "parameter": { "column": [ "id", "ts", "vc" ], "connection": [ { "jdbcUrl": [ "jdbc:mysql://hadoop1:3306/test" ], "table": [ "sensor" ] } ], "username": "root", "password": "000000" } }, "writer": { "name": "doriswriter", "parameter": { "feLoadUrl": ["hadoop1:8030", "hadoop2:8030", "hadoop3:8030"], "beLoadUrl": ["hadoop1:8040", "hadoop2:8040", "hadoop3:8040"], "jdbcUrl": "jdbc:mysql://hadoop1:9030/", "database": "test_db", "table": "sensor", "column": ["id", "ts", "vc"], "username": "test", "password": "test", "postSql": [], "preSql": [], "loadProps": { }, "maxBatchRows" : 500000, "maxBatchByteSize" : 104857600, "labelPrefix": "my_prefix", "lineDelimiter": "\n" } } } ] } } 参数说明:jdbcUrl描述:Doris 的 JDBC 连接串,用户执行 preSql 或 postSQL。必选:是默认值:无feLoadUrl描述:和 beLoadUrl 二选一。作为 Stream Load 的连接目标。格式为 “ip:port”。其中IP 是 FE 节点 IP,port 是 FE 节点的 http_port。可以填写多个,doriswriter 将以轮询的方式访问。必选:否默认值:无beLoadUrl描述:和 feLoadUrl 二选一。作为 Stream Load 的连接目标。格式为 “ip:port”。其中 IP 是 BE 节点 IP,port 是 BE 节点的 webserver_port。可以填写多个,doriswriter 将以轮询的方式访问。必选:否默认值:无username描述:访问 Doris 数据库的用户名必选:是默认值:无password描述:访问 Doris 数据库的密码必选:否默认值:空database描述:需要写入的 Doris 数据库名称。必选:是默认值:无table描述:需要写入的 Doris 表名称。必选:是默认值:无column描述:目的表需要写入数据的字段,这些字段将作为生成的 Json 数据的字段名。字段之间用英文逗号分隔。例如: “column”: [“id”,“name”,“age”]。必选:是默认值:否preSql描述:写入数据到目的表前,会先执行这里的标准语句。必选:否默认值:无postSql描述:写入数据到目的表后,会执行这里的标准语句。必选:否默认值:无maxBatchRows描述:每批次导入数据的最大行数。和 maxBatchByteSize 共同控制每批次的导入数量。每批次数据达到两个阈值之一,即开始导入这一批次的数据。必选:否默认值:500000maxBatchByteSize描述:每批次导入数据的最大数据量。和 maxBatchRows 共同控制每批次的导入数量。每批次数据达到两个阈值之一,即开始导入这一批次的数据。必选:否默认值:104857600labelPrefix描述:每批次导入任务的 label 前缀。最终的 label 将有 labelPrefix + UUID + 序号 组 成必选:否默认值:datax_doris_writer_lineDelimiter描述:每批次数据包含多行,每行为 Json 格式,每行的的分隔符即为 lineDelimiter。支持多个字节, 例如’\x02\x03’。必选:否默认值:\nloadProps描述:StreamLoad 的请求参数,详情参照 StreamLoad 介绍页面。必选:否默认值:无connectTimeout描述:StreamLoad 单次请求的超时时间, 单位毫秒(ms)。必选:否默认值:-1数据湖分析JDBC和ODBCJDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,是一个标准,一个协议,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。简言之,JDBC就是Java用于执行SQL语句实现数据库操作的API。当JDBC提出标准以后,由对应的数据库厂商来进行相应的实现,而这些实现JDBC接口的驱动程序才是真正操作数据库的东西。所以基于这种设计,我们只需要面向JDBC这一个统一的接口进行开发,就可以实现对不同的数据库进行操作了。ODBC是早期的数据库规范,是开放式数据库连接。与JDBC一样,ODBC也是一个API,充当客户端应用程序和服务器端数据库之间的接口。ODBC是最广泛使用的,并且可以理解许多不同的编程语言。但它的代码很复杂,难以理解。JDBC和ODBC的区别:JDBC代表java数据库连接,是面向对象的。而ODBC代表开放式数据库连接,是程序性的。JDBC只能将其用于Java语言开发的程序中,可以在任何平台上使用;ODBC可以将其用于任何语言,如C,C ++等本地语言开发的ODBC驱动程序,仅可以选择在Windows平台上使用。对于Java应用程序,不建议使用ODBC,因为内部转换会导致性能下降,应用程序将变为平台相关;强烈建议使用JDBC,因为我们没有性能和平台相关的问题。ODBC的代码很复杂,很难学习。但是,JDBC的代码更简单,更容易运行。ODBC 外部表ODBC External Table Of Doris 提供了 Doris 通过数据库访问的标准接口(ODBC)来访问外部表,外部表省去了繁琐的数据导入工作,让 Doris 可以具有了访问各式数据库的能力,并借助 Doris 本身的 OLAP 的能力来解决外部表的数据分析问题:支持各种数据源接入 Doris支持 Doris 与各种数据源中的表联合查询,进行更加复杂的分析操作通过 insert into 将 Doris 执行的查询结果写入外部的数据源使用方式(1)ODBC Driver 的安装和配置各大主流数据库都会提供 ODBC 的访问 Driver,用户可以执行参照各数据库官方推荐的方式安装对应的 ODBC Driver LiB 库。安装完成之后,查找对应的数据库的 Driver Lib 库的路径,并且修改 be/conf/odbcinst.ini的配置:[MySQL Driver] Description = ODBC for MySQL Driver = /usr/lib64/libmyodbc8w.so FileUsage = 1 上述配置[]里的对应的是 Driver 名,在建立外部表时需要保持外部表的 Driver 名和配置文件之中的一致。Driver= 这个要根据实际 BE 安装 Driver 的路径来填写,本质上就是一个动态库的路径,这里需要保证该动态库的前置依赖都被满足。切记,这里要求所有的 BE 节点都安装上相同的 Driver,并且安装路径相同,同时有相同的 be/conf/odbcinst.ini 的配置。(2)Doris 中创建 ODBC 的外表方式一:不使用 Resource 创建 ODBC 的外表CREATE EXTERNAL TABLE `baseall_oracle` ( `k1` decimal(9, 3) NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=ODBC COMMENT "ODBC" PROPERTIES ( "host" = "192.168.0.1", "port" = "8086", "user" = "test", "password" = "test", "database" = "test", "table" = "baseall", "driver" = "Oracle 19 ODBC driver", "odbc_type" = "oracle" ); 方式二:通过 ODBC_Resource 来创建 ODBC 外表(推荐使用的方式)。CREATE EXTERNAL RESOURCE `oracle_odbc` PROPERTIES ( "type" = "odbc_catalog", "host" = "192.168.0.1", "port" = "8086", "user" = "test", "password" = "test", "database" = "test", "odbc_type" = "oracle", "driver" = "Oracle 19 ODBC driver" ); CREATE EXTERNAL TABLE `baseall_oracle` ( `k1` decimal(9, 3) NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=ODBC COMMENT "ODBC" PROPERTIES ( "odbc_catalog_resource" = "oracle_odbc", "database" = "test", "table" = "baseall" ); 参数说明:hosts :外表数据库的 IP 地址driver :ODBC 外表 Driver 名,需要和 be/conf/odbcinst.ini 中的 Driver 名一致。odbc_type :外表数据库的类型,当前支持 oracle, mysql, postgresqluser :外表数据库的用户名password :对应用户的密码信息(3)查询用法完成在Doris中建立ODBC外表后,除了无法使用Doris中的数据模型(rollup、预聚合、物化视图等)外,与普通的Doris表并无区别:select * from oracle_table where k1 > 1000 and k3 ='term' or k4 like '%doris';(4)数据写入在Doris中建立ODBC外表后,可以通过insert into语句直接写入数据,也可以将Doris执行完查询之后的结果写入ODBC外表,或者是从一个ODBC外表将数据导入另一个ODBC外表。insert into oracle_table values(1, "doris"); insert into oracle_table select * from postgre_table;(5)事务Doris的数据是由一组batch的方式写入外部表的,如果中途导入中断,之前写入数据可能需要回滚。所以ODBC外表支持数据写入时的事务,事务的支持需要通过session variable:enable_odbc_transcation 设置。set enable_odbc_transcation = true; 事务保证了ODBC外表数据写入的原子性,但是一定程度上会降低数据写入的性能,可以考虑酌情开启该功能。使用 ODBC 的 MySQL 外表CentOS 数据库 ODBC 版本对应关系:Mysql版本Mysql ODBC版本8.0.278.0.27,8.0265.7.365.3.11,5.3.135.6.515.3.11,5.3.135.5.625.3.11,5.3.13MySQL 与 Doris 的数据类型匹配:MySQLDoris替换方案BOOLEANBOOLEANCHARCHAR当前仅支持UTF8编码VARCHARVARCHAR当前仅支持UTF8编码DATEDATEFLOATFLOATTINYINTTINYINTSMALLINTSMALLINTINTINTBIGINTBIGINTDOUBLEDOUBLEDATETIMEDATETIMEDECIMALDECIMAL(1)安装 unixODBC(可选)安装 yum install -y unixODBC unixODBC-devel libtool-ltdl libtool-ltdl-devel 查看是否安装成功 odbcinst -j(2)安装 MySQL 对应版本的 ODBC(每个 BE 节点都要)下载 wget https://downloads.mysql.com/archives/get/p/10/file/mysql-connector-odbc-5.3.11-1.el7.x86_64.rpm 安装 yum install -y mysql-connector-odbc-5.3.11-1.el7.x86_64.rpm 查看是否安装成功 myodbc-installer -d -l(3)配置 unixODBC,验证通过 ODBC 访问 Mysql编辑 ODBC 配置文件 vim /etc/odbc.ini [mysql] Description = Data source MySQL Driver = MySQL ODBC 5.3 Unicode Driver Server = hadoop1 Host = hadoop1 Database = test Port = 3306 User = root Password = 000000 测试链接 isql -v mysql(4)准备 MySQL 表CREATE TABLE `test_cdc` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=91234 DEFAULT CHARSET=utf8mb4; INSERT INTO `test_cdc` VALUES (123, 'this is a update'); INSERT INTO `test_cdc` VALUES (1212, '测试 flink CDC'); INSERT INTO `test_cdc` VALUES (1234, '这是测试'); INSERT INTO `test_cdc` VALUES (11233, 'zhangfeng_1'); INSERT INTO `test_cdc` VALUES (21233, 'zhangfeng_2'); INSERT INTO `test_cdc` VALUES (31233, 'zhangfeng_3'); INSERT INTO `test_cdc` VALUES (41233, 'zhangfeng_4'); INSERT INTO `test_cdc` VALUES (51233, 'zhangfeng_5'); INSERT INTO `test_cdc` VALUES (61233, 'zhangfeng_6'); INSERT INTO `test_cdc` VALUES (71233, 'zhangfeng_7'); INSERT INTO `test_cdc` VALUES (81233, 'zhangfeng_8'); INSERT INTO `test_cdc` VALUES (91233, 'zhangfeng_9');(5)修改 Doris 的配置文件(每个 BE 节点都要,不用重启 BE)在 BE 节点的 conf/odbcinst.ini,添加我们的刚才注册的的 ODBC 驱动([MySQL ODBC 5.3.11]这部分)。# Driver from the postgresql-odbc package # Setup from the unixODBC package [PostgreSQL] Description = ODBC for PostgreSQL Driver = /usr/lib/psqlodbc.so Setup = /usr/lib/libodbcpsqlS.so FileUsage = 1 # Driver from the mysql-connector-odbc package # Setup from the unixODBC package [MySQL ODBC 5.3.11] Description = ODBC for MySQL Driver= /usr/lib64/libmyodbc5w.so FileUsage = 1 # Driver from the oracle-connector-odbc package # Setup from the unixODBC package [Oracle 19 ODBC driver] Description=Oracle ODBC driver for Oracle 19 Driver=/usr/lib/libsqora.so.19.1(6)Doris 建 Resource通过 ODBC_Resource 来创建 ODBC 外表,这是推荐的方式,这样 resource 可以复用。CREATE EXTERNAL RESOURCE `mysql_5_3_11` PROPERTIES ( "host" = "hadoop1", "port" = "3306", "user" = "root", "password" = "000000", "database" = "test", "table" = "test_cdc", "driver" = "MySQL ODBC 5.3.11", --名称要和上面[]里的名称一致 "odbc_type" = "mysql", "type" = "odbc_catalog")(7)基于 Resource 创建 Doris 外表CREATE EXTERNAL TABLE `test_odbc_5_3_11` ( `id` int NOT NULL , `name` varchar(255) null ) ENGINE=ODBC COMMENT "ODBC" PROPERTIES ( "odbc_catalog_resource" = "mysql_5_3_11", --名称就是 resource 的名称 "database" = "test", "table" = "test_cdc" );(8)查询 Doris 外表select * from `test_odbc_5_3_11`;使用 ODBC 的 Oracle 外表CentOS 数据库 ODBC 版本对应关系:Oracle版本Oracle ODBC版本Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Productionoracle-instantclient19.13-odbc-19.13.0.0.0Oracle Database 12c Standard Edition Release 12.2.0.1.0 - 64bit Productionoracle-instantclient19.13-odbc-19.13.0.0.0Oracle Database 18c Enterprise Edition Release 18.0.0.0.0 - Productionoracle-instantclient19.13-odbc-19.13.0.0.0Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Productionoracle-instantclient19.13-odbc-19.13.0.0.0Oracle Database 21c Enterprise Edition Release 21.0.0.0.0 - Productionoracle-instantclient19.13-odbc-19.13.0.0.0与 Doris 的数据类型匹配:OracleDoris替换方案不支持BOOLEANOracle可用number(1) 替换booleanCHARCHARVARCHARVARCHARDATEDATEFLOATFLOAT无TINYINTOracle可由NUMMBER替换SMALLINTSMALLINTINTINT无BIGINTOracle可由NUMMBER替换无DOUBLEOracle可由NUMMBER替换DATETIMEDATETIMENUMBERDECIMAL(1)安装 Oracle 对应版本的 ODBC(每个 BE 节点都要):下载 4 个安装包 wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-sqlplus-19.13.0.0.0-2.x86_64.rpm wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-devel-19.13.0.0.0-2.x86_64.rpm wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-odbc-19.13.0.0.0-2.x86_64.rpm wget https://download.oracle.com/otn_software/linux/instantclient/1913000/oracle-instantclient19.13-basic-19.13.0.0.0-2.x86_64.rpm 安装 4 个安装包 rpm -ivh oracle-instantclient19.13-basic-19.13.0.0.0-2.x86_64.rpm rpm -ivh oracle-instantclient19.13-devel-19.13.0.0.0-2.x86_64.rpm rpm -ivh oracle-instantclient19.13-odbc-19.13.0.0.0-2.x86_64.rpm rpm -ivh oracle-instantclient19.13-sqlplus-19.13.0.0.0-2.x86_64.rpm(2)修改 Doris 的配置(每个 BE 节点都要,不用重启)修改 BE 节点 conf/odbcinst.ini 文件,加入刚才/etc/odbcinst.ini 添加的一样内容,并删除原先的 Oracle 配置:[Oracle 19 ODBC driver] Description = Oracle ODBC driver for Oracle 19 Driver = /usr/lib/oracle/19.13/client64/lib/libsqora.so.19.1(3)创建 ResourceCREATE EXTERNAL RESOURCE `oracle_19` PROPERTIES ( "host" = "hadoop2", "port" = "1521", "user" = "atguigu", "password" = "000000", "database" = "orcl", --数据库示例名称,也就是 ORACLE_SID "driver" = "Oracle 19 ODBC driver", --名称一定和 be odbcinst.ini里的 oracle 部分的[]里的内容一样 "odbc_type" = "oracle", "type" = "odbc_catalog" );(4)基于 Resource 创建 Doris 外表CREATE EXTERNAL TABLE `oracle_odbc` ( id int, name VARCHAR(20) NOT NULL ) ENGINE=ODBC COMMENT "ODBC" PROPERTIES ( "odbc_catalog_resource" = "oracle_19", "database" = "orcl", "table" = "student" );ES外表Doris-On-ES 将 Doris 的分布式查询规划能力和 ES(Elasticsearch)的全文检索能力相结合,提供更完善的 OLAP 分析场景解决方案:ES 中的多 index 分布式 Join 查询Doris 和 ES 中的表联合查询,更复杂的全文检索过滤创建 ES 外表后,FE 会请求建表指定的主机,获取所有节点的 HTTP 端口信息以及 index 的 shard 分布信息等,如果请求失败会顺序遍历 host 列表直至成功或完全失败查询时会根据 FE 得到的一些节点信息和 index 的元数据信息,生成查询计划并发给对应的 BE 节点BE 节点会根据就近原则即优先请求本地部署的 ES 节点,BE 通过 HTTP Scroll 方式流式的从 ES index 的每个分片中并发的从_source 或 docvalue 中获取数据Doris 计算完结果后,返回给用户使用方式(1)Doris 中创建 ES 外表创建 ES 索引PUT test { "settings": { "index": { "number_of_shards": "1", "number_of_replicas": "0" } }, "mappings": { "doc": { // ES 7.x 版本之后创建索引时不需要指定 type,会有一个默认且唯 一的`_doc` type "properties": { "k1": { "type": "long" }, "k2": { "type": "date" }, "k3": { "type": "keyword" }, "k4": { "type": "text", "analyzer": "standard" }, "k5": { "type": "float" } } } } } ES 索引导入数据POST /_bulk {"index":{"_index":"test","_type":"doc"}} { "k1" : 100, "k2": "2020-01-01", "k3": "Trying out Elasticsearch", "k4": "Trying out Elasticsearch", "k5": 10.0} {"index":{"_index":"test","_type":"doc"}} { "k1" : 100, "k2": "2020-01-01", "k3": "Trying out Doris", "k4": "Trying out Doris", "k5": 10.0} {"index":{"_index":"test","_type":"doc"}} { "k1" : 100, "k2": "2020-01-01", "k3": "Doris On ES", "k4": "Doris On ES", "k5": 10.0} {"index":{"_index":"test","_type":"doc"}} { "k1" : 100, "k2": "2020-01-01", "k3": "Doris", "k4": "Doris", "k5": 10.0} {"index":{"_index":"test","_type":"doc"}} { "k1" : 100, "k2": "2020-01-01", "k3": "ES", "k4": "ES", "k5": 10.0} Doris 中创建 ES 外表CREATE EXTERNAL TABLE `es_test` ( `k1` bigint(20) COMMENT "", `k2` datetime COMMENT "", `k3` varchar(20) COMMENT "", `k4` varchar(100) COMMENT "", `k5` float COMMENT "" ) ENGINE=ELASTICSEARCH // ENGINE 必须是 Elasticsearch PROPERTIES ( "hosts" = "http://hadoop1:9200,http://hadoop2:9200,http://hadoop3:9200", "index" = "test", "type" = "doc", "user" = "", "password" = "" ); 参数说明:参数说明hostsES集群地址,可以是一个或多个,也可以是ES前端的负载均衡地址index对应的ES的index名字,支持alias,如果使用doc_value,需要使用真实的名称typeindex的type,ES 7.x及以后的版本不传此参数userES集群用户名password对应用户的密码信息ES 7.x之前的集群请注意在建表的时候选择正确的索引类型type认证方式目前仅支持Http Basic认证,并且需要确保该用户有访问: /_cluster/state/、_nodes/http等路径和index的读权限; 集群未开启安全认证,用户名和密码不需要设置Doris表中的列名需要和ES中的字段名完全匹配,字段类型应该保持一致ENGINE必须是 ElasticsearchDoris On ES 一个重要的功能就是过滤条件的下推: 过滤条件下推给 ES,这样只有真正满足条件的数据才会被返回,能够显著的提高查询性能和降低 Doris 和 Elasticsearch 的 CPU、memory、IO 使用量。下面的操作符(Operators)会被优化成如下 ES Query:SQL syntaxES 5.x+ syntax=term queryinterms query> , < , >= , ⇐range queryandbool.filterorbool.shouldnotbool.must_notnot inbool.must_not + terms queryis_not_nullexists queryis_nullbool.must_not + exists queryesqueryES原生json形式的QueryDSL数据类型映射:Doris\ESbyteshortintegerlongfloatdoublekeywordtextdatetinyint√smallint√√int√√√bigint√√√√float√double√char√√varchar√√date√datetime√参数配置(1)启用列式扫描优化查询速度"enable_docvalue_scan" = "true"参数说明是否开启通过 ES/Lucene 列式存储获取查询字段的值,默认为 false。开启后 Doris 从 ES中获取数据会遵循以下两个原则:①尽力而为: 自动探测要读取的字段是否开启列式存储(doc_value: true),如果获取的字段全部有列存,Doris 会从列式存储中获取所有字段的值②自动降级: 如果要获取的字段只要有一个字段没有列存,所有字段的值都会从行存_source 中解析获取优势:默认情况下,Doris On ES 会从行存也就是_source 中获取所需的所有列,_source 的存储采用的行式+json 的形式存储,在批量读取性能上要劣于列式存储,尤其在只需要少数列的情况下尤为明显,只获取少数列的情况下,docvalue 的性能大约是_source 性能的十几倍。注意text 类型的字段在 ES 中是没有列式存储,因此如果要获取的字段值有 text 类型字段会自动降级为从_source 中获取;在获取的字段数量过多的情况下(>= 25),从 docvalue中获取字段值的性能会和从_source中获取字段值基本一样。(2)探测 keyword 类型字段"enable_keyword_sniff" = "true"参数说明:是否对 ES 中字符串类型分词类型(text) fields 进行探测,获取额外的未分词(keyword)字段名(multi-fields 机制)在 ES 中可以不建立 index 直接进行数据导入,这时候 ES 会自动创建一个新的索引,针对字符串类型的字段 ES 会创建一个既有 text 类型的字段又有 keyword 类型的字段,这就是 ES 的 multi fields 特性,mapping 如下:"k4": { "type": "text", "fields": { "keyword": { "type": "keyword", "ignore_above": 256 } } } 对 k4 进行条件过滤时比如=,Doris On ES 会将查询转换为 ES 的 TermQuery。SQL 过滤条件: k4 = "Doris On ES" 转换成 ES 的 query DSL 为: "term" : { "k4": "Doris On ES" }因为 k4 的第一字段类型为 text,在数据导入的时候就会根据 k4 设置的分词器(如果没有设置,就是 standard 分词器)进行分词处理得到 doris、on、es 三个 Term,如下 ES analyze API 分析POST /_analyze { "analyzer": "standard", "text": "Doris On ES" } 分词的结果是:{ "tokens": [ { "token": "doris", "start_offset": 0, "end_offset": 5, "type": "<ALPHANUM>", "position": 0 }, { "token": "on", "start_offset": 6, "end_offset": 8, "type": "<ALPHANUM>", "position": 1 }, { "token": "es", "start_offset": 9, "end_offset": 11, "type": "<ALPHANUM>", "position": 2 } ] } 查询时使用的是:"term" : { "k4": "Doris On ES" }Doris On ES 这个 term 匹配不到词典中的任何 term,不会返回任何结果,而启用enable_keyword_sniff: true 会自动将 k4 = "Doris On ES"转换成 k4.keyword = "Doris On ES"来完全匹配 SQL 语义,转换后的 ES query DSL 为:"term" : { "k4.keyword": "Doris On ES" }k4.keyword 的类型是 keyword,数据写入 ES 中是一个完整的 term,所以可以匹配。(3)开启节点自动发现"nodes_discovery" = "true"参数说明:是否开启 es 节点发现,默认为 true。当配置为 true 时,Doris 将从 ES 找到所有可用的相关数据节点(在上面分配的分片)。如果 ES 数据节点的地址没有被 Doris BE 访问,则设置为 false。ES 集群部署在与公共 Internet隔离的内网,用户通过代理访问。(4)配置 https 访问模式"http_ssl_enabled" = "true"参数说明:ES 集群是否开启 https 访问模式。目前 fe/be 实现方式为信任所有,这是临时解决方案,后续会使用真实的用户配置证书。查询用法完成在 Doris 中建立 ES 外表后,除了无法使用 Doris 中的数据模型(rollup、预聚合、物化视图等)外并无区别。(1)基本查询select * from es_table where k1 > 1000 and k3 ='term' or k4 like 'fu*z_' (2)扩展的 esquery(field, QueryDSL)通过 esquery(field, QueryDSL)函数将一些无法用 sql 表述的 query 如 match_phrase、geoshape 等下推给 ES 进行过滤处理,esquery 的第一个列名参数用于关联 index,第二个参数是 ES 的基本 Query DSL 的 json 表述,使用花括号{}包含,json 的 root key 有且只能有一个,如 match_phrase、geo_shape、bool 等。match_phrase 查询:select * from es_table where esquery(k4, '{"match_phrase": {"k4": "doris on es"}}'); geo 相关查询:select * from es_table where esquery(k4, '{ "geo_shape": { "location": { "shape": { "type": "envelope", "coordinates": [ [ 13, 53 ], [ 14, 52 ] ] }, "relation": "within" } } }'); bool 查询:select * from es_table where esquery(k4, ' { "bool": { "must": [ { "terms": { "k1": [ 11, 12 ] } }, { "terms": { "k2": [ 100 ] } } ] } }'); 使用建议(1)时间类型字段使用建议在 ES 中,时间类型的字段使用十分灵活,但是在 Doris On ES 中如果对时间类型字段的类型设置不当,则会造成过滤条件无法下推。创建索引时对时间类型格式的设置做最大程度的格式兼容:"dt": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis" }在 Doris 中建立该字段时建议设置为 date 或 datetime,也可以设置为 varchar 类型, 使用如下 SQL 语句都可以直接将过滤条件下推至 ES:select * from doe where k2 > '2020-06-21'; select * from doe where k2 < '2020-06-21 12:00:00'; select * from doe where k2 < 1593497011; select * from doe where k2 < now(); select * from doe where k2 < date_format(now(), '%Y-%m-%d'); 注意:在 ES 中如果不对时间类型的字段设置 format, 默认的时间类型字段格式为strict_date_optional_time||epoch_millis导入到 ES 的日期字段如果是时间戳需要转换成 ms, ES 内部处理时间戳都是按照ms 进行处理的, 否则 Doris On ES 会出现显示错误。(2)获取 ES 元数据字段_id导入文档在不指定_id 的情况下 ES 会给每个文档分配一个全局唯一的_id 即主键, 用户也可以在导入时为文档指定一个含有特殊业务意义的_id; 如果需要在 Doris On ES 中获取该字段值,建表时可以增加类型为 varchar 的_id 字段:CREATE EXTERNAL TABLE `doe` ( `_id` varchar COMMENT "", `city` varchar COMMENT "" ) ENGINE=ELASTICSEARCH PROPERTIES ( "hosts" = "http://127.0.0.1:8200", "user" = "root", "password" = "root", "index" = "doe", "type" = "doc" )注意:_id 字段的过滤条件仅支持=和 in 两种_id 字段只能是 varchar 类型JDBC外表JDBC External Table Of Doris 提供了Doris通过数据库访问的标准接口(JDBC)来访问外部表,外部表省去了繁琐的数据导入工作,让Doris可以具有了访问各式数据库的能力,并借助Doris本身的OLAP的能力来解决外部表的数据分析问题:支持各种数据源接入Doris支持Doris与各种数据源中的表联合查询,进行更加复杂的分析操作通过JDBC_Resource来创建JDBC外表:CREATE EXTERNAL RESOURCE jdbc_resource properties ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url"="jdbc:mysql://192.168.0.1:3306/test?useCursorFetch=true", "driver_url"="http://IP:port/mysql-connector-java-5.1.47.jar", "driver_class"="com.mysql.jdbc.Driver" ); CREATE EXTERNAL TABLE `baseall_mysql` ( `k1` tinyint(4) NULL, `k2` smallint(6) NULL, `k3` int(11) NULL, `k4` bigint(20) NULL, `k5` decimal(9, 3) NULL ) ENGINE=JDBC PROPERTIES ( "resource" = "jdbc_resource", "table" = "baseall", "table_type"="mysql" );参数说明:参数说明type“jdbc”, 必填项标志资源类型user访问外表数据库所使的用户名password该用户对应的密码信息jdbc_urlJDBC的URL协议,包括数据库类型,IP地址,端口号和数据库名,不同数据库协议格式不一样。例如mysql: “jdbc:mysql://127.0.0.1:3306/test?useCursorFetch=true”。driver_class访问外表数据库的驱动包类名,例如mysql是:com.mysql.jdbc.Driver.driver_url用于下载访问外部数据库的jar包驱动URL。http://IP:port/mysql-connector-java-5.1.47.jar。本地单机测试时,可将jar包放在本地路径下,“driver_url”=“file:///home/disk1/pathTo/mysql-connector-java-5.1.47.jar”,多机时需保证具有完全相同的路径信息。resource在Doris中建立外表时依赖的资源名,对应上步创建资源时的名字。table在Doris中建立外表时,与外部数据库相映射的表名。table_type在Doris中建立外表时,该表来自那个数据库。例如mysql,postgresql,sqlserver,oracle如果你是本地路径方式,这里数据库驱动依赖的jar包,FE、BE节点都要放置。Hive外表Hive External Table of Doris 提供了 Doris 直接访问 Hive 外部表的能力,外部表省去了繁琐的数据导入工作,并借助 Doris 本身的 OLAP 的能力来解决 Hive 表的数据分析问题:支持 Hive 数据源接入Doris支持 Doris 与 Hive 数据源中的表联合查询,进行更加复杂的分析操作支持 访问开启 kerberos 的 Hive 数据源支持 访问数据存储在腾讯 CHDFS 上的 Hive 数据源创建:-- 语法 CREATE [EXTERNAL] TABLE table_name ( col_name col_type [NULL | NOT NULL] [COMMENT "comment"] ) ENGINE=HIVE [COMMENT "comment"] PROPERTIES ( 'property_name'='property_value', ... ); -- 例子1:创建 Hive 集群中 hive_db 下的 hive_table 表 CREATE TABLE `t_hive` ( `k1` int NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=HIVE COMMENT "HIVE" PROPERTIES ( 'hive.metastore.uris' = 'thrift://192.168.0.1:9083', 'database' = 'hive_db', 'table' = 'hive_table' ); -- 例子2:创建 Hive 集群中 hive_db 下的 hive_table 表,HDFS使用HA配置 CREATE TABLE `t_hive` ( `k1` int NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=HIVE COMMENT "HIVE" PROPERTIES ( 'hive.metastore.uris' = 'thrift://192.168.0.1:9083', 'database' = 'hive_db', 'table' = 'hive_table', 'dfs.nameservices'='hacluster', 'dfs.ha.namenodes.hacluster'='n1,n2', 'dfs.namenode.rpc-address.hacluster.n1'='192.168.0.1:8020', 'dfs.namenode.rpc-address.hacluster.n2'='192.168.0.2:8020', 'dfs.client.failover.proxy.provider.hacluster'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' ); -- 例子3:创建 Hive 集群中 hive_db 下的 hive_table 表, HDFS使用HA配置并开启kerberos认证方式 CREATE TABLE `t_hive` ( `k1` int NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=HIVE COMMENT "HIVE" PROPERTIES ( 'hive.metastore.uris' = 'thrift://192.168.0.1:9083', 'database' = 'hive_db', 'table' = 'hive_table', 'dfs.nameservices'='hacluster', 'dfs.ha.namenodes.hacluster'='n1,n2', 'dfs.namenode.rpc-address.hacluster.n1'='192.168.0.1:8020', 'dfs.namenode.rpc-address.hacluster.n2'='192.168.0.2:8020', 'dfs.client.failover.proxy.provider.hacluster'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider', 'dfs.namenode.kerberos.principal'='hadoop/_HOST@REALM.COM' 'hadoop.security.authentication'='kerberos', 'hadoop.kerberos.principal'='doris_test@REALM.COM', 'hadoop.kerberos.keytab'='/path/to/doris_test.keytab' ); -- 例子4:创建 Hive 集群中 hive_db 下的 hive_table 表, Hive数据存储在S3上 CREATE TABLE `t_hive` ( `k1` int NOT NULL COMMENT "", `k2` char(10) NOT NULL COMMENT "", `k3` datetime NOT NULL COMMENT "", `k5` varchar(20) NOT NULL COMMENT "", `k6` double NOT NULL COMMENT "" ) ENGINE=HIVE COMMENT "HIVE" PROPERTIES ( 'hive.metastore.uris' = 'thrift://192.168.0.1:9083', 'database' = 'hive_db', 'table' = 'hive_table', 'AWS_ACCESS_KEY' = 'your_access_key', 'AWS_SECRET_KEY' = 'your_secret_key', 'AWS_ENDPOINT' = 's3.us-east-1.amazonaws.com', 'AWS_REGION' = 'us-east-1' ); 参数说明:外表列列名要于 Hive 表一一对应列的顺序需要与 Hive 表一致必须包含 Hive 表中的全部列Hive 表分区列无需指定,与普通列一样定义即可。ENGINE 需要指定为 HIVEPROPERTIES 属性:hive.metastore.uris:Hive Metastore 服务地址database:挂载 Hive 对应的数据库名table:挂载 Hive 对应的表名hadoop.username: 访问hdfs用户名,当认证为simple时需要dfs.nameservices:name service名称,与hdfs-site.xml保持一致`dfs.ha.namenodes.[nameservice ID]:namenode的id列表,与hdfs-site.xml保持一致dfs.namenode.rpc-address.[nameservice ID].[name node ID]:Name node的rpc地址,数量与namenode数量相同,与hdfs-site.xml保持一致dfs.client.failover.proxy.provider.[nameservice ID] :HDFS客户端连接活跃namenode的java类,通常是"org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider"访问开启kerberos的Hive数据源,需要为Hive外表额外配置如下 PROPERTIES 属性:hadoop.security.authentication:认证方式请设置为 kerberos,默认为simpledfs.namenode.kerberos.principal:HDFS namenode 服务的Kerberos 主体hadoop.kerberos.principal:设置 Doris 连接 HDFS 时使用的 Kerberos 主体hadoop.kerberos.keytab:设置 keytab 本地文件路径AWS_ACCESS_KEY: AWS账户的access key id.AWS_SECRET_KEY: AWS账户的secret access key.AWS_ENDPOINT: S3 endpoint. 例如:s3.us-east-1.amazonaws.comAWS_REGION: AWS区域. 例如:us-east-1注意:若要使 Doris 访问开启kerberos认证方式的hadoop集群,需要在 Doris 集群所有运行节点上部署 Kerberos 客户端 kinit,并配置 krb5.conf,填写KDC 服务信息等。PROPERTIES 属性 hadoop.kerberos.keytab 的值需要指定 keytab 本地文件的绝对路径,并允许 Doris 进程访问该本地文件。关于HDFS集群的配置可以写入hdfs-site.xml文件中,该配置文件在fe和be的conf目录下,用户创建Hive表时,不需要再填写HDFS集群配置的相关信息。支持的 Hive 列类型与 Doris 对应关系如下表:HiveDoris描述BOOLEANBOOLEANCHARCHAR当前仅支持UTF8编码VARCHARVARCHAR当前仅支持UTF8编码TINYINTTINYINTSMALLINTSMALLINTINTINTBIGINTBIGINTFLOATFLOATDOUBLEDOUBLEDECIMALDECIMALDATEDATETIMESTAMPDATETIMETimestamp 转成 Datetime 会损失精度多源数据目录(※)基本概念多源数据目录(Multi-Catalog)是 Doris 1.2.0 版本中推出的功能,旨在能够更方便对接外部数据目录,以增强Doris的数据湖分析和联邦数据查询能力。上诉JDBC、ODBC、ES、Hive外表的方式不建议使用了。在之前的 Doris 版本中,用户数据只有两个层级:Database 和 Table。当我们需要连接一个外部数据目录时,我们只能在Database 或 Table 层级进行对接。比如通过 create external table 的方式创建一个外部数据目录中的表的映射,或通过 create external database 的方式映射一个外部数据目录中的 Database。 如果外部数据目录中的 Database 或 Table 非常多,则需要用户手动进行一一映射,使用体验不佳。而新的 Multi-Catalog 功能在原有的元数据层级上,新增一层Catalog,构成 Catalog -> Database -> Table 的三层元数据层级。其中,Catalog 可以直接对应到外部数据目录。目前支持的外部数据目录包括:HiveIcebergHudiElasticsearchJDBC: 对接数据库访问的标准接口(JDBC)来访问各式数据库的数据。该功能将作为之前外表连接方式(External Table)的补充和增强,帮助用户进行快速的多数据目录联邦查询。有以下概念:1.Internal CatalogDoris 原有的 Database 和 Table 都将归属于 Internal Catalog。Internal Catalog 是内置的默认 Catalog,用户不可修改或删除。2.External Catalog可以通过 CREATE CATALOG 命令创建一个 External Catalog。创建后,可以通过 SHOW CATALOGS 命令查看已创建的 Catalog。3.切换 Catalog用户登录 Doris 后,默认进入 Internal Catalog,因此默认的使用和之前版本并无差别,可以直接使用 SHOW DATABASES,USE DB 等命令查看和切换数据库。用户可以通过SWITCH命令切换 Catalog。如:SWITCH internal; SWITCH hive_catalog; 切换后,可以直接通过 SHOW DATABASES,USE DB 等命令查看和切换对应 Catalog 中的 Database。Doris 会自动通过 Catalog 中的 Database 和 Table。用户可以像使用 Internal Catalog 一样,对 External Catalog 中的数据进行查看和访问。当前,Doris 只支持对 External Catalog 中的数据进行只读访问。4.删除 CatalogExternal Catalog 中的 Database 和 Table 都是只读的。但是可以删除 Catalog(Internal Catalog无法删除)。可以通过 DROP CATALOG命令删除一个 External Catalog。该操作仅会删除 Doris 中该 Catalog 的映射信息,并不会修改或变更任何外部数据目录的内容。5.ResourceResource 是一组配置的集合。用户可以通过 CREATE RESOURCE 命令创建一个 Resource。之后可以在创建 Catalog 时使用这个 Resource。一个 Resource 可以被多个 Catalog 使用,以复用其中的配置。Hive通过连接 Hive Metastore,或者兼容 Hive Metatore 的元数据服务,Doris 可以自动获取 Hive 的库表信息,并进行数据查询。除了 Hive 外,很多其他系统也会使用 Hive Metastore 存储元数据。所以通过 Hive Catalog,我们不仅能访问 Hive,也能访问使用 Hive Metastore 作为元数据存储的系统。如 Iceberg、Hudi 等。使用限制:hive 支持 1/2/3 版本。支持 Managed Table 和 External Table。可以识别 Hive Metastore 中存储的 hive、iceberg、hudi 元数据。支持数据存储在 Juicefs 上的 hive 表,用法如下(需要把juicefs-hadoop-x.x.x.jar放在 fe/lib/ 和 apache_hdfs_broker/lib/ 下)。(1)创建CatalogCREATE CATALOG hive PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hive.metastore.sasl.enabled' = 'true', 'hive.metastore.kerberos.principal' = 'your-hms-principal', 'dfs.nameservices'='your-nameservice', 'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007', 'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007', 'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider', 'hadoop.security.authentication' = 'kerberos', 'hadoop.kerberos.keytab' = '/your-keytab-filepath/your.keytab', 'hadoop.kerberos.principal' = 'your-principal@YOUR.COM', 'yarn.resourcemanager.principal' = 'your-rm-principal' );除了 type 和 hive.metastore.uris 两个必须参数外,还可以通过更多参数来传递连接所需要的信息。在所有的 BE、FE 节点下放置 krb5.conf 文件和 keytab 认证文件,keytab 认证文件路径和配置保持一致,krb5.conf 文件默认放置在 /etc/krb5.conf 路径。 hive.metastore.kerberos.principal 的值需要和所连接的 hive metastore 的同名属性保持一致,可从 hive-site.xml 中获取。提供 Hadoop KMS 加密传输信息,示例如下:CREATE CATALOG hive PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'dfs.encryption.key.provider.uri' = 'kms://http@kms_host:kms_port/kms' );其它存储:# hive数据存储在JuiceFS,示例如下: CREATE CATALOG hive PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hadoop.username' = 'root', 'fs.jfs.impl' = 'io.juicefs.JuiceFileSystem', 'fs.AbstractFileSystem.jfs.impl' = 'io.juicefs.JuiceFS', 'juicefs.meta' = 'xxx' ); # hive元数据存储在Glue,数据存储在S3,示例如下: CREATE CATALOG hive PROPERTIES ( "type"="hms", "hive.metastore.type" = "glue", "aws.region" = "us-east-1", "aws.glue.access-key" = "ak", "aws.glue.secret-key" = "sk", "AWS_ENDPOINT" = "s3.us-east-1.amazonaws.com", "AWS_REGION" = "us-east-1", "AWS_ACCESS_KEY" = "ak", "AWS_SECRET_KEY" = "sk", "use_path_style" = "true" );在 1.2.1 版本之后,我们也可以将这些信息通过创建一个 Resource 统一存储,然后在创建 Catalog 时使用这个 Resource。示例如下:# 1. 创建 Resource CREATE RESOURCE hms_resource PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hadoop.username' = 'hive', 'dfs.nameservices'='your-nameservice', 'dfs.ha.namenodes.your-nameservice'='nn1,nn2', 'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007', 'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007', 'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' ); # 2. 创建 Catalog 并使用 Resource,这里的 Key Value 信息会覆盖 Resource 中的信息。 CREATE CATALOG hive WITH RESOURCE hms_resource PROPERTIES( 'key' = 'value' );我们也可以直接将 hive-site.xml 放到 FE 和 BE 的 conf 目录下,系统也会自动读取 hive-site.xml 中的信息。信息覆盖的规则如下:Resource 中的信息覆盖 hive-site.xml 中的信息。CREATE CATALOG PROPERTIES 中的信息覆盖 Resource 中的信息。连接开启 Ranger 权限校验的 Hive Metastore 需要增加配置 & 配置环境:创建 Catalog 时增加:"access_controller.properties.ranger.service.name" = "hive", "access_controller.class" = "org.apache.doris.catalog.authorizer.RangerHiveAccessControllerFactory", 配置所有 FE 环境:①将 HMS conf 目录下的配置文件ranger-hive-audit.xml,ranger-hive-security.xml,ranger-policymgr-ssl.xml复制到 <doris_home>/conf 目录下。②修改 ranger-hive-security.xml 的属性,参考配置如下:<?xml version="1.0" encoding="UTF-8"?> <?xml-stylesheet type="text/xsl" href="configuration.xsl"?> <configuration> #The directory for caching permission data, needs to be writable <property> <name>ranger.plugin.hive.policy.cache.dir</name> <value>/mnt/datadisk0/zhangdong/rangerdata</value> </property> #The time interval for periodically pulling permission data <property> <name>ranger.plugin.hive.policy.pollIntervalMs</name> <value>30000</value> </property> <property> <name>ranger.plugin.hive.policy.rest.client.connection.timeoutMs</name> <value>60000</value> </property> <property> <name>ranger.plugin.hive.policy.rest.client.read.timeoutMs</name> <value>60000</value> </property> <property> <name>ranger.plugin.hive.policy.rest.ssl.config.file</name> <value></value> </property> <property> <name>ranger.plugin.hive.policy.rest.url</name> <value>http://172.21.0.32:6080</value> </property> <property> <name>ranger.plugin.hive.policy.source.impl</name> <value>org.apache.ranger.admin.client.RangerAdminRESTClient</value> </property> <property> <name>ranger.plugin.hive.service.name</name> <value>hive</value> </property> <property> <name>xasecure.hive.update.xapolicies.on.grant.revoke</name> <value>true</value> </property> </configuration> ③为获取到 Ranger 鉴权本身的日志,可在 <doris_home>/conf 目录下添加配置文件 log4j.properties。④重启 FE。(2)查看 Catalogmysql> SHOW CATALOGS; +-----------+-------------+----------+ | CatalogId | CatalogName | Type | +-----------+-------------+----------+ | 10024 | hive | hms | | 0 | internal | internal | +-----------+-------------+----------+(3)切换 Catalog通过 SWITCH 命令切换到 hive catalog,并查看其中的数据库:mysql> SWITCH hive; Query OK, 0 rows affected (0.00 sec) mysql> SHOW DATABASES; +-----------+ | Database | +-----------+ | default | | random | | ssb100 | | tpch1 | | tpch100 | | tpch1_orc | +-----------+ (4)使用 Catalog切换到 Catalog 后,则可以正常使用内部数据源的功能。如切换到 tpch100 数据库,并查看其中的表:mysql> USE tpch100; Database changed mysql> SHOW TABLES; +-------------------+ | Tables_in_tpch100 | +-------------------+ | customer | | lineitem | | nation | | orders | | part | | partsupp | | region | | supplier | +-------------------+(5)查询SELECT l_shipdate, l_orderkey, l_partkey FROM lineitem limit 10; # 也可以和其他数据目录中的表进行关联查询: SELECT l.l_shipdate FROM hive.tpch100.lineitem l WHERE l.l_partkey IN (SELECT p_partkey FROM internal.db1.part) LIMIT 10;这里我们通过 catalog.database.table 这种全限定的方式标识一张表,如:internal.db1.part。其中 catalog 和 database 可以省略,缺省使用当前 SWITCH 和 USE 后切换的 catalog 和 database。可以通过 INSERT INTO 命令,将 hive catalog 中的表数据,插入到 interal catalog 中的内部表,从而达到导入外部数据目录数据的效果:mysql> SWITCH internal; Query OK, 0 rows affected (0.00 sec) mysql> USE db1; Database changed mysql> INSERT INTO part SELECT * FROM hive.tpch100.part limit 1000; Query OK, 1000 rows affected (0.28 sec) {'label':'insert_212f67420c6444d5_9bfc184bf2e7edb8', 'status':'VISIBLE', 'txnId':'4'}lceberg使用限制:支持 Iceberg V1/V2 表格式。V2 格式仅支持 Position Delete 方式,不支持 Equality Delete。(1)基于Hive Metastore创建Catalog和 Hive Catalog 基本一致,这里仅给出简单示例:CREATE CATALOG iceberg PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hadoop.username' = 'hive', 'dfs.nameservices'='your-nameservice', 'dfs.ha.namenodes.your-nameservice'='nn1,nn2', 'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007', 'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007', 'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' ); (2)基于Iceberg API创建CatalogHive Metastore作为元数据服务CREATE CATALOG iceberg PROPERTIES ( 'type'='iceberg', 'iceberg.catalog.type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hadoop.username' = 'hive', 'dfs.nameservices'='your-nameservice', 'dfs.ha.namenodes.your-nameservice'='nn1,nn2', 'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007', 'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007', 'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' ); Glue Catalog作为元数据服务CREATE CATALOG glue PROPERTIES ( "type"="iceberg", "iceberg.catalog.type" = "glue", "glue.endpoint" = "https://glue.us-east-1.amazonaws.com", "warehouse" = "s3://bucket/warehouse", "AWS_ENDPOINT" = "s3.us-east-1.amazonaws.com", "AWS_REGION" = "us-east-1", "AWS_ACCESS_KEY" = "ak", "AWS_SECRET_KEY" = "sk", "use_path_style" = "true" ); glue.endpoint: Glue Endpoint.warehouse: Glue Warehouse Location. Glue Catalog的根路径,用于指定数据存放位置。REST Catalog作为元数据服务该方式需要预先提供REST服务,用户需实现获取Iceberg元数据的REST接口。CREATE CATALOG iceberg PROPERTIES ( 'type'='iceberg', 'iceberg.catalog.type'='rest', 'uri' = 'http://172.21.0.1:8181', ); 若数据存放在S3上,properties中可以使用以下参数:"AWS_ACCESS_KEY" = "ak" "AWS_SECRET_KEY" = "sk" "AWS_REGION" = "region-name" "AWS_ENDPOINT" = "http://endpoint-uri" "AWS_CREDENTIALS_PROVIDER" = "provider-class-name" // 可选,默认凭证类基于BasicAWSCredentials实现。 Hudi使用限制:Hudi 目前仅支持 Copy On Write 表的 Snapshot Query,以及 Merge On Read 表的 Read Optimized Query。后续将支持 Incremental Query 和 Merge On Read 表的 Snapshot Query。目前仅支持 Hive Metastore 类型的 Catalog。所以使用方式和 Hive Catalog 基本一致。后续版本将支持其他类型的 Catalog。和 Hive Catalog 基本一致,这里仅给出简单示例:CREATE CATALOG hudi PROPERTIES ( 'type'='hms', 'hive.metastore.uris' = 'thrift://172.21.0.1:7004', 'hadoop.username' = 'hive', 'dfs.nameservices'='your-nameservice', 'dfs.ha.namenodes.your-nameservice'='nn1,nn2', 'dfs.namenode.rpc-address.your-nameservice.nn1'='172.21.0.2:4007', 'dfs.namenode.rpc-address.your-nameservice.nn2'='172.21.0.3:4007', 'dfs.client.failover.proxy.provider.your-nameservice'='org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider' );ESElasticsearch Catalog 除了支持自动映射 ES 元数据外,也可以利用 Doris 的分布式查询规划能力和 ES(Elasticsearch) 的全文检索能力相结合,提供更完善的 OLAP 分析场景解决方案。CREATE CATALOG es PROPERTIES ( "type"="es", "hosts"="http://127.0.0.1:9200" );因为 Elasticsearch 没有 Database 的概念,所以连接 ES 后,会自动生成一个唯一的 Database:default_db。并且在通过 SWITCH 命令切换到 ES Catalog 后,会自动切换到 default_db。无需再执行 USE default_db 命令。参数:参数是否必须默认值说明hosts是ES 地址,可以是一个或多个,也可以是 ES 的负载均衡地址user否空ES 用户名password否空对应用户的密码信息doc_value_scan否true是否开启通过 ES/Lucene 列式存储获取查询字段的值keyword_sniff否true是否对 ES 中字符串分词类型 text.fields 进行探测,通过 keyword 进行查询。设置为 false 会按照分词后的内容匹配nodes_discovery否true是否开启 ES 节点发现,默认为 true,在网络隔离环境下设置为 false,只连接指定节点ssl否falseES 是否开启 https 访问模式,目前在 fe/be 实现方式为信任所有mapping_es_id否false是否映射 ES 索引中的 _id 字段like_push_down否true是否将 like 转化为 wildchard 下推到 ES,会增加 ES cpu 消耗认证方式目前仅支持 Http Basic 认证,并且需要确保该用户有访问: /_cluster/state/、_nodes/http 等路径和 index 的读权限; 集群未开启安全认证,用户名和密码不需要设置。5.x 和 6.x 中一个 index 中的多个 type 默认取第一个。JDBCJDBC Catalog 通过标准 JDBC 协议,连接其他数据源。连接后,Doris 会自动同步数据源下的 Database 和 Table 的元数据,以便快速访问这些外部数据。使用限制:仅支持 MySQL、PostgreSQL、Oracle、SQLServer、Clickhouse、Doris(1)MySQLCREATE CATALOG jdbc_mysql PROPERTIES ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url" = "jdbc:mysql://127.0.0.1:3306/demo", "driver_url" = "mysql-connector-java-5.1.47.jar", "driver_class" = "com.mysql.jdbc.Driver" )(2)PostgreSQLCREATE CATALOG jdbc_postgresql PROPERTIES ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url" = "jdbc:postgresql://127.0.0.1:5449/demo", "driver_url" = "postgresql-42.5.1.jar", "driver_class" = "org.postgresql.Driver" );映射关系如下:DorisPostgreSQLCatalogDatabaseDatabaseSchemaTableTable(3)OracleCREATE CATALOG jdbc_oracle PROPERTIES ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url" = "jdbc:oracle:thin:@127.0.0.1:1521:helowin", "driver_url" = "ojdbc6.jar", "driver_class" = "oracle.jdbc.driver.OracleDriver" );映射关系如下:DorisOracleCatalogDatabaseDatabaseUserTableTable(4)ClickhouseCREATE CATALOG jdbc_clickhouse PROPERTIES ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url" = "jdbc:clickhouse://127.0.0.1:8123/demo", "driver_url" = "clickhouse-jdbc-0.3.2-patch11-all.jar", "driver_class" = "com.clickhouse.jdbc.ClickHouseDriver" ); (5)SQLServerCREATE CATALOG sqlserver_catalog PROPERTIES ( "type"="jdbc", "user"="SA", "password"="Doris123456", "jdbc_url" = "jdbc:sqlserver://localhost:1433;DataBaseName=doris_test", "driver_url" = "mssql-jdbc-11.2.3.jre8.jar", "driver_class" = "com.microsoft.sqlserver.jdbc.SQLServerDriver" ); 映射关系如下:DorisSQLServerCatalogDatabaseDatabaseSchemaTableTable(6)DorisJdbc Catalog也支持连接另一个Doris数据库:CREATE CATALOG doris_catalog PROPERTIES ( "type"="jdbc", "user"="root", "password"="123456", "jdbc_url" = "jdbc:mysql://127.0.0.1:9030?useSSL=false", "driver_url" = "mysql-connector-java-5.1.47.jar", "driver_class" = "com.mysql.jdbc.Driver" );
0
0
0
浏览量521
攻城狮无远

Doris-07-索引的详细介绍

索引介绍Apache Doris 存储引擎采用类似 LSM 树的结构提供快速的数据写入支持。进行数据导入时,数据会先写入 Tablet 对应的 MemTable 中,当 MemTable 写满之后,会将 MemTable 里的数据刷写(Flush)到磁盘,生成一个个不超过 256MB 的不可变的 Segment 文件。MemTable 采用 SkipList 的数据结构,将数据暂时保存在内存中,SkipList 会按照 Key 对数据行进行排序,因此,刷写到磁盘上的 Segment 文件也是按 Key 排序的。Apache Doris 底层采用列存的方式来存储数据,每一列数据会被分为多个 Data Page。为了提高数据读取效率,Apache Doris 底层存储引擎提供了丰富的索引类型,目前 Doris 主要支持两类索引:内建的智能索引,包括前缀索引和 ZoneMap 索引。用户手动创建的二级索引,包括 倒排索引、 bloomfilter索引、 ngram bloomfilter索引和bitmap索引。数据从 MemTable 刷写到磁盘的过程分为两个阶段:第一阶段是将 MemTable 中的行存结构在内存中转换为列存结构,并为每一列生成对应的索引结构;第二阶段是将转换后的列存结构写入磁盘,生成 Segment 文件。前缀索引索引生成不同于传统的数据库设计,Doris 不支持在任意列上创建索引。Doris 这类 MPP 架构的 OLAP 数据库,通常都是通过提高并发,来处理大量数据的。本质上,Doris 的数据存储在类似 SSTable(Sorted String Table)的数据结构中。该结构是一种有序的数据结构,可以按照指定的列进行排序存储。在这种数据结构上,以排序列作为条件进行查找,会非常的高效。在 Aggregate、Uniq 和 Duplicate 三种数据模型中。底层的数据存储,是按照各自建表语句中,AGGREGATE KEY、UNIQ KEY 和 DUPLICATE KEY 中指定的列进行排序存储的。而前缀索引,即在排序的基础上,实现的一种根据给定前缀列,快速查询数据的索引方式。我们将一行数据的前 36 个字节 作为这行数据的前缀索引。当遇到 VARCHAR 类型时,前缀索引会直接截断。前缀索引是一种稀疏索引。数据刷写过程中,每写入一定的数据行(默认为 1024 行)就会生成一条前缀索引项。前缀索引会对每一个索引间隔的第一个数据行的前缀字段进行编码,前缀字段的编码与前缀字段的值具有相同的排序规则,即前缀字段的值排序越靠前,对应的编码值排序也越靠前。Segment 文件是按 Key 排序的,因此,前缀索引项也是按 Key 排序的。一个 Segment 文件中的前缀索引数据保存在一个独立的 Short Key Page 中,其中包含每一条前缀索引项的编码数据、每一条前缀索引项的 offset、Short Key Page 的 footer 以及 Short Key Page 的 Checksum 信息。Short Key Page 的 footer 中记录了 Page 的类型、前缀索引编码数据的大小、前缀索引 offset 数据的大小、前缀索引项的数目等信息。Short Key Page 在 Segment 中的 offset 和大小会被保存在Segment文件的footer中,以便于数据读取时能够正确地从Segment文件中加载出前缀索引数据。前缀索引的存储结构如图所示。查询过滤数据查询时,会打开Segment文件,从footer中获取Short Key Page的offset以及大小,然后从Segment文件中读取Short Key Page中的索引数据,并解析出每一条前缀索引项。如果查询过滤条件包含前缀字段时,就可以使用前缀索引进行快速地行过滤。查询过滤条件会被划分成多个Key Range。对一个Key Range进行行过滤的方法如下:在整个Segment的行范围内寻找Key Range上界对应的行号upper rowid(寻找Segment中第一个大于Key Range上界的行)。对Key Range上界的前缀字段key进行编码。寻找key可能存在的范围下界start。根据编码寻找前缀索引中第一个等于(存在前缀索引项与key的编码相同)或大于(不存在前缀索引项的与key的编码相同)key编码的前缀索引项。如果找到满足条件的索引项,并且该索引项不是第一条前缀索引项,则将该索引项的前一条前缀索引项对应的行号记录为start(前缀索引是稀疏索引,第一个等于或大于Key Range上界key的数据行有可能在前一条前缀索引项对应的数据行之后);如果找到满足条件的索引项,并且该索引项是第一条前缀索引项,则记录该索引项对应的行号为start。如果没有找到一条前缀索引项等于或大于key的编码,则记录最后一条前缀索引项对应的行号为start(第一个等于或大于key的行有可能在最后一条前缀索引项之后)。寻找key可能存在的范围上界end。根据编码寻找前缀索引中第一个大于key的二进制编码的索引项。如果找到满足条件的索引项,则记录该索引项对应的行号为end;如果没有找到一条前缀索引项大于key的编码,则记录Segment最后一行的行号为end。使用二分查找算法在start与end之间的行范围内寻找第一个大于key的编码的行,行号记为upper rowid。注:前缀索引是稀疏索引,不能精确定位到key所在的行,只能粗粒度地定位出key可能存在的范围,然后使用二分查找算法精确地定位key的位置,如图所示。在0 ~ upper rowid范围内寻找Key Range下界对应的行号lower rowid(寻找Segment中第一个等于或大于Key Range下界的行)。与寻找Key Range上界对应的row id的方法相同,不再赘述。获取Key Range的行范围。upper_rowid与lower_rowid之间的所有数据行都是当前Key Range需要扫描的行范围。Ordinal 索引索引生成Apache Doris 底层采用列存的方式来存储数据,每一列数据会被分为多个Data Page。数据刷写时,会为每一个Data Page生成一条Ordinal索引项,其中保存Data Page在Segment文件中的offset、Data Page的大小以及Data Page的起始行号,所有Data Page的Ordinal索引项会保存在一个Ordinal Index Page中, Ordinal Index Page在Segment文件中的offset以及Ordinal Index Page的大小会被保存在Segment文件的footer中,以便于数据读取时能够通过两级索引找到Data Page(首先,通过Segment文件的footer找到Ordinal Index Page,然后,通过Ordinal Index Page中的索引项找到Data Page)。Ordinal Index Page包含以下信息:所有Ordinal索引项数据;Ordinal Index Page的footer:包含当前Page的类型、Ordinal索引项数据的大小、Ordinal索引项数目等信息;Short Key Page的Checksum信息。如果列中只有一个Data Page时,即该列只有一条Ordinal索引项,则Segment文件中不需要保存该列的Ordinal索引数据,只需要将这唯一的Data Page在Segment文件中的offset以及该Data Page的大小保存在Segment文件的footer中。数据读取时可以通过Segment文件的footer直接找到这唯一的Data Page。Ordinal索引的存储结构如图所示。Ordinal索引的作用是为了方便其他类型的索引能够使用统一的方式查找Data Page,进而可以对其他类型的索引屏蔽Data Page在Segment文件中的offset。查询过滤数据查询时,会加载每一个列的Ordinal索引数据。通过Segment footer中记录的Ordinal索引的Meta信息判断当前列是否存在Ordinal Index Page,即判断当前列是否有多个Data Page。如果当前列存在Ordinal Index Page,则从Segment footer中获取Ordinal Index Page在Segment中的offset和Ordinal Index Page的大小,然后从Segment文件中读取Ordinal Index Page数据,并解析出每一条Ordinal索引项,即可通过Ordinal索引项获取当前列中每一个Data Page的起始行号、Data Page在Segment中的offset以及Data Page的大小。如果当前列不存在Ordinal Index Page,则可以直接从Segment footer中获取当前列中唯一的Data Page在Segment中的offset以及Data Page的大小。Zone Map 索引Apache Doris 会为Segment文件中的一列数据添加Zone Map索引,同时会为列中的每一个Data Page添加Zone Map索引。Zone Map索引项中记录了每一列或列中每一个Data Page的最大值(max value)、最小值(min value)、是否有null值(has null)以及是否有非null值(has not null)的信息。初始化时,max value会被设置为当前列类型的最小值,min value会被设置为当前列类型的最大值,has null和has not null会被设置为false。索引生成数据刷写时,会给每一个Data Page创建一条Zone Map索引项。向Data Page中每添加一条数据,都会更新Data Page的Zone Map索引项。如果添加的数据是null,则将Zone Map索引项的has null标志设置为true,否则,将Zone Map索引项的has not null标志设置为true。如果添加的数据小于Zone Map索引项的min value,则使用当前数据更新min value;如果添加的数据大于Zone Map索引项的max value,则使用当前数据更新max value。当一个Data Page写满之后,会更新一次列的Zone Map索引项,如果Data Page索引项的min value小于列索引项的min value,则使用Data Page索引项的min value更新列索引项的min value;如果Data Page索引项的max value大于列索引项的max value,则使用Data Page索引项的max value更新列索引项的max value;如果Data Page索引项的has null标志为true,则更新列索引项的has null标志为true;如果Data Page索引项的has not null标志为true,则更新列索引项的has not null标志为true。更新Zone Map索引的过程如图4所示。列中每一个Data Page的Zone Map索引项会被序列化之后保存在Zone Map Index Page中。Zone Map Index Page中包含以下信息:Zone Map索引项数据、Zone Map Index Page的footer以及Zone Map Index Page的Checksum信息。Zone Map Index Page的footer中包含当前Page的类型、当前Page中Zone Map索引项数据的大小、当前Page中Zone Map索引项数目以及当前Page中第一条索引项在整个列的Zone Map索引项中的序号等信息。一个Zone Map Index Page写满之后,会创建新的Zone Map Index Page用于记录该列后续的Zone Map索引项。如果某一列有多个Zone Map Index Page,则该列的Zone Map索引会采用两级索引机制。第二级索引为多个的Zone Map Index Page,其中保存Data Page的Zone Map索引数据,每一个Zone Map Index Page会生成一条Ordinal索引项,所有Zone Map Index Page的Ordinal索引项会被保存在一个Ordinal Index Page(注意,此处的Ordinal 索引与第3部分的Ordinal 索引不同,此处的Ordinal 索引指向Zone Map Index Page,而第3部分的Ordinal 索引指向Data Page)中作为一级索引。每一个的Ordinal索引项由key和value两部分组成,key记录了当前Zone Map Index Page中第一条索引项在整个列的Zone Map索引项中的序号,value记录了当前Zone Map Index Page在Segment文件中的offset和大小。Ordinal Index Page中包含以下信息:所有Zone Map Index Page的Ordinal 索引数据、Ordinal Index Page的footer以及Ordinal Index Page的Checksum信息。Ordinal Index Page的footer中包含当前Page的类型、当前Page中索引数据的大小、当前Page中索引项数目等。一级索引Ordinal Index Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。如果某一列只有一个Zone Map Index Page,则不需要两级索引,这个唯一的Zone Map Index Page在Block中的offset和大小会被记录在Segment文件的footer中。Zone Map索引的存储结构如图所示。查询过滤数据查询时,会加载每一个列的Zone Map索引数据,并解析出每一个Data Page的Zone Map索引数据。通过Segment footer中记录的Zone Map索引的Meta信息判断当前列的Zone Map是否含有两级索引。如果含有两级索引,则Segment footer中记录了一级索引Ordinal Index Page在Segment文件中的offset和大小,加载一级索引Ordinal Index Page,并解析出每一个的Ordinal索引项的key和value,key记录了每一个Zone Map Index Page中第一条索引项在整个列所有的Zone Map索引项中的序号,value记录了每一个Zone Map Index Page在Segment文件中的offset和大小。否则,当前列的Zone Map索引只含有一个Zone Map Index Page,Segment footer中记录了该Zone Map Index Page在Segment文件中的offset和大小。可以通过Zone Map Index Page解析出每一个Data Page的Zone Map索引数据,其中包括最大值(max value)、最小值(min value)、是否有null值(has null)以及是否有非null值(has not null)的信息。使用Zone Map对Data Page进行过滤的方法如下:过滤条件的运算符不是IS。如果Zone Map索引的has null为true(Data Page中含有NULL值),则Data Page不能被过滤掉。过滤条件为 field = value。如果 value在Zone Map索引的最大值与最小值之间,则Data Page不能被过滤掉。过滤条件为 field != value。如果value小于Zone Map索引的最小值或value大于Zone Map索引的最大值,则Data Page不能被过滤掉。过滤条件为 field < value。如果value大于Zone Map索引的最小值,则Data Page不能被过滤掉。过滤条件为 field <= value。如果value大于或等于Zone Map索引的最小值,则Data Page不能被过滤掉。过滤条件为 field > value。如果value小于Zone Map索引的最大值,则Data Page不能被过滤掉。过滤条件为 field >= value。如果value小于或等于Zone Map索引的最大值,则Data Page不能被过滤掉。过滤条件为 field IN {value1, value2, …}。如果value1、value2、…中至少存在一个值在Zone Map索引的最大值与最小值之间,则Data Page不能被过滤掉。过滤条件为 field IS NULL。如果Zone Map索引的has null为true(Data Page中含有NULL值),则Data Page不能被过滤掉。过滤条件为 field IS NOT NULL。如果Zone Map索引的has not null为true(Data Page中含有非NULL值),则Data Page不能被过滤掉。对于未被Zone Map索引过滤的Data Page,可以使用Ordinal索引快速定位这些Data Page的起始行的行号,并获取这些Data Page的行范围。通过Data Page对应的Ordinal索引项快速获取当前Data Page的起始行的行号start,通过下一条Ordinal索引项快速获取后一个Data Page的起始行的行号end,左闭右开区间[start, end)即为当前Data Page的row范围。Bitmap 索引为了加速数据查询,Apache Doris支持用户为某些字段添加Bitmap索引。Bitmap索引由两部分组成:有序字典:有序保存一列中所有的不同取值。字典值的Roaring位图:保存有序字典中每一个取值的Roaring位图,表示字典值在列中的行号。例如:如图所示,一列数据为[x, x, y, y, y, z, y, x, z, x],一共包含10行,则该列数据的Bitmap索引的有序字典为{x, y, z}, x、y、z对应的位图分别为:x的位图: [0, 1, 7, 9]y的位图: [2, 3, 4, 6]z的位图: [5, 8]创建语句:CREATE INDEX [IF NOT EXISTS] index_name ON table1 (siteid) USING BITMAP COMMENT 'balabala';索引生成数据刷写时,会给用户指定的列创建Bitmap索引。向列中每添加一个值,都会更新当前列的Bitmap索引。从Bitmap索引的有序字典中查找添加的值是否已经存在,如果本次添加的值在Bitmap索引的有序字典中已经存在,则直接更新该字典值对应的Roaring位图,如果本次添加的值在Bitmap索引的有序字典中不存在,则将该值添加到有序字典,并为该字典值创建Roaring位图。当然,NULL值也会有单独的Roaring位图。Bitmap索引的字典数据和Roaring位图数据分开存储(一一对应)。列中Bitmap索引的字典值会按顺序保存在Dict Page中。Dict Page中包含以下信息:Bitmap索引的字典数据、Dict Page的footer以及Dict Page的Checksum信息。Dict Page的footer中包含当前Page的类型、当前Page中Bitmap索引的字典数据的大小、当前Page中Bitmap索引的字典值数目以及当前Page中第一个字典值在整个列的Bitmap索引字典值中的序号等信息。Bitmap索引的字典数据会按照LZ4F格式进行压缩。一个Dict Page写满之后,会创建新的Dict Page用于记录该列后续的字典数据。如果某一列有多个Dict Page,则会采用两级索引机制:第二级索引为多个的Dict Page,其中保存Bitmap索引的字典数据,每一个Dict Page生成一条Value索引项,每一个的Value索引项记录了当前Dict Page中第一个字典值的编码以及当前Dict Page在Segment文件中的offset和大小,所有Dict Page的Value索引项会被保存在一个Value Index Page中作为一级索引。Value Index Page中包含以下信息:所有Dict Page的Value索引数据、Value Index Page的footer以及Value Index Page的Checksum信息。Value Index Page的footer中包含当前Page的类型、当前Page中索引数据的大小、当前Page中索引项数目等。一级索引Value Index Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。如果某一列只有一个Dict Page,则不需要两级索引,这个唯一的Dict Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。Bitmap索引的字典数据的存储结构如图所示。列中Bitmap索引的Roaring位图数据会保存在Bitmap Page中。Bitmap Page中包含以下信息:Bitmap索引的Roaring位图数据、Bitmap Page的footer以及Bitmap Page的Checksum信息。Bitmap Page的footer中包含当前Page的类型、当前Page中Bitmap索引的Roaring位图数据的大小、当前Page中Bitmap索引的Roaring位图数目以及当前Page中第一个Roaring位图在整个列的Bitmap索引的Roaring位图中的序号等信息。Bitmap索引的Roaring位图数据不进行压缩。一个Bitmap Page写满之后,会创建新的Bitmap Page用于记录该列后续的Roaring位图数据。如果某一列有多个Bitmap Page,则会采用两级索引机制。第二级索引为多个的Bitmap Page,其中保存Bitmap索引的位图数据,每一个Bitmap Page生成一条Ordinal索引项,所有Bitmap Page的Ordinal索引项会被保存在一个Ordinal Index Page 中作为一级索引。每一个的Ordinal索引项由key和value两部分组成,key记录了当前Bitmap Page中第一个Roaring位图在整个列的BitMap索引Roaring位图中的序号,value记录了当前Bitmap Page在Segment文件中的offset和大小。Ordinal Index Page中包含以下信息:所有Bitmap Page的Ordinal 索引数据、Ordinal Index Page的footer以及Ordinal Index Page的Checksum信息。Ordinal Index Page的footer中包含当前Page的类型、当前Page中索引数据的大小、当前Page中索引项数目等。一级索引Ordinal Index Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。如果某一列只有一个Bitmap Page,则不需要两级索引,这个唯一的Bitmap Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。Bitmap索引的Roaring位图数据的存储结构如图所示。查询过滤数据查询时,会加载列的Bitmap索引数据,并解析出有序字典和Roaring位图数据。首先,通过Segment footer中记录的Bitmap索引的字典Meta信息判断当前列的Bitmap索引的字典是否含有两级索引,如果含有两级索引,则Segment footer中记录了一级索引Value Index Page在Block中的offset和大小,首先加载一级索引Value Index Page,并解析出每一个的Value索引项,获得每一个Dict Page中第一个字典值和每一个Dict Page在Segment文件中的offset和大小;否则,当前列的Bitmap索引只含有一个Dict Page,Segment footer中记录了该Dict Page在Segment文件中的offset和大小。可以通过Dict Page解析出每一个字典值。然后,通过Segment footer中记录的Bitmap索引的Roaring位图Meta信息判断当前列的Bitmap索引的Roaring位图是否含有两级索引,如果含有两级索引,则Segment footer中记录了一级索引Ordinal Index Page在Segment文件中的offset和大小,首先加载一级索引Ordinal Index Page,并解析出每一个的Ordinal索引项,获得每一个Bitmap Page中第一个Roaring位图在整个列的Bitmap索引Roaring位图中的序号以及每一个Bitmap Page在Segment文件中的offset和大小;否则,当前列的Bitmap索引只含有一个Bitmap Page,Segment footer中记录了该Bitmap Page在Segment文件中的offset和大小。可以通过Bitmap Page中解析出每一个字典值对应的Roaring位图。真正使用Bitmap索引进行数据过滤时才会加载Dict Page和Bitmap Page。使用某一个查询过滤条件进行行过滤的方法如下:过滤条件为 field = value。从 Dict Page 中寻找第一个等于或大于 value 的字典值,并且获取该字典值在有序字典中的序号ordinal。如果寻找到的字典值恰好等于value,则从 Bitmap Page 中读取第ordinal个位图,则该位图表示通过该查询条件过滤之后留下的行范围。过滤条件为 field != value。从Dict Page中寻找第一个等于或大于value的字典值,并且获取该字典值在有序字典中的序号ordinal。如果寻找到的字典值恰好等于value,则从Bitmap Page中读取第ordinal个位图,则该位图表示需要被过滤掉的行范围。过滤条件为 field < value。从Dict Page中寻找第一个等于或大于value的字典值,并且获取该字典值在有序字典中的序号ordinal。从Bitmap Page中读取前面ordinal个位图,这些位图的并集表示通过该查询条件过滤之后留下的行范围。过滤条件为 field <= value。从Dict Page中寻找第一个等于或大于value的字典值,并且获取该字典值在有序字典中的序号ordinal。如果寻找到的字典值恰好等于value,则从Bitmap Page中读取前面ordinal + 1个位图;如果寻找到的字典值大于value,则从Bitmap Page中读取前面ordinal个位图,这些位图的并集表示通过该查询条件过滤之后留下的行范围。过滤条件为 field > value。从Dict Page中寻找第一个等于或大于value的字典值,并且获取该字典值在有序字典中的序号ordinal。如果寻找到的字典值恰好等于value,则从Bitmap Page中读取第ordinal个位图之后的所有位图;如果寻找到的字典值大于value,则从Bitmap Page中读取第ordinal以及之后的所有位图,这些位图的并集表示通过该查询条件过滤之后留下的行范围。过滤条件为 field >= value。从Dict Page中寻找第一个等于或大于value的字典值,并且获取该字典值在有序字典中的序号ordinal。从Bitmap Page中读取ordinal之后的所有位图,这些位图的并集表示通过该查询条件过滤之后留下的行范围。适用场景Apache Doris支持在建表时对指定的列创建Bitmap索引,也可以对已经创建的表执行Alter Table命令添加Bitmap索引。 ALTER TABLE table_name ADD INDEX index_name (column_name) USING BITMAP COMMENT ‘’; 目前只支持对TINYINT、SMALLINT、INT、 UNSIGNEDINT、BIGINT、LARGEINT、CHAR、 VARCHAR、DATE、DATETIME、BOOL和DECIMAL类型的字段创建Bitmap索引,其他类型的字段均不支持Bitmap索引。Bitmap索引比较适合在基数较低的列上进行等值查询或范围查询的场景。Bloom Filter 索引Apache Doris支持用户对取值区分度比较大的字段添加Bloom Filter索引,Bloom Filter索引按照Data Page的粒度生成。数据写入时,会记录每一个写入Data Page的值,当一个Data Page写满之后,会根据该Data Page的所有不同取值为该Data Page生成Bloom Filter索引。数据查询时,查询条件在设置有Bloom Filter索引的字段进行过滤,当某个Data Page的Bloom Filter没有命中时,表示该Data Page中没有需要的数据,这样可以对Data Page进行快速过滤,减少不必要的数据读取。创建语句:CREATE TABLE IF NOT EXISTS sale_detail_bloom ( sale_date date NOT NULL COMMENT "销售时间", customer_id int NOT NULL COMMENT "客户编号", saler_id int NOT NULL COMMENT "销售员", sku_id int NOT NULL COMMENT "商品编号", category_id int NOT NULL COMMENT "商品分类", sale_count int NOT NULL COMMENT "销售数量", sale_price DECIMAL(12,2) NOT NULL COMMENT "单价", sale_amt DECIMAL(20,2) COMMENT "销售总金额" ) Duplicate KEY(sale_date, customer_id,saler_id,sku_id,category_id) PARTITION BY RANGE(sale_date) ( PARTITION P_202111 VALUES [('2021-11-01'), ('2021-12-01')) ) DISTRIBUTED BY HASH(saler_id) BUCKETS 10 PROPERTIES ( "replication_num" = "3", "bloom_filter_columns"="saler_id,category_id", "dynamic_partition.enable" = "true", "dynamic_partition.time_unit" = "MONTH", "dynamic_partition.time_zone" = "Asia/Shanghai", "dynamic_partition.start" = "-2147483648", "dynamic_partition.end" = "2", "dynamic_partition.prefix" = "P_", "dynamic_partition.replication_num" = "3", "dynamic_partition.buckets" = "3" );索引生成数据刷写时,会给每一个Data Page创建一条Bloom Filter索引项。Apache Doris采用了基于Block的Bloom Filter算法。每一个Data Page对应的Bloom Filter索引数据会被划分为多个Block,每个Block的数据长度为BYTES_PER_BLOCK(默认为32字节,共256bit),Block中的每一个Bit位会被初始化为0。向Data Page中写入数据时,每一个不同的取值value都会将一个Block中的BITS_SET_PER_BLOCK(默认值为8)个Bit置位为1。Bloom Filter索引的结构如图所示。单个Data Page的Bloom Filter索引数据长度BLOOM_FILTER_BIT通过如下公式计算:其中,N表示当前Data Page中的不同取值的个数;FPP(False Positive Probablity) 表示期望的误判率,默认取值为0.05。(注:计算出的Bloom Filter数据长度(单位为bit)一定是2的整数次幂。)Bloom Filter中,每一个Block的长度为BYTES_PER_BLOCK(32字节),因此,Bloom Filter中的Block数量通过如下公式计算:为Data Page生成Bloom Filter索引项的方法如下:针对Data Page中的每一个不同的取值value,计算出一个64位的HASH_CODE。Apache Doris中,Bloom Filter的Hash策略为HASH_MURMUR3。取HASH_CODE的高32位计算出当前value在Bloom Filter中对应的Block,方法如下:其中,BLOCK_INDEX表示Block的序号,BLOCK_NUM为2的整数次幂,则BLOCK_INDEX一定小于BLOCK_NUM。取HASH_CODE的低32位计算出当前value会将Block中的哪些Bit置位为1,方法如下:uint32_t key = (uint32_t)HASH_CODE uint32_t SALT[8] = {0x47b6137b, 0x44974d91, 0x8824ad5b, 0xa2b7289d, 0x705495c7, 0x2df1424b, 0x9efc4947, 0x5c6bfb31}; uint32_t masks[BITS_SET_PER_BLOCK]; for (int i = 0; i < BITS_SET_PER_BLOCK; ++i) { masks[i] = key * SALT[i]; masks[i] = masks[i] >> 27; masks[i] = 0x1 << masks[i]; } 其中,masks[i]包含32个Bit,其中只有1个Bit被置位为1,其他31个Bit均为0。将masks[i]与Block中第i个32Bit按位取或,更新Data Page的Bloom Filter索引数据。(一个Block中包含256个Bit,即 BITS_SET_PER_BLOCK 个32 Bit)uint32_t* BLOCK_OFFSET = BLOOM_FILTER_OFFSET + BYTES_PER_BLOCK * BLOCK_INDEX for (int i = 0; i < BITS_SET_PER_BLOCK; ++i) { *(BLOCK_OFFSET + i) |= masks[i]; } Bloom Filter索引项中单独设置了Data Page中是否包含了NULL值的标志。列中每一个Data Page的Bloom Filter索引项会被保存在Bloom Filter Index Page中。Bloom Filter Index Page中包含以下信息:Bloom Filter索引项数据、Bloom Filter Index Page的footer以及Bloom Filter Index Page的Checksum信息。Bloom Filter Index Page的footer中包含当前Page的类型、当前Page中Bloom Filter索引项数据的大小、当前Page中Bloom Filter索引项数目以及当前Page中第一条索引项在整个列的Bloom Filter索引项中的序号等信息。一个Bloom Filter Index Page写满之后,会创建新的Bloom Filter Index Page用于记录该列后续的Bloom Filter索引项。如果某一列有多个Bloom Filter Index Page,则该列的Bloom Filter索引会采用两级索引机制。第二级索引为多个的Bloom Filter Index Page,其中保存Data Page的Bloom Filter索引数据,每一个Bloom Filter Index Page生成一条Ordinal索引项,所有Bloom Filter Index Page的Ordinal索引项会被保存在一个Ordinal Index Page中作为一级索引。每一个的Ordinal索引项由key和value两部分组成,key记录了当前Bloom Filter Index Page中第一条索引项在整个列的Bloom Filter索引项中的序号,value记录了当前Bloom Filter Index Page在Segment文件中的offset和大小。Ordinal Index Page中包含以下信息:所有Bloom Filter Index Page的Ordinal 索引数据、Ordinal Index Page的footer以及Ordinal Index Page的Checksum信息。Ordinal Index Page的footer中包含当前Page的类型、当前Page中索引数据的大小、当前Page中索引项数目等。一级索引Ordinal Index Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。如果某一列只有一个Bloom Filter Index Page,则不需要两级索引,这个唯一的Bloom Filter Index Page在Segment文件中的offset和大小会被记录在Segment文件的footer中。Bloom Filter索引的存储结构如图所示。查询过滤数据查询时,会加载列的Bloom Filter索引数据,并解析出每一个Data Page的Bloom Filter索引项。首先,通过Segment footer中记录的Bloom Filter索引的Meta信息判断当前列的Bloom Filter是否含有两级索引,如果含有两级索引,则Segment footer中记录了一级索引Ordinal Index Page在Segment文件中的offset和大小,先加载一级索引Ordinal Index Page,并解析出每一个的Ordinal索引项的key和value,key记录了每一个Bloom Filter Index Page中第一条索引项在整个列所有的Bloom Filter索引项中的序号,value记录了每一个Bloom Filter Index Page在Segment文件中的offset和大小;否则,当前列的Bloom Filter索引只含有一个Bloom Filter Index Page,Segment footer中记录了该Bloom Filter Index Page在Segment文件中的offset和大小。可以通过Bloom Filter Index Page解析出每一个Data Page的Bloom Filter索引数据。判断某个值value是否命中Bloom Filter的方法如下:首先,基于HASH_MURMUR3方法对查询过滤条件的值value计算出64位的HASH_CODE;然后,采用与生成Bloom Filter索引数据相同的方法计算出该value值在Bloom Filter中对应的Block,以及在Block中对应的BITS_SET_PER_BLOCK个Bit位。判断Bloom Filter索引数据中对应Block的这BITS_SET_PER_BLOCK个Bit的值是否均为1。如果对应Block中的这BITS_SET_PER_BLOCK个Bit值均为1,则表示Bloom Filter命中,该value值在Bloom Filter对应的Data Page中可能存在;否则,表示Bloom Filter未命中,该value值在Bloom Filter对应的Data Page中一定不存在。数据查询时,查询过滤条件(“=”、"IS"或"IN"语句)在设置有Bloom Filter索引的列依次对每一个Data Page进行过滤。进行NULL值查询时,可以直接使用Bloom Filter索引项中的NULL值标志进行Data Page过滤。进行非NULL值查询时,使用查询过滤条件对Data Page进行过滤的方法如下:过滤条件为 field = value 。如果value未命中某一个Data Page对应的Bloom Filter,则该Data Page可以被过滤掉。过滤条件为 field IN {value1, value2, …} 。如果 value1、value2、…中所有值都未命中某一个Data Page对应的Bloom Filter,则该Data Page可以被过滤掉。过滤条件为 field IS NULL 。如果NULL值未命中某一个Data Page对应的Bloom Filter,则该Data Page可以被过滤掉。适用场景Apache Doris支持在建表时对指定的列创建Bloom Filter索引,也可以对已经创建的表执行Alter Table命令添加Bloom Filter索引。 ALTER TABLE table_name SET ("bloom_filter_columns"="c1, c2, c3"); 目前只支持对SMALLINT、INT、UNSIGNEDINT、 BIGINT、LARGEINT、CHAR、 VARCHAR、DATE、DATETIME和DECIMAL类型的字段创建Bloom Filter索引,其他类型的字段均不支持Bloom Filter索引。对于创建了Bloom Filter索引的字段,查询条件是"="、"is"或"in"语句时,才会使用Bloom Filter索引进行Data Page的过滤。Bloom Filter索引比较适合在基数较高的列上进行等值查询的场景。NGram BloomFilter索引为了提升like的查询性能,增加了NGram BloomFilter索引,其实现主要参照了ClickHouse的ngrambf。NGram BloomFilter只支持字符串列NGram BloomFilter索引和BloomFilter索引为互斥关系,即同一个列只能设置两者中的一个NGram大小和BloomFilter的字节数,可以根据实际情况调优,如果NGram比较小,可以适当增加BloomFilter大小如果要查看某个查询是否命中了NGram Bloom Filter索引,可以通过查询的Profile信息查看表创建时指定:CREATE TABLE `table3` ( `siteid` int(11) NULL DEFAULT "10" COMMENT "", `citycode` smallint(6) NULL COMMENT "", `username` varchar(32) NULL DEFAULT "" COMMENT "", INDEX idx_ngrambf (`username`) USING NGRAM_BF PROPERTIES("gram_size"="3", "bf_size"="256") COMMENT 'username ngram_bf index' ) ENGINE=OLAP AGGREGATE KEY(`siteid`, `citycode`, `username`) COMMENT "OLAP" DISTRIBUTED BY HASH(`siteid`) BUCKETS 10 PROPERTIES ( "replication_num" = "1" ); -- PROPERTIES("gram_size"="3", "bf_size"="256"),分别表示gram的个数和bloom filter的字节数。 -- gram的个数跟实际查询场景相关,通常设置为大部分查询字符串的长度,bloom filter字节数,可以通过测试得出,通常越大过滤效果越好,可以从256开始进行验证测试看看效果。当然字节数越大也会带来索引存储、内存cost上升。 -- 如果数据基数比较高,字节数可以不用设置过大,如果基数不是很高,可以通过增加字节数来提升过滤效果。倒排索引介绍从2.0.0版本开始,Doris支持倒排索引,可以用来进行文本类型的全文检索、普通数值日期类型的等值范围查询,快速从海量数据中过滤出满足条件的行。inverted index:倒排索引,是信息检索领域常用的索引技术,将文本分割成一个个词,构建 词 -> 文档编号 的索引,可以快速查找一个词在哪些文档出现。Doris使用CLucene作为底层的倒排索引库。CLucene是一个用C++实现的高性能、稳定的Lucene倒排索引库。Doris进一步优化了CLucene,使得它更简单、更快、更适合数据库场景。在Doris的倒排索引实现中,table的一行对应一个文档、一列对应文档中的一个字段,因此利用倒排索引可以根据关键词快速定位包含它的行,达到WHERE子句加速的目的。与Doris中其他索引不同的是,在存储层倒排索引使用独立的文件,跟segment文件有逻辑对应关系、但存储的文件相互独立。这样的好处是可以做到创建、删除索引不用重写tablet和segment文件,大幅降低处理开销。Doris倒排索引的功能简要介绍如下:增加了字符串类型的全文检索支持字符串全文检索,包括同时匹配多个关键字MATCH_ALL、匹配任意一个关键字MATCH_ANY支持字符串数组类型的全文检索支持英文、中文分词加速普通等值、范围查询,覆盖bitmap索引的功能,未来会代替bitmap索引支持字符串、数值、日期时间类型的 =, !=, >, >=, <, <= 快速过滤支持字符串、数字、日期时间数组类型的 =, !=, >, >=, <, <=支持完善的逻辑组合新增索引对OR NOT逻辑的下推支持多个条件的任意AND OR NOT组合灵活、快速的索引管理支持在创建表上定义倒排索引支持在已有的表上增加倒排索引,而且支持增量构建倒排索引,无需重写表中的已有数据支持删除已有表上的倒排索引,无需重写表中的已有数据使用建表时定义倒排索引:CREATE TABLE table_name ( columns_difinition, INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'] INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'] ) table_properties;USING INVERTED 是必须的,用于指定索引类型是倒排索引PROPERTIES 是可选的,用于指定倒排索引的额外属性,目前有一个属性parser指定分词器默认不指定代表不分词english是英文分词,适合被索引列是英文的情况,用空格和标点符号分词,性能高chinese是中文分词,适合被索引列有中文或者中英文混合的情况,采用jieba分词库,性能比english分词低COMMENT 是可选的,用于指定注释CREATE TABLE table_name ( columns_difinition, INDEX idx_name1(column_name1) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'] INDEX idx_name2(column_name2) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'] ) table_properties;已有表增加倒排索引:-- 语法1 CREATE INDEX idx_name ON table_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment']; -- 语法2 ALTER TABLE table_name ADD INDEX idx_name(column_name) USING INVERTED [PROPERTIES("parser" = "english|chinese")] [COMMENT 'your comment'];删除倒排索引:-- 语法1 DROP INDEX idx_name ON table_name; -- 语法2 ALTER TABLE table_name DROP INDEX idx_name;利用倒排索引加速查询:-- 1. 全文检索关键词匹配,通过MATCH_ANY MATCH_ALL完成 SELECT * FROM table_name WHERE column_name MATCH_ANY | MATCH_ALL 'keyword1 ...'; -- 1.1 logmsg中包含keyword1的行 SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword1'; -- 1.2 logmsg中包含keyword1或者keyword2的行,后面还可以添加多个keyword SELECT * FROM table_name WHERE logmsg MATCH_ANY 'keyword2 keyword2'; -- 1.3 logmsg中同时包含keyword1和keyword2的行,后面还可以添加多个keyword SELECT * FROM table_name WHERE logmsg MATCH_ALL 'keyword2 keyword2'; -- 2. 普通等值、范围、IN、NOT IN,正常的SQL语句即可,例如 SELECT * FROM table_name WHERE id = 123; SELECT * FROM table_name WHERE ts > '2023-01-01 00:00:00'; SELECT * FROM table_name WHERE op_type IN ('add', 'delete');
0
0
0
浏览量512
攻城狮无远

Doris-02-数据表的操作

数据表的操作创建用户和数据库创建 test 用户mysql -h hadoop1 -P 9030 -uroot -p create user 'test' identified by 'test'; 创建数据库create database test_db; 用户授权grant all on test_db to test; 基本概念在 Doris 中,数据都以关系表(Table)的形式进行逻辑上的描述。Row & Column一张表包括行(Row)和列(Column)。Row 即用户的一行数据。Column 用于描述一行数据中不同的字段。在默认的数据模型中,Column 只分为排序列和非排序列。存储引擎会按照排序列对数据进行排序存储,并建立稀疏索引,以便在排序数据上进行快速查找。而在聚合模型中,Column 可以分为两大类:Key 和 Value。从业务角度看,Key 和Value 可以分别对应维度列和指标列。从聚合模型的角度来说,Key 列相同的行,会聚合成一行。其中 Value 列的聚合方式由用户在建表时指定。Partition & Tablet在 Doris 的存储引擎中,用户数据首先被划分成若干个分区(Partition),划分的规则通常是按照用户指定的分区列进行范围划分,比如按时间划分。而在每个分区内,数据被进一步的按照 Hash 的方式分桶,分桶的规则是要找用户指定的分桶列的值进行 Hash 后分桶。每个分桶就是一个数据分片(Tablet),也是数据划分的最小逻辑单元。Tablet 之间的数据是没有交集的,独立存储的。Tablet 也是数据移动、复制等操作的最小物理存储单元。Partition 可以视为是逻辑上最小的管理单元。数据的导入与删除,都可以或仅能针对一个 Partition 进行。建表语句语法和示例使用 CREATE TABLE 命令建立一个表(Table)。更多详细参数可以查看:HELP CREATE TABLE;建表语法:CREATE [EXTERNAL] TABLE [IF NOT EXISTS] [database.]table_name (column_definition1[, column_definition2, ...] [, index_definition1[, index_definition12,]]) [ENGINE = [olap|mysql|broker|hive]] [key_desc] [COMMENT "table comment"]; [partition_desc] [distribution_desc] [rollup_index] [PROPERTIES ("key"="value", ...)] [BROKER PROPERTIES ("key"="value", ...)];Doris 的建表是一个同步命令,命令返回成功,即表示建表成功。Doris 支持支持单分区和复合分区两种建表方式。复合分区:既有分区也有分桶第一级称为 Partition,即分区。用户可以指定某一维度列作为分区列(当前只支持整型和时间类型的列),并指定每个分区的取值范围。第二级称为 Distribution,即分桶。用户可以指定一个或多个维度列以及桶数对数据进行 HASH 分布。单分区:只做 HASH 分布,即只分桶。字段类型:注:聚合模型在定义字段类型后,可以指定字段的 agg_type 聚合类型,如果不指定,则该列为 key 列。否则,该列为 value 列, 类型包括:SUM:求和。适用数值类型。 MIN:求最小值。适合数值类型。 MAX:求最大值。适合数值类型。 REPLACE:替换。对于维度列相同的行,指标列会按照导入的先后顺序,后倒入的替换先导入的。 REPLACE_IF_NOT_NULL:非空值替换。和 REPLACE 的区别在于对于null值,不做替换。这里要注意的是字段默认值要给NULL,而不能是空字符串,如果是空字符串,会给你替换成空字符串。 HLL_UNION:HLL 类型的列的聚合方式,通过 HyperLogLog 算法聚合。 BITMAP_UNION:BIMTAP 类型的列的聚合方式,进行位图的并集聚合。建表示例:我们以一个建表操作来说明 Doris 的数据划分。CREATE TABLE IF NOT EXISTS example_db.expamle_range_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `date` DATE NOT NULL COMMENT "数据灌入日期时间", `timestamp` DATETIME NOT NULL COMMENT "数据灌入的时间戳", `city` VARCHAR(20) COMMENT "用户所在城市", `age` SMALLINT COMMENT "用户年龄", `sex` TINYINT COMMENT "用户性别", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY RANGE(`date`) ( PARTITION `p201701` VALUES LESS THAN ("2017-02-01"), PARTITION `p201702` VALUES LESS THAN ("2017-03-01"), PARTITION `p201703` VALUES LESS THAN ("2017-04-01") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2018-01-01 12:00:00" );List Partition:CREATE TABLE IF NOT EXISTS example_db.expamle_list_tbl ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `date` DATE NOT NULL COMMENT "数据灌入日期时间", `timestamp` DATETIME NOT NULL COMMENT "数据灌入的时间戳", `city` VARCHAR(20) COMMENT "用户所在城市", `age` SMALLINT COMMENT "用户年龄", `sex` TINYINT COMMENT "用户性别", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时 间" ) ENGINE=olap AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) PARTITION BY LIST(`city`) ( PARTITION `p_cn` VALUES IN ("Beijing", "Shanghai", "Hong Kong"), PARTITION `p_usa` VALUES IN ("New York", "San Francisco"), PARTITION `p_jp` VALUES IN ("Tokyo") ) DISTRIBUTED BY HASH(`user_id`) BUCKETS 16 PROPERTIES ( "replication_num" = "3", "storage_medium" = "SSD", "storage_cooldown_time" = "2018-01-01 12:00:00" );列定义以 AGGREGATE KEY 数据模型为例进行说明。更多数据模型参阅 Doris 数据模型。列的基本类型,可以通过在 mysql-client 中执行 HELP CREATE TABLE; 查看。AGGREGATE KEY 数据模型中,所有没有指定聚合方式(SUM、REPLACE、MAX、MIN)的列视为 Key 列。而其余则为 Value 列。定义列时,可参照如下建议:Key 列必须在所有 Value 列之前。尽量选择整型类型。因为整型类型的计算和查找比较效率远高于字符串。对于不同长度的整型类型的选择原则,遵循够用即可。对于 VARCHAR 和 STRING 类型的长度,遵循 够用即可。所有列的总字节长度(包括 Key 和 Value)不能超过 100KB。分区与分桶Doris 支持两层的数据划分。第一层是 Partition,支持 Range 和 List 的划分方式。第二层是 Bucket(Tablet),仅支持 Hash 的划分方式。也可以仅使用一层分区。使用一层分区时,只支持 Bucket 划分。PartitionPartition 列可以指定一列或多列。分区类必须为 KEY 列。多列分区的使用方式在后面介绍。不论分区列是什么类型,在写分区值时,都需要加双引号。分区数量理论上没有上限。当不使用 Partition 建表时,系统会自动生成一个和表名同名的,全值范围的Partition。该 Partition 对用户不可见,并且不可删改。(1) Range 分区分区列通常为时间列,以方便的管理新旧数据。不可添加范围重叠的分区。Partition 指定范围的方式。VALUES LESS THAN (…) 仅指定上界,系统会将前一个分区的上界作为该分区的下界,生成一个左闭右开的区间。分区的删除不会改变已存在分区的范围。删除分区可能出现空洞。VALUES […) 指定同时指定上下界,生成一个左闭右开的区间。通过 VALUES […) 同时指定上下界比较容易理解。这里举例说明,当使用 VALUES LESS THAN (…) 语句进行分区的增删操作时,分区范围的变化情况:如上 expamle_range_tbl 示例,当建表完成后,会自动生成如下 3 个分区:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) 增加一个分区 p201705 VALUES LESS THAN (“2017-06-01”),分区结果如下:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201703: [2017-03-01, 2017-04-01) p201705: [2017-04-01, 2017-06-01) 此时删除分区 p201703,则分区结果如下:p201701: [MIN_VALUE, 2017-02-01) p201702: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01) 注意到 p201702 和 p201705 的分区范围并没有发生变化,而这两个分区之间,出现了一个空洞:[2017-03-01, 2017-04-01)。即如果导入的数据范围在这个空洞范围内,是无法导入的。继续删除分区 p201702,分区结果如下:p201701: [MIN_VALUE, 2017-02-01) p201705: [2017-04-01, 2017-06-01) 空洞范围变为:[2017-02-01, 2017-04-01)现在增加一个分区 p201702new VALUES LESS THAN (“2017-03-01”),分区结果如下:p201701: [MIN_VALUE, 2017-02-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01) 可以看到空洞范围缩小为:[2017-03-01, 2017-04-01)现在删除分区 p201701,并添加分区 p201612 VALUES LESS THAN (“2017-01-01”),分区结果如下:p201612: [MIN_VALUE, 2017-01-01) p201702new: [2017-02-01, 2017-03-01) p201705: [2017-04-01, 2017-06-01) 即出现了一个新的空洞:[2017-01-01, 2017-02-01)(2)List 分区分区列支持 BOOLEAN, TINYINT, SMALLINT, INT, BIGINT, LARGEINT, DATE, DATETIME, CHAR, VARCHAR 数据类型,分区值为枚举值。只有当数据为目标分区枚举值其中之一时,才可以命中分区。不可添加范围重叠的分区。Partition 支持通过 VALUES IN (…) 来指定每个分区包含的枚举值。下面通过示例说明,进行分区的增删操作时,分区的变化。如上 example_list_tbl 示例,当建表完成后,会自动生成如下 3 个分区:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") 增加一个分区 p_uk VALUES IN (“London”),分区结果如下:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_jp: ("Tokyo") p_uk: ("London") 删除分区 p_jp,分区结果如下:p_cn: ("Beijing", "Shanghai", "Hong Kong") p_usa: ("New York", "San Francisco") p_uk: ("London") 多列分区:Doris 支持指定多列作为分区列,示例如下:Range 分区:PARTITION BY RANGE(`date`, `id`) ( PARTITION `p201701_1000` VALUES LESS THAN ("2017-02-01", "1000"), PARTITION `p201702_2000` VALUES LESS THAN ("2017-03-01", "2000"), PARTITION `p201703_all` VALUES LESS THAN ("2017-04-01") ) 指定 date(DATE 类型) 和 id(INT 类型) 作为分区列。以上示例最终得到的分区如下:p201701_1000: [(MIN_VALUE, MIN_VALUE), ("2017-02-01", "1000") ) p201702_2000: [("2017-02-01", "1000"), ("2017-03-01", "2000") ) p201703_all: [("2017-03-01", "2000"), ("2017-04-01", MIN_VALUE)) 注意,最后一个分区用户缺省只指定了 date 列的分区值,所以 id 列的分区值会默认填充 MIN_VALUE。当用户插入数据时,分区列值会按照顺序依次比较,最终得到对应的分区。举例如下:数据 --> 分区 2017-01-01, 200 --> p201701_1000 2017-01-01, 2000 --> p201701_1000 2017-02-01, 100 --> p201701_1000 2017-02-01, 2000 --> p201702_2000 2017-02-15, 5000 --> p201702_2000 2017-03-01, 2000 --> p201703_all 2017-03-10, 1 --> p201703_all 2017-04-01, 1000 --> 无法导入 2017-05-01, 1000 --> 无法导入 List 分区PARTITION BY LIST(`id`, `city`) ( PARTITION `p1_city` VALUES IN (("1", "Beijing"), ("1", "Shanghai")), PARTITION `p2_city` VALUES IN (("2", "Beijing"), ("2", "Shanghai")), PARTITION `p3_city` VALUES IN (("3", "Beijing"), ("3", "Shanghai")) ) p1_city: [("1", "Beijing"), ("1", "Shanghai")] p2_city: [("2", "Beijing"), ("2", "Shanghai")] p3_city: [("3", "Beijing"), ("3", "Shanghai")] 当用户插入数据时,分区列值会按照顺序依次比较,最终得到对应的分区。举例如下:数据 ---> 分区 1, Beijing ---> p1_city 1, Shanghai ---> p1_city 2, Shanghai ---> p2_city 3, Beijing ---> p3_city 1, Tianjin ---> 无法导入 4, Beijing ---> 无法导入 Bucket如果使用了 Partition,则 DISTRIBUTED … 语句描述的是数据在各个分区内的划分规则。如果不使用 Partition,则描述的是对整个表的数据的划分规则。分桶列可以是多列,但必须为 Key 列。分桶列可以和 Partition 列相同或不同。分桶列的选择,是在 查询吞吐 和 查询并发 之间的一种权衡:如果选择多个分桶列,则数据分布更均匀。如果一个查询条件不包含所有分桶列的等值条件,那么该查询会触发所有分桶同时扫描,这样查询的吞吐会增加,单个查询的延迟随之降低。这个方式适合大吞吐低并发的查询场景。如果仅选择一个或少数分桶列,则对应的点查询可以仅触发一个分桶扫描。此时,当多个点查询并发时,这些查询有较大的概率分别触发不同的分桶扫描,各个查询之间的 IO 影响较小(尤其当不同桶分布在不同磁盘上时),所以这种方式适合高并发的点查询场景。分桶的数量理论上没有上限。使用复合分区的场景:以下场景推荐使用复合分区:有时间维度或类似带有有序值的维度,可以以这类维度列作为分区列。分区粒度可以根据导入频次、分区数据量等进行评估。历史数据删除需求:如有删除历史数据的需求(比如仅保留最近 N 天的数据)。使用复合分区,可以通过删除历史分区来达到目的。也可以通过在指定分区内发送 DELETE 语句进行数据删除。解决数据倾斜问题:每个分区可以单独指定分桶数量。如按天分区,当每天的数据量差异很大时,可以通过指定分区的分桶数,合理划分不同分区的数据,分桶列建议选择区分度大的列。PROPERTIES在建表语句的最后 PROPERTIES 中,可以指定以下两个参数:(1)replication_num每个 Tablet 的副本数量。默认为 3,建议保持默认即可。在建表语句中,所有 Partition 中的 Tablet 副本数量统一指定。而在增加新分区时,可以单独指定新分区中 Tablet 的副本数量。副本数量可以在运行时修改。强烈建议保持奇数。最大副本数量取决于集群中独立 IP 的数量(注意不是 BE 数量)。Doris 中副本分布的原则是,不允许同一个 Tablet 的副本分布在同一台物理机上,而识别物理机即通过 IP。所以,即使在同一台物理机上部署了 3 个或更多 BE 实例,如果这些 BE 的 IP 相同,则依然只能设置副本数为 1。对于一些小,并且更新不频繁的维度表,可以考虑设置更多的副本数。这样在 Join 查询时,可以有更大的概率进行本地数据 Join。(2)storage_medium & storage_cooldown_timeBE 的数据存储目录可以显式的指定为 SSD 或者 HDD(通过 .SSD 或者 .HDD 后缀区分)。建表时,可以统一指定所有 Partition 初始存储的介质。注意,后缀作用是显式指定磁盘介质,而不会检查是否与实际介质类型相符。默认初始存储介质可通过 fe 的配置文件 fe.conf 中指定 default_storage_medium=xxx,如果没有指定,则默认为 HDD。如果指定为 SSD,则数据初始存放在 SSD 上。如果没有指定 storage_cooldown_time,则默认 30 天后,数据会从 SSD 自动迁移到 HDD 上。如果指定了 storage_cooldown_time,则在到达 storage_cooldown_time 时间后,数据才会迁移。注意,当指定 storage_medium 时,如果 FE 参数 enable_strict_storage_medium_check 为False 该参数只是一个“尽力而为”的设置。即使集群内没有设置 SSD 存储介质,也不会报错,而是自动存储在可用的数据目录中。 同样,如果 SSD 介质不可访问、空间不足,都可能导致数据初始直接存储在其他可用介质上。而数据到期迁移到 HDD 时,如果 HDD 介质不 可 访 问 、 空 间 不 足 , 也 可 能 迁 移 失 败 ( 但 是 会 不 断 尝 试 ) 。 如 果 FE 参 数enable_strict_storage_medium_check 为 True 则当集群内没有设置 SSD 存储介质时,会报错Failed to find enough host in all backends with storage medium is SSD。ENGINE本示例中,ENGINE 的类型是 olap,即默认的 ENGINE 类型。在 Doris 中,只有这个ENGINE 类型是由 Doris 负责数据管理和存储的。其他 ENGINE 类型,如 mysql、broker、es 等等,本质上只是对外部其他数据库或系统中的表的映射,以保证 Doris 可以读取这些数据。而 Doris 本身并不创建、管理和存储任何非 olap ENGINE 类型的表和数据。数据模型Doris 的数据模型主要分为 3 类:Aggregate、Uniq、Duplicate。Aggregate 模型表中的列按照是否设置了 AggregationType,分为 Key(维度列)和 Value(指标列)。没有设置 AggregationType 的称为 Key,设置了 AggregationType 的称为 Value。当我们导入数据时,对于 Key 列相同的行会聚合成一行,而 Value 列会按照设置的AggregationType 进行聚合。AggregationType 目前有以下四种聚合方式:SUM:求和,多行的 Value 进行累加。REPLACE:替代,下一批数据中的 Value 会替换之前导入过的行中的 Value。REPLACE_IF_NOT_NULL :当遇到 null 值则不更新。MAX:保留最大值。MIN:保留最小值。数据的聚合,在 Doris 中有如下三个阶段发生:每一批次数据导入的 ETL 阶段。该阶段会在每一批次导入的数据内部进行聚合。底层 BE 进行数据 Compaction 的阶段。该阶段,BE 会对已导入的不同批次的数据进行进一步的聚合。数据查询阶段。在数据查询时,对于查询涉及到的数据,会进行对应的聚合。数据在不同时间,可能聚合的程度不一致。比如一批数据刚导入时,可能还未与之前已存在的数据进行聚合。但是对于用户而言,用户只能查询到聚合后的数据。即不同的聚合程度对于用户查询而言是透明的。用户需始终认为数据以最终的完成的聚合程度存在,而不应假设某些聚合还未发生。(1)示例一:导入数据聚合建表CREATE TABLE IF NOT EXISTS test_db.example_site_visit ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `date` DATE NOT NULL COMMENT "数据灌入日期时间", `city` VARCHAR(20) COMMENT "用户所在城市", `age` SMALLINT COMMENT "用户年龄", `sex` TINYINT COMMENT "用户性别", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间", `last_visit_date_not_null` DATETIME REPLACE_IF_NOT_NULL DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间" ) AGGREGATE KEY(`user_id`, `date`, `city`, `age`, `sex`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10; 插入数据insert into test_db.example_site_visit values\ (10000,'2017-10-01','北京',20,0,'2017-10-01 06:00:00','2017-10-01 06:00:00',20,10,10),\ (10000,'2017-10-01','北京',20,0,'2017-10-01 07:00:00','2017-10-01 07:00:00',15,2,2),\ (10001,'2017-10-01','北京',30,1,'2017-10-01 17:05:45','2017-10-01 07:00:00',2,22,22),\ (10002,'2017-10-02',' 上 海 ',20,1,'2017-10-02 12:59:12',null,200,5,5),\ (10003,'2017-10-02','广州',32,0,'2017-10-02 11:20:00','2017-10-02 11:20:00',30,11,11),\ (10004,'2017-10-01','深圳',35,0,'2017-10-01 10:00:15','2017-10-01 10:00:15',100,3,3),\ (10004,'2017-10-03','深圳',35,0,'2017-10-03 10:20:22','2017-10-03 10:20:22',11,6,6); 注意:Insert into 单条数据这种操作在 Doris 里只能演示不能在生产使用,会引发写阻塞。查看表select * from test_db.example_site_visit; 可以看到,用户 10000 只剩下了一行聚合后的数据。而其余用户的数据和原始数据保持一致。经过聚合,Doris 中最终只会存储聚合后的数据。换句话说,即明细数据会丢失,用户不能够再查询到聚合前的明细数据了。(2)示例二:保留明细数据建表:CREATE TABLE IF NOT EXISTS test_db.example_site_visit2 ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `date` DATE NOT NULL COMMENT "数据灌入日期时间", `timestamp` DATETIME COMMENT "数据灌入时间,精确到秒", `city` VARCHAR(20) COMMENT "用户所在城市", `age` SMALLINT COMMENT "用户年龄", `sex` TINYINT COMMENT "用户性别", `last_visit_date` DATETIME REPLACE DEFAULT "1970-01-01 00:00:00" COMMENT "用户最后一次访问时间", `cost` BIGINT SUM DEFAULT "0" COMMENT "用户总消费", `max_dwell_time` INT MAX DEFAULT "0" COMMENT "用户最大停留时间", `min_dwell_time` INT MIN DEFAULT "99999" COMMENT "用户最小停留时间" ) AGGREGATE KEY(`user_id`, `date`, `timestamp`, `city`, `age`, `sex`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10; 插入数据:insert into test_db.example_site_visit2 values(10000,'2017-10-01','2017-10-01 08:00:05',' 北 京 ',20,0,'2017-10-01 06:00:00',20,10,10),\ (10000,'2017-10-01','2017-10-01 09:00:05','北京',20,0,'2017-10-01 07:00:00',15,2,2),\ (10001,'2017-10-01','2017-10-01 18:12:10','北京',30,1,'2017-10-01 17:05:45',2,22,22),\ (10002,'2017-10-02','2017-10-02 13:10:00','上海',20,1,'2017-10-02 12:59:12',200,5,5),\ (10003,'2017-10-02','2017-10-02 13:15:00','广州',32,0,'2017-10-02 11:20:00',30,11,11),\ (10004,'2017-10-01','2017-10-01 12:12:48','深圳',35,0,'2017-10-01 10:00:15',100,3,3),\ (10004,'2017-10-03','2017-10-03 12:38:20','深圳',35,0,'2017-10-03 10:20:22',11,6,6); 查看表:select * from test_db.example_site_visit2; 存储的数据,和导入数据完全一样,没有发生任何聚合。这是因为,这批数据中,因为加入了 timestamp 列,所有行的 Key 都不完全相同。也就是说,只要保证导入的数据中,每一行的 Key 都不完全相同,那么即使在聚合模型下,Doris 也可以保存完整的明细数据。(3)示例三:导入数据与已有数据聚合往实例一中继续插入数据:insert into test_db.example_site_visit values(10004,'2017-10-03','深圳',35,0,'2017-10-03 11:22:00',null,44,19,19),\ (10005,'2017-10-03','长沙',29,1,'2017-10-03 18:11:02','2017-10-03 18:11:02',3,1,1); 查看表:select * from test_db.example_site_visit; 可以看到,用户 10004 的已有数据和新导入的数据发生了聚合。同时新增了 10005 用户的数据。Uniq 模型在某些多维分析场景下,用户更关注的是如何保证 Key 的唯一性,即如何获得 Primary Key 唯一性约束。因此,我们引入了 Uniq 的数据模型。该模型本质上是聚合模型的一个特例,也是一种简化的表结构表示方式。建表CREATE TABLE IF NOT EXISTS test_db.user ( `user_id` LARGEINT NOT NULL COMMENT "用户 id", `username` VARCHAR(50) NOT NULL COMMENT "用户昵称", `city` VARCHAR(20) COMMENT "用户所在城市", `age` SMALLINT COMMENT "用户年龄", `sex` TINYINT COMMENT "用户性别", `phone` LARGEINT COMMENT "用户电话", `address` VARCHAR(500) COMMENT "用户地址", `register_time` DATETIME COMMENT "用户注册时间" ) UNIQUE KEY(`user_id`, `username`) DISTRIBUTED BY HASH(`user_id`) BUCKETS 10; 插入数据insert into test_db.user values\ (10000,'wuyanzu',' 北 京 ',18,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\ (10000,'wuyanzu',' 北 京 ',19,0,12345678910,' 北 京 朝 阳 区 ','2017-10-01 07:00:00'),\ (10000,'zhangsan','北京',20,0,12345678910,'北京海淀区','2017-11-15 06:10:20'); 查询表select * from test_db.user; Uniq 模型完全可以用聚合模型中的 REPLACE 方式替代。其内部的实现方式和数据存储方式也完全一样。Duplicate 模型在某些多维分析场景下,数据既没有主键,也没有聚合需求。Duplicate 数据模型可以满足这类需求。数据完全按照导入文件中的数据进行存储,不会有任何聚合。即使两行数据完全相同,也都会保留。 而在建表语句中指定的 DUPLICATE KEY,只是用来指明底层数据按照那些列进行排序。建表CREATE TABLE IF NOT EXISTS test_db.example_log ( `timestamp` DATETIME NOT NULL COMMENT "日志时间", `type` INT NOT NULL COMMENT "日志类型", `error_code` INT COMMENT "错误码", `error_msg` VARCHAR(1024) COMMENT "错误详细信息", `op_id` BIGINT COMMENT "负责人 id", `op_time` DATETIME COMMENT "处理时间" ) DUPLICATE KEY(`timestamp`, `type`) DISTRIBUTED BY HASH(`timestamp`) BUCKETS 10; 插入数据insert into test_db.example_log values\ ('2017-10-01 08:00:05',1,404,'not found page', 101, '2017-10-01 08:00:05'),\ ('2017-10-01 08:00:05',1,404,'not found page', 101, '2017-10-01 08:00:05'),\ ('2017-10-01 08:00:05',2,404,'not found page', 101, '2017-10-01 08:00:06'),\ ('2017-10-01 08:00:06',2,404,'not found page', 101, '2017-10-01 08:00:07'); 查看表select * from test_db.example_log; 数据模型的选择建议因为数据模型在建表时就已经确定,且无法修改。所以,选择一个合适的数据模型非常重要。**Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。**但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。Uniq 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)动态分区动态分区是在 Doris 0.12 版本中引入的新功能。旨在对表级别的分区实现生命周期管理(TTL),减少用户的使用负担。目前实现了动态添加分区及动态删除分区的功能。动态分区只支持 Range 分区。原理和使用方式原理:在某些使用场景下,用户会将表按照天进行分区划分,每天定时执行例行任务,这时需要使用方手动管理分区,否则可能由于使用方没有创建分区导致数据导入失败,这给使用方带来了额外的维护成本。通过动态分区功能,用户可以在建表时设定动态分区的规则。FE 会启动一个后台线程,根据用户指定的规则创建或删除分区。用户也可以在运行时对现有规则进行变更。使用方式:动态分区的规则可以在建表时指定,或者在运行时进行修改。当前仅支持对单分区列的分区表设定动态分区规则。建表时指定:CREATE TABLE tbl1 (...) PROPERTIES ( "dynamic_partition.prop1" = "value1", "dynamic_partition.prop2" = "value2", ... )运行时修改:ALTER TABLE tbl1 SET ( "dynamic_partition.prop1" = "value1", "dynamic_partition.prop2" = "value2", ... )动态分区规则参数(1)主要参数:动态分区的规则参数都以 dynamic_partition. 为前缀:(2)创建历史分区的参数:dynamic_partition.create_history_partition默认为 false。当置为 true 时,Doris 会自动创建所有分区,当期望创建的分区个数大于 max_dynamic_partition_num 值时,操作将被禁止。当不指定 start 属性时,该参数不生效。dynamic_partition.history_partition_num当 create_history_partition 为 true 时,该参数用于指定创建历史分区数量。默认值为 - 1, 即未设置。dynamic_partition.hot_partition_num指定最新的多少个分区为热分区。对于热分区,系统会自动设置其 storage_medium 参数为 SSD,并且设置 storage_cooldown_time。hot_partition_num 是往前 n 天和未来所有分区我们举例说明。假设今天是 2021-05-20,按天分区,动态分区的属性设置为:hot_partition_num=2, end=3, start=-3。则系统会自动创建以下分区,并且设置 storage_medium 和 storage_cooldown_time 参数:p20210517 : ["2021-05-17", "2021-05-18") storage_medium=HDD storage_cooldown_time=9999-12-31 23:59:59 p20210518 : ["2021-05-18", "2021-05-19") storage_medium=HDD storage_cooldown_time=9999-12-31 23:59:59 p20210519 : ["2021-05-19", "2021-05-20") storage_medium=SSD storage_cooldown_time=2021-05-21 00:00:00 p20210520 : ["2021-05-20", "2021-05-21") storage_medium=SSD storage_cooldown_time=2021-05-22 00:00:00 p20210521 : ["2021-05-21", "2021-05-22") storage_medium=SSD storage_cooldown_time=2021-05-23 00:00:00 p20210522 : ["2021-05-22", "2021-05-23") storage_medium=SSD storage_cooldown_time=2021-05-24 00:00:00 p20210523 : ["2021-05-23", "2021-05-24") storage_medium=SSD storage_cooldown_time=2021-05-25 00:00:00 dynamic_partition.reserved_history_periods需要保留的历史分区的时间范围。当 dynamic_partition.time_unit 设置为"DAY/WEEK/MONTH" 时,需要以 [yyyy-MM-dd,yyyy-MM-dd],[…,…] 格式进行设置。当dynamic_partition.time_unit 设置为 “HOUR” 时,需要以 [yyyy-MM-dd HH:mm:ss,yyyy MM-dd HH:mm:ss],[…,…] 的格式来进行设置。如果不设置,默认为 “NULL”。我们举例说明。假设今天是 2021-09-06,按天分类,动态分区的属性设置为:time_unit="DAY/WEEK/MONTH", \ end=3, \ start=-3, \ reserved_history_periods="[2020-06-01,2020-06-20],[2020-10-31,2020-11-15]"。 则系统会自动保留:["2020-06-01","2020-06-20"], ["2020-10-31","2020-11-15"] 或者time_unit="HOUR", \ end=3, \ start=-3, \ reserved_history_periods="[2020-06-01 00:00:00,2020-06-01 03:00:00]".则系统会自动保留:["2020-06-01 00:00:00","2020-06-01 03:00:00"](3)创建历史分区规则假设需要创建的历史分区数量为 expect_create_partition_num,根据不同的设置具体数量如下:create_history_partition = truedynamic_partition.history_partition_num 未设置,即 -1。则 expect_create_partition_num = end - start;dynamic_partition.history_partition_num 已设置则 expect_create_partition_num = end - max(start, -histoty_partition_num);create_history_partition = false不会创建历史分区,expect_create_partition_num = end - 0;当 expect_create_partition_num > max_dynamic_partition_num(默认 500)时,禁止创建过多分区。(4)创建历史分区举例假设今天是 2021-05-20,按天分区,动态分区的属性设置为:create_history_partition=true, end=3, start=-3, history_partition_num=1,则系统会自动创建以下分区:p20210519 p20210520 p20210521 p20210522 p20210523history_partition_num=5,其余属性与 1 中保持一直,则系统会自动创建以下分区:p20210517 p20210518 p20210519 p20210520 p20210521 p20210522 p20210523history_partition_num=-1 即不设置历史分区数量,其余属性与 1 中保持一直,则系统会自动创建以下分区:p20210517 p20210518 p20210519 p20210520 p20210521 p20210522 p20210523(5)注意事项动 态 分 区 使 用 过 程 中 , 如 果 因 为 一 些 意 外 情 况 导 致 dynamic_partition.start 和dynamic_partition.end 之间的某些分区丢失,那么当前时间与 dynamic_partition.end 之间的丢失分区会被重新创建,dynamic_partition.start 与当前时间之间的丢失分区不会重新创建。示例(1)创建动态分区表分区列 time 类型为 DATE,创建一个动态分区规则。按天分区,只保留最近 7 天的分区,并且预先创建未来 3 天的分区。create table student_dynamic_partition1 (id int, time date, name varchar(50), age int ) duplicate key(id,time) PARTITION BY RANGE(time)() DISTRIBUTED BY HASH(id) buckets 10 PROPERTIES( "dynamic_partition.enable" = "true", "dynamic_partition.time_unit" = "DAY", "dynamic_partition.start" = "-7", "dynamic_partition.end" = "3", "dynamic_partition.prefix" = "p", "dynamic_partition.buckets" = "10", "replication_num" = "1" );(2)查看动态分区表调度情况SHOW DYNAMIC PARTITION TABLES;LastUpdateTime: 最后一次修改动态分区属性的时间LastSchedulerTime: 最后一次执行动态分区调度的时间State: 最后一次执行动态分区调度的状态LastCreatePartitionMsg: 最后一次执行动态添加分区调度的错误信息LastDropPartitionMsg: 最后一次执行动态删除分区调度的错误信息(3)查看表的分区SHOW PARTITIONS FROM student_dynamic_partition1;(4)插入测试数据,可以全部成功(修改成对应时间)insert into student_dynamic_partition1 values(1,'2022-03-31 11:00:00','name1',18); insert into student_dynamic_partition1 values(1,'2022-04-01 11:00:00','name1',18); insert into student_dynamic_partition1 values(1,'2022-04-02 11:00:00','name1',18);(5)设置创建历史分区ALTER TABLE student_dynamic_partition1 SET ("dynamic_partition.create_history_partition" = "true"); 查看分区情况 SHOW PARTITIONS FROM student_dynamic_partition1;(6)动态分区表与手动分区表相互转换对于一个表来说,动态分区和手动分区可以自由转换,但二者不能同时存在,有且只有一种状态。手动分区转换为动态分区如果一个表在创建时未指定动态分区,可以通过 ALTER TABLE 在运行时修改动态分区相关属性来转化为动态分区,具体示例可以通过 HELP ALTER TABLE 查看。注意:如果已设定 dynamic_partition.start,分区范围在动态分区起始偏移之前的历史分区将会被删除。动态分区转换为手动分区ALTER TABLE tbl_name SET (“dynamic_partition.enable” = “false”)关闭动态分区功能后,Doris 将不再自动管理分区,需要用户手动通过 ALTER TABLE 的方式创建或删除分区。临时分区临时分区是归属于某一分区表的。只有分区表可以创建临时分区。规则:临时分区的分区列和正式分区相同,且不可修改。一张表所有临时分区之间的分区范围不可重叠,但临时分区的范围和正式分区范围可以重叠。临时分区的分区名称不能和正式分区以及其他临时分区重复。使用场景(1)原子的覆盖写操作某些情况下,用户希望能够重写某一分区的数据,但如果采用先删除再导入的方式进行,在中间会有一段时间无法查看数据。这时,用户可以先创建一个对应的临时分区,将新的数据导入到临时分区后,通过替换操作,原子的替换原有分区,以达到目的。对于非分区表的原子覆盖写操作,可以看替换表部分。(2)修改分桶数某些情况下,用户在创建分区时使用了不合适的分桶数。则用户可以先创建一个对应分区范围的临时分区,并指定新的分桶数。然后通过 INSERT INTO 命令将正式分区的数据导入到临时分区中,通过替换操作,原子的替换原有分区,以达到目的。(3)合并或分割分区某些情况下,用户希望对分区的范围进行修改,比如合并两个分区,或将一个大分区分割成多个小分区。则用户可以先建立对应合并或分割后范围的临时分区,然后通过 INSERT INTO 命令将正式分区的数据导入到临时分区中,通过替换操作,原子的替换原有分区,以达到目的。操作临时分区支持添加、删除、替换操作。(1)添加临时分区可以通过 ALTER TABLE ADD TEMPORARY PARTITION 语句对一个表添加临时分区:ALTER TABLE tbl1 ADD TEMPORARY PARTITION tp1 VALUES LESS THAN("2020-02-01"); ALTER TABLE tbl2 ADD TEMPORARY PARTITION tp1 VALUES [("2020-01-01"), ("2020-02-01")); ALTER TABLE tbl1 ADD TEMPORARY PARTITION tp1 VALUES LESS THAN("2020-02-01") ("in_memory" = "true", "replication_num" = "1") DISTRIBUTED BY HASH(k1) BUCKETS 5; ALTER TABLE tbl3 ADD TEMPORARY PARTITION tp1 VALUES IN ("Beijing", "Shanghai"); ALTER TABLE tbl4 ADD TEMPORARY PARTITION tp1 VALUES IN ((1, "Beijing"), (1, "Shanghai")); ALTER TABLE tbl3 ADD TEMPORARY PARTITION tp1 VALUES IN ("Beijing", "Shanghai") ("in_memory" = "true", "replication_num" = "1") DISTRIBUTED BY HASH(k1) BUCKETS 5;添加操作的一些说明:临时分区的添加和正式分区的添加操作相似。临时分区的分区范围独立于正式分区。临时分区可以独立指定一些属性。包括分桶数、副本数、是否是内存表、存储介质等信息。(2)删除临时分区可以通过 ALTER TABLE DROP TEMPORARY PARTITION 语句删除一个表的临时分区:ALTER TABLE tbl1 DROP TEMPORARY PARTITION tp1;删除操作的一些说明:删除临时分区,不影响正式分区的数据。(3)替换分区可以通过 ALTER TABLE REPLACE PARTITION 语句将一个表的正式分区替换为临时分区。ALTER TABLE tbl1 REPLACE PARTITION (p1) WITH TEMPORARY PARTITION (tp1); ALTER TABLE tbl1 REPLACE PARTITION (p1, p2) WITH TEMPORARY PARTITION (tp1, tp2, tp3); ALTER TABLE tbl1 REPLACE PARTITION (p1, p2) WITH TEMPORARY PARTITION (tp1, tp2) PROPERTIES ( "strict_range" = "false", "use_temp_partition_name" = "true" );替换操作有两个特殊的可选参数:strict_range:默认为 true。对于 Range 分区,当该参数为 true 时,表示要被替换的所有正式分区的范围并集需要和替换的临时分区的范围并集完全相同。当置为 false 时,只需要保证替换后,新的正式分区间的范围不重叠即可。对于 List 分区,该参数恒为 true。要被替换的所有正式分区的枚举值必须和替换的临时分区枚举值完全相同。use_temp_partition_name:默认为 false。当该参数为 false,并且待替换的分区和替换分区的个数相同时,则替换后的正式分区名称维持不变。如果为 true,则替换后,正式分区的名称为替换分区的名称。导入和查询(1)导入临时分区根据导入方式的不同,指定导入临时分区的语法稍有差别。这里通过示例进行简单说明:INSERT INTO tbl TEMPORARY PARTITION(tp1, tp2, ...) SELECT .... curl --location-trusted -u root: -H "label:123" -H "temporary_partitions: tp1, tp2, ..." -T testData http://host:port/api/testDb/testTbl/_stream_load LOAD LABEL example_db.label1 ( DATA INFILE("hdfs://hdfs_host:hdfs_port/user/palo/data/input/file") INTO TABLE `my_table` TEMPORARY PARTITION (tp1, tp2, ...) ... ) WITH BROKER hdfs ("username"="hdfs_user", "password"="hdfs_password"); CREATE ROUTINE LOAD example_db.test1 ON example_tbl COLUMNS(k1, k2, k3, v1, v2, v3 = k1 * 100), TEMPORARY PARTITIONS(tp1, tp2, ...), WHERE k1 > 100 PROPERTIES (...) FROM KAFKA (...);(2)查询临时分区SELECT ... FROM tbl1 TEMPORARY PARTITION(tp1, tp2, ...) JOIN tbl2 TEMPORARY PARTITION(tp1, tp2, ...) ON ... WHERE ...;RollupROLLUP 在多维分析中是“上卷”的意思,即将数据按某种指定的粒度进行进一步聚合。基本概念在 Doris 中,我们将用户通过建表语句创建出来的表称为 Base 表(Base Table)。Base 表中保存着按用户建表语句指定的方式存储的基础数据。在 Base 表之上,我们可以创建任意多个 ROLLUP 表。这些 ROLLUP 的数据是基于 Base 表产生的,并且在物理上是独立存储的。ROLLUP 表的基本作用,在于在 Base 表的基础上,获得更粗粒度的聚合数据。Aggregate 和 Uniq 模型中的 ROLLUP因为 Uniq 只是 Aggregate 模型的一个特例,所以这里我们不加以区别。示例1:以example_site_visit2表为例:查看表的结构信息desc example_site_visit2 all; 比如需要查看某个用户的总消费,那么可以建立一个只有 user_id 和 cost 的 rollupalter table example_site_visit2 add rollup rollup_cost_userid(user_id,cost); 查看表的结构信息desc example_site_visit2 all; 然后可以通过 explain 查看执行计划,是否使用到了 rollupexplain SELECT user_id, sum(cost) FROM example_site_visit2 GROUP BY user_id; Doris 会自动命中这个 ROLLUP 表,从而只需扫描极少的数据量,即可完成这次聚合查询。通过命令查看完成状态SHOW ALTER TABLE ROLLUP;示例 2:获得不同城市,不同年龄段用户的总消费、最长和最短页面驻留时间创建 ROLLUPalter table example_site_visit2 add rollup rollup_city_age_cost_maxd_mind(c查看 rollup 使用explain SELECT city, age, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM example_site_visit2 GROUP BY city, age; explain SELECT city, sum(cost), max(max_dwell_time), min(min_dwell_time) FROM example_site_visit2 GROUP BY city; explain SELECT city, age, sum(cost), min(min_dwell_time) FROM example_site_visi通过命令查看完成状态SHOW ALTER TABLE ROLLUP;Duplicate模型中的ROLLUP(命中前缀索引)因为 Duplicate 模型没有聚合的语意。所以该模型中的 ROLLUP,已经失去了“上卷”这一层含义。而仅仅是作为调整列顺序,以命中前缀索引的作用。因为建表时已经指定了列顺序,所以一个表只有一种前缀索引。这对于使用其他不能命中前缀索引的列作为条件进行的查询来说,效率上可能无法满足需求。因此,我们可以通过创建 ROLLUP 来人为的调整列顺序。举例说明:Base 表结构如下:我们可以在此基础上创建一个 ROLLUP 表:可以看到,ROLLUP 和 Base 表的列完全一样,只是将 user_id 和 age 的顺序调换了。那么当我们进行如下查询时:SELECT * FROM table where age=20 and message LIKE "%error%";会优先选择 ROLLUP 表,因为 ROLLUP 的前缀索引匹配度更高。说明ROLLUP 最根本的作用是提高某些查询的查询效率(无论是通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。因此 ROLLUP 的含义已经超出了“上卷”的范围。这也是为什么在源代码中,将其命名为 Materialized Index(物化索引)的原因。ROLLUP 是附属于 Base 表的,可以看做是 Base 表的一种辅助数据结构。用户可以在 Base 表的基础上,创建或删除 ROLLUP,但是不能在查询中显式的指定查询某ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。ROLLUP 的数据是独立物理存储的。因此,创建的 ROLLUP 越多,占用的磁盘空间也就越大。同时对导入速度也会有影响(导入的 ETL 阶段会自动产生所有ROLLUP 的数据),但是不会降低查询效率(只会更好)。ROLLUP 的数据更新与 Base 表是完全同步的。用户无需关心这个问题。ROLLUP 中列的聚合方式,与 Base 表完全相同。在创建 ROLLUP 无需指定,也不能修改。查询能否命中 ROLLUP 的一个必要条件(非充分条件)是,查询所涉及的所有列(包括 select list 和 where 中的查询条件列等)都存在于该 ROLLUP 的列中。否则,查询只能命中 Base 表。某些类型的查询(如 count(*))在任何条件下,都无法命中 ROLLUP。可以通过 EXPLAIN your_sql; 命令获得查询执行计划,在执行计划中,查看是否命中 ROLLUP。可以通过 DESC tbl_name ALL; 语句显示 Base 表和所有已创建完成的 ROLLUP。物化视图基本概念物化视图就是包含了查询结果的数据库对象,可能是对远程数据的本地 copy,也可能是一个表或多表 join 后结果的行或列的子集,也可能是聚合后的结果。说白了,就是预先存储查询结果的一种数据库对象。在 Doris 中的物化视图,就是查询结果预先存储起来的特殊的表。物化视图的出现主要是为了满足用户,既能对原始明细数据的任意维度分析,也能快速的对固定维度进行分析查询。适用场景:分析需求覆盖明细数据查询以及固定维度查询两方面。查询仅涉及表中的很小一部分列或行。查询包含一些耗时处理操作,比如:时间很久的聚合操作等。查询需要匹配不同前缀索引。优势:对于那些经常重复的使用相同的子查询结果的查询性能大幅提升。Doris 自动维护物化视图的数据,无论是新的导入,还是删除操作都能保证 base 表和物化视图表的数据一致性。无需任何额外的人工维护成本。查询时,会自动匹配到最优物化视图,并直接从物化视图中读取数据。自动维护物化视图的数据会造成一些维护开销,会在后面的物化视图的局限性中展开说明。物化视图 VS Rollup:在没有物化视图功能之前,用户一般都是使用 Rollup 功能通过预聚合方式提升查询效率的。但是 Rollup 具有一定的局限性,他不能基于明细模型做预聚合。物化视图则在覆盖了 Rollup 的功能的同时,还能支持更丰富的聚合函数。所以物化视图其实是 Rollup 的一个超集。也就是说,之前 ALTER TABLE ADD ROLLUP 语法支持的功能现在均可以通过CREATE MATERIALIZED VIEW 实现。示例一:# 创建base表 create table sales_records( record_id int, seller_id int, store_id int, sale_date date, sale_amt bigint ) distributed by hash(record_id) properties("replication_num" = "1"); # 插入数据 insert into sales_records values(1,2,3,'2020-02-02',10); # 基于这个 Base 表的数据提交一个创建物化视图的任务 create materialized view store_amt as select store_id, sum(sale_amt) from sales_records group by store_id; # 由于创建物化视图是一个异步的操作,用户在提交完创建物化视图任务后,需要异步的通过命令检查物化视图是否构建完成。 SHOW ALTER TABLE MATERIALIZED VIEW FROM test_db; (Version 0.13) # 查看 Base 表的所有物化视图 desc sales_records all; # 检验当前查询是否匹配到了合适的物化视图 EXPLAIN SELECT store_id, sum(sale_amt) FROM sales_records GROUP BY store_id; # 删除物化视图语法 DROP MATERIALIZED VIEW 物化视图名 on Base 表名;原理Doris 系统提供了一整套对物化视图的 DDL 语法,包括创建,查看,删除。DDL 的语法和 PostgreSQL, Oracle 都是一致的。但是 Doris 目前创建物化视图只能在单表操作,不支持 join。(1)创建物化视图首先要根据查询语句的特点来决定创建一个什么样的物化视图。并不是说物化视图定义和某个查询语句一模一样就最好。这里有两个原则:从查询语句中抽象出,多个查询共有的分组和聚合方式作为物化视图的定义。不需要给所有维度组合都创建物化视图。首先第一个点,一个物化视图如果抽象出来,并且多个查询都可以匹配到这张物化视图。这种物化视图效果最好。因为物化视图的维护本身也需要消耗资源。如果物化视图只和某个特殊的查询很贴合,而其他查询均用不到这个物化视图。则会导致这张物化视图的性价比不高,既占用了集群的存储资源,还不能为更多的查询服务。所以用户需要结合自己的查询语句,以及数据维度信息去抽象出一些物化视图的定义。第二点就是,在实际的分析查询中,并不会覆盖到所有的维度分析。所以给常用的维度组合创建物化视图即可,从而到达一个空间和时间上的平衡。通过下面命令就可以创建物化视图了。创建物化视图是一个异步的操作,也就是说用户成功提交创建任务后,Doris 会在后台对存量的数据进行计算,直到创建成功。具体的语法可以通过下面命令查看:HELP CREATE MATERIALIZED VIEW 这里以一个销售记录表为例:比如我们有一张销售记录明细表,存储了每个交易的时间,销售员,销售门店,和金额。提交完创建物化视图的任务后,Doris 就会异步在后台生成物化视图的数据,构建物化视图。在构建期间,用户依然可以正常的查询和导入新的数据。创建任务会自动处理当前的存量数据和所有新到达的增量数据,从而保持和 base 表的数据一致性。用户不需关心一致性问题。(2)查询自动匹配物化视图创建完成后,用户的查询会根据规则自动匹配到最优的物化视图。物化视图的自动匹配分为下面两个步骤:根据查询条件选出一个最优的物化视图:这一步的输入是所有候选物化视图表的元数据,根据查询的条件从候选集中输出最优的一个物化视图;根据选出的物化视图对查询进行改写:这一步是结合上一步选择出的最优物化视图,进行查询的改写,最终达到直接查询物化视图的目的。(3)最优路径选择这里分为两个步骤:对候选集合进行一个过滤。只要是查询的结果能从物化视图数据计算(取部分行,部分列,或部分行列的聚合)出都可以留在候选集中,过滤完成后候选集合大小>=1;从候选集合中根据聚合程度,索引等条件选出一个最优的也就是查询花费最少物化视图。这里再举一个相对复杂的例子,来体现这个过程:候选集过滤目前分为 4 层,每一层过滤后去除不满足条件的物化视图。比如查询 7 月 19 日,各个销售员都买了多少钱,候选集中包括所有的物化视图以及 base表共 4 个:第一层过滤先判断查询 where 中的谓词涉及到的数据是否能从物化视图中得到。也就是销售时间列是否在表中存在。由于第三个物化视图中根本不存在销售时间列。所以在这一层过滤中,mv_3 就被淘汰了。第二层是过滤查询的分组列是否为候选集的分组列的子集。也就是销售员 id 是否为表中分组列的子集。由于第二个物化视图中的分组列并不涉及销售员 id。所以在这一层过滤中,mv_2 也被淘汰了。第三层过滤是看查询的聚合列是否为候选集中聚合列的子集。也就是对销售额求和是否能从候选集的表中聚合得出。这里 base 表和物化视图表均满足标准。最后一层是过滤看查询需要的列是否存在于候选集合的列中。由于候选集合中的表均满足标准,所以最终候选集合中的表为 销售明细表,以及 mv_1,这两张。候选集过滤完后输出一个集合,这个集合中的所有表都能满足查询的需求。但每张表的查询效率都不同。这时候就需要再这个集合根据前缀索引是否能匹配到,以及聚合程度的高低来选出一个最优的物化视图。从表结构中可以看出,base 表的销售日期列是一个非排序列,而物化视图表的日期是一个排序列,同时聚合程度上 mv_1 表明显比 base 表高。所以最后选择出 mv_1 作为该查询的最优匹配。最后再根据选择出的最优解,改写查询。刚才的查询选中 mv_1 后,将查询改写为从 mv_1 中读取数据,过滤出日志为 7 月 19 日的 mv_1 中的数据然后返回即可。使用限制目前支持的聚合函数包括,常用的 sum,min,max count,以及计算 pv ,uv, 留存率,等常用的去重算法 hll_union,和用于精确去重计算 count(distinct)的算法bitmap_union。物化视图的聚合函数的参数不支持表达式仅支持单列,比如: sum(a+b)不支持。使用物化视图功能后,由于物化视图实际上是损失了部分维度数据的。所以对表的 DML 类型操作会有一些限制:如果表的物化视图 key 中不包含删除语句中的条件列,则删除语句不能执行。比如想要删除渠道为 app 端的数据,由于存在一个物化视图并不包含渠道这个字段,则这个删除不能执行,因为删除在物化视图中无法被执行。这时候你只能把物化视图先删除,然后删除完数据后,重新构建一个新的物化视图。单表上过多的物化视图会影响导入的效率:导入数据时,物化视图和 base 表数据是同步更新的,如果一张表的物化视图表超过 10 张,则有可能导致导入速度很慢。这就像单次导入需要同时导入 10 张表数据是一样的。相同列,不同聚合函数,不能同时出现在一张物化视图中,比如:select sum(a), min(a) from table 不支持。物化视图针对 Unique Key 数据模型,只能改变列顺序,不能起到聚合的作用,所以在 Unique Key 模型上不能通过创建物化视图的方式对数据进行粗粒度聚合操作。修改表使用 ALTER TABLE 命令可以对表进行修改,包括 partition 、rollup、schema change、rename 和 index 五种。语法:ALTER TABLE [database.]table alter_clause1[, alter_clause2, ...];alter_clause 分为 partition 、rollup、schema change、rename 和 index 五种。(1)rename将名为 table1 的表修改为 table2ALTER TABLE table1 RENAME table2; 将表 example_table 中名为 rollup1 的 rollup index 修改为 rollup2ALTER TABLE example_table RENAME ROLLUP rollup1 rollup2; 将表 example_table 中名为 p1 的 partition 修改为 p2ALTER TABLE example_table RENAME PARTITION p1 p2; (2)partition增加分区, 使用默认分桶方式现有分区 [MIN, 2013-01-01),增加分区 [2013-01-01, 2014-01-01):ALTER TABLE example_db.my_tableADD PARTITION p1 VALUES LESS THAN ("2014-01-01"); 增加分区,使用新的分桶数ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES LESS THAN ("2015-01-01") DISTRIBUTED BY HASH(k1) BUCKETS 20; 增加分区,使用新的副本数ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES LESS THAN ("2015-01-01") ("replication_num"="1"); 修改分区副本数ALTER TABLE example_db.my_table MODIFY PARTITION p1 SET("replication_num"="1");批量修改指定分区ALTER TABLE example_db.my_table MODIFY PARTITION (p1, p2, p4) SET("in_memory"="true");删除分区ALTER TABLE example_db.my_table DROP PARTITION p1;增加一个指定上下界的分区ALTER TABLE example_db.my_table ADD PARTITION p1 VALUES [("2014-01-01"), ("2014-02-01"));(3)rollup创建 index: example_rollup_index,基于 base index(k1,k2,k3,v1,v2)。列式存储ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index(k1, k3, v1, v2);创建 index: example_rollup_index2,基于 example_rollup_index(k1,k3,v1,v2)ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index2 (k1, v1) FROM example_rollup_index;创建 index: example_rollup_index3, 基于 base index (k1,k2,k3,v1), 自定义 rollup 超时时间一小时。ALTER TABLE example_db.my_table ADD ROLLUP example_rollup_index(k1, k3, v1) PROPERTIES("timeout" = "3600");删除 index: example_rollup_index2ALTER TABLE example_db.my_table DROP ROLLUP example_rollup_index2;(4)表结构变更使用 ALTER TABLE 命令可以修改表的 Schema,包括如下修改:增加列删除列修改列类型改变列顺序以增加列为例:我们新增一列 uv,类型为 BIGINT,聚合类型为 SUM,默认值为 0:ALTER TABLE table1 ADD COLUMN uv BIGINT SUM DEFAULT '0' after pv;提交成功后,可以通过以下命令查看作业进度:SHOW ALTER TABLE COLUMN;当作业状态为 FINISHED,则表示作业完成。新的 Schema 已生效。查看新的 SchemaDESC table1;可以使用以下命令取消当前正在执行的作业:CANCEL ALTER TABLE ROLLUP FROM table1;替换表在 0.14 版本中,Doris 支持对两个表进行原子的替换操作。 该操作仅适用于 OLAP 表。分区级别的替换操作,使用临时分区。ALTER TABLE [db.]tbl1 REPLACE WITH TABLE tbl2 [PROPERTIES('swap' = 'true')]; 将表 tbl1 替换为表 tbl2。如果 swap 参数为 true,则替换后,名称为 tbl1 表中的数据为原 tbl2 表中的数据。而名称为 tbl2 表中的数据为原 tbl1 表中的数据。即两张表数据发生了互换。如果 swap 参数为 false,则替换后,名称为 tbl1 表中的数据为原 tbl2 表中的数据。而名称为 tbl2 表被删除。原理:替换表功能,实际上是将以下操作集合变成一个原子操作。假设要将表 A 替换为表 B,且 swap 为 true,则操作如下:将表 B 重名为表 A。将表 A 重名为表 B。如果 swap 为 false,则操作如下:删除表 A。将表 B 重名为表 A。删除数据(Delete)Doris 目前可以通过两种方式删除数据:DELETE FROM 语句和 ALTER TABLE DROP PARTITION 语句。(1) DELETE FROM Statement(条件删除)delete from 语句类似标准 delete 语法,具体使用可以查看 help delete; 帮助。语法:DELETE FROM table_name [PARTITION partition_name] WHERE column_name1 op { value | value_list } [ AND column_name2 op { value | value_list } ...];如:delete from student_kafka where id=1;注意事项:该语句只能针对 Partition 级别进行删除。如果一个表有多个 partition 含有需要删除的数据,则需要执行多次针对不同 Partition 的 delete 语句。而如果是没有使用Partition 的表,partition 的名称即表名。where 后面的条件谓词只能针对 Key 列,并且谓词之间,只能通过 AND 连接。如果想实现 OR 的语义,需要执行多条 delete。delete 是一个同步命令,命令返回即表示执行成功。从代码实现角度,delete 是一种特殊的导入操作。该命令所导入的内容,也是一个新的数据版本,只是该版本中只包含命令中指定的删除条件。在实际执行查询时,会根据这些条件进行查询时过滤。所以,不建议大量频繁使用 delete 命令,因为这可能导致查询效率降低。数据的真正删除是在 BE 进行数据 Compaction 时进行的。所以执行完 delete 命令后,并不会立即释放磁盘空间。delete 命令一个较强的限制条件是,在执行该命令时,对应的表,不能有正在进行的导入任务(包括 PENDING、ETL、LOADING)。而如果有 QUORUM_FINISHED 状态的导入任务,则可能可以执行。delete 也有一个隐含的类似 QUORUM_FINISHED 的状态。即如果 delete 只在多数副本上完成了,也会返回用户成功。但是会在后台生成一个异步的 delete job(Async Delete Job),来继续完成对剩余副本的删除操作。如果此时通过 show delete 命令,可以看到这种任务在 state 一栏会显示 QUORUM_FINISHED。(2)DROP PARTITION Statement(删除分区)该命令可以直接删除指定的分区。因为 Partition 是逻辑上最小的数据管理单元,所以使用 DROP PARTITION 命令可以很轻量的完成数据删除工作。并且该命令不受 load 以及任何其他操作的限制,同时不会影响查询效率。是比较推荐的一种数据删除方式。该命令是同步命令,执行成功即生效。而后台数据真正删除的时间可能会延迟 10 分钟左右。
0
0
0
浏览量525
攻城狮无远

Doris-03-Doris的查询(Join查询、去重)

查询查询设置(1)增大内存一个查询任务,在单个 BE 节点上默认使用不超过 2GB 内存,内存不够时, 查询可能会出现‘Memory limit exceeded’。SHOW VARIABLES LIKE "%mem_limit%";exec_mem_limit 的单位是 byte,可以通过 SET 命令改变 exec_mem_limit 的值。如改为 8GB。SET exec_mem_limit = 8589934592;上述设置仅仅在当前 session 有效, 如果想永久有效, 需要添加 global 参数。SET GLOBAL exec_mem_limit = 8589934592;(2)修改超时时间doris 默认最长查询时间为 300s, 如果仍然未完成, 会被 cancel 掉,查看配置:SHOW VARIABLES LIKE "%query_timeout%";可以修改为 60s:SET query_timeout = 60;同样, 如果需要全局生效需要添加参数 global。set global query_timeout = 60;当前超时的检查间隔为 5 秒,所以小于 5 秒的超时不会太准确。查询重试和高可用(ProxySQL)当部署多个 FE 节点时,用户可以在多个 FE 之上部署负载均衡层来实现 Doris 的高可用。代码方式:自己在应用层代码进行重试和负载均衡。比如发现一个连接挂掉,就自动在其他连接上进行重试。应用层代码重试需要应用自己配置多个 doris 前端节点地址。JDBC Connector:如果使用 mysql jdbc connector 来连接 Doris,可以使用 jdbc 的自动重试机制:jdbc:mysql://[host1][:port1],[host2][:port2][,[host3][:port3]]... [/[database]][?propertyName1=propertyValue1[&propertyName2=proper tyValue2]...] ProxySQL 方式ProxySQL 是灵活强大的 MySQL 代理层, 是一个能实实在在用在生产环境的 MySQL 中间件,可以实现读写分离,支持 Query 路由功能,支持动态指定某个 SQL 进行 cache,支持动态加载配置、故障切换和一些 SQL 的过滤功能。Doris 的 FE 进程负责接收用户连接和查询请求,其本身是可以横向扩展且高可用的,但是需要用户在多个 FE 上架设一层 proxy,来实现自动的连接负载均衡。(1)安装 ProxySQL (yum 方式)配置 yum 源 # vim /etc/yum.repos.d/proxysql.repo [proxysql_repo] name= ProxySQL YUM repository baseurl=http://repo.proxysql.com/ProxySQL/proxysql- 1.4.x/centos/\$releasever gpgcheck=1 gpgkey=http://repo.proxysql.com/ProxySQL/repo_pub_key 执行安装 # yum clean all # yum makecache # yum -y install proxysql 查看版本 # proxysql --version 设置开机自启动 # systemctl enable proxysql # systemctl start proxysql # systemctl status proxysql 启动后会监听两个端口, 默认为 6032 和 6033。6032 端口是 ProxySQL 的管理端口, 6033 是 ProxySQL 对外提供服务的端口 (即连接到转发后端的真正数据库的转发端口)。 # netstat -tunlp (2)ProxySQL 配置ProxySQL 有配置文件 /etc/proxysql.cnf 和配置数据库文件/var/lib/proxysql/proxysql.db。这里需要特别注意:如果存在如果存在"proxysql.db"文件(在/var/lib/proxysql 目录下),则ProxySQL 服务只有在第一次启动时才会去读取 proxysql.cnf 文件并解析;后面启动会就不会读取 proxysql.cnf 文件了!如果想要让 proxysql.cnf 文件里的配置在重启 proxysql 服务后生效(即想要让 proxysql 重启时读取并解析 proxysql.cnf 配置文件),则需要先删除/var/lib/proxysql/proxysql.db 数据库文件,然后再重启 proxysql 服务。这样就相当于初始化启动 proxysql 服务了,会再次生产一个纯净的 proxysql.db 数据库文件(如果之前配置了proxysql 相关路由规则等,则就会被抹掉)查看及修改配置文件:主要是几个参数,在下面已经注释出来了,可以根据自己的需要进行修改:# vim /etc/proxysql.cnf datadir="/var/lib/proxysql" #数据目录 admin_variables= { admin_credentials="admin:admin" #连接管理端的用户名与密码 mysql_ifaces="0.0.0.0:6032" #管理端口,用来连接 proxysql 的管理数据库 } mysql_variables= { threads=4 #指定转发端口开启的线程数量 max_connections=2048 default_query_delay=0 default_query_timeout=36000000 have_compress=true poll_timeout=2000 interfaces="0.0.0.0:6033" #指定转发端口,用于连接后端 mysql 数据库的,相当于代理作用 default_schema="information_schema" stacksize=1048576 server_version="5.7.28" #指定后端 mysql 的版本 connect_timeout_server=3000 monitor_username="monitor" monitor_password="monitor" monitor_history=600000 monitor_connect_interval=60000 monitor_ping_interval=10000 monitor_read_only_interval=1500 monitor_read_only_timeout=500 ping_interval_server_msec=120000 ping_timeout_server=500 commands_stats=true sessions_sort=true connect_retries_on_failure=10 } mysql_servers = () mysql_users: () mysql_query_rules: () scheduler= () mysql_replication_hostgroups= () 连接 ProxySQL 管理端口测试:# mysql -h 127.0.0.1 -P 6032 -u admin -p 查看 main 库(默认登陆后即在此库)的 global_variables 表信息 show databases; use main; show tables; ProxySQL 配置后端 Doris FE使用 insert 语句添加主机到 mysql_servers 表中,其中:hostgroup_id 为 10 表示写组,为 20 表示读组,我们这里不需要读写分离,无所谓随便设置哪一个都可以。mysql -u admin -p admin -P 6032 -h 127.0.0.1 insert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.8.101',9030); insert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.8.102',9030); insert into mysql_servers(hostgroup_id,hostname,port) values(10,'192.168.8.103',9030); # 如果在插入过程中,出现报错: # ERROR 1045 (#2800): UNIQUE constraint failed: mysql_servers.hostgroup_id, mysql_servers.hostname, mysql_servers.port # 说明可能之前就已经定义了其他配置,可以清空这张表 或者 删除对应 host 的配置 select * from mysql_servers; delete from mysql_servers; # 查看这 3 个节点是否插入成功,以及它们的状态。 select * from mysql_servers\G; # 如上修改后,加载到 RUNTIME,并保存到 disk,下面两步非常重要,不然退出以后配置信息就没了,必须保存 load mysql servers to runtime; save mysql servers to disk; 监控 Doris FE 节点配置添 doris fe 节点之后,还需要监控这些后端节点。对于后端多个 FE 高可用负载均衡环境来说,这是必须的,因为 ProxySQL 需要通过每个节点的 read_only 值来自动调整它们是属于读组还是写组。首先在后端 master 主数据节点上创建一个用于监控的用户名。# 在 doris fe master 主数据库节点行执行: # mysql -h hadoop1 -P 9030 -u root -p create user monitor@'192.168.8.%' identified by 'monitor'; grant ADMIN_PRIV on *.* to monitor@'192.168.8.%'; # 然后回到 mysql-proxy 代理层节点上配置监控 # mysql -uadmin -padmin -P6032 -h127.0.0.1 set mysql-monitor_username='monitor'; set mysql-monitor_password='monitor'; # 修改后,加载到 RUNTIME,并保存到 disk load mysql variables to runtime; save mysql variables to disk; # 验证监控结果:ProxySQL 监控模块的指标都保存在 monitor 库的 log 表中。 # 以下是连接是否正常的监控(对 connect 指标的监控): # 注意:可能会有很多 connect_error,这是因为没有配置监控信息时的错误,配置后如果connect_error 的结果为 NULL 则表示正常。 select * from mysql_server_connect_log; # 查看心跳信息的监控(对 ping 指标的监控) select * from mysql_server_ping_log; # 查看 read_only 日志此时也为空(正常来说,新环境配置时,这个只读日志是为空的) select * from mysql_server_read_only_log; load mysql servers to runtime; save mysql servers to disk; # 查看结果 select hostgroup_id,hostname,port,status,weight from mysql_servers; 配置 Doris 用户上面的所有配置都是关于后端 Doris FE 节点的,现在可以配置关于 SQL 语句的,包括:发送 SQL 语句的用户、SQL 语句的路由规则、SQL 查询的缓存、SQL 语句的重写等等。以下是 SQL 请求所使用的用户配置,例如 root 用户。这要求我们需要先在后端 Doris FE 节点添加好相关用户。这里以 root 和 doris 两个用户名为例。# 首先,在 Doris FE master 主数据库节点上执行: # mysql -h hadoop1 -P 9030 -u root -p # root 用户已经存在,直接创建 doris 用户: create user doris@'%' identified by 'doris'; grant ADMIN_PRIV on *.* to doris@'%'; # 回到 mysql-proxy 代理层节点,配置 mysql_users 表,将刚才的两个用户添加到该表中。 insert into mysql_users(username,password,default_hostgroup) values('root','000000',10); insert into mysql_users(username,password,default_hostgroup) values('doris','doris',10); # 加载用户到运行环境中,并将用户信息保存到磁盘 load mysql users to runtime; save mysql users to disk; select * from mysql_users\G # 只有 active=1 的用户才是有效的用户。确保 transaction_persistent 为 1: update mysql_users set transaction_persistent=1 where username='root'; update mysql_users set transaction_persistent=1 where username='doris'; load mysql users to runtime; save mysql users to disk; # 这里不需要读写分离,将这两个参数设为 true: UPDATE global_variables SET variable_value='true' WHERE variable_name='mysql-forward_autocommit'; UPDATE global_variables SET variable_value='true' WHERE variable_name='mysql-autocommit_false_is_transaction'; LOAD MYSQL VARIABLES TO RUNTIME; SAVE MYSQL VARIABLES TO DISK; 这样就可以通过 sql 客户端,使用 doris 的用户名密码去连接了 ProxySQL 了 。通过 ProxySQL 连接 Doris 进行测试分别使用 root 用户和 doris 用户测试下它们是否能路由到默认的 hostgroup_id=10 (它是一个写组)读数据。下面是通过转发端口 6033 连接的,连接的是转发到后端真正的数据库。mysql -udoris -pdoris -P6033 -h hadoop1 -e "show databases;" 到此就结束了,可以用 MySQL 客户端,JDBC 等任何连接 MySQL 的方式连接 ProxySQL 去操作 doris 了。验证:将 hadoop1 的 fe 停止,再执行mysql -udoris -pdoris -P6033 -h hadoop1 -e "show databases;" 能够正常使用。简单查询简单查询SELECT * FROM example_site_visit LIMIT 3; SELECT * FROM example_site_visit ORDER BY user_id; JoinSELECT SUM(example_site_visit.cost) FROM example_site_visit JOIN example_site_visit2 WHERE example_site_visit.user_id = example_site_visit2.user_id; select example_site_visit.user_id, sum(example_site_visit.cost) from example_site_visit join example_site_visit2 where example_site_visit.user_id = example_site_visit2.user_id group by example_site_visit.user_id; 子查询SELECT SUM(cost) FROM example_site_visit2 WHERE user_id IN (SELECT user_id FROM example_site_visit WHERE user_id > 10003); Join 查询Broadcast Join系统默认实现 Join 的方式,是将小表进行条件过滤后,将其广播到大表所在的各个节点上,形成一个内存 Hash 表,然后流式读出大表的数据进行 Hash Join。Doris 会自动尝试进行 Broadcast Join,如果预估小表过大则会自动切换至 Shuffle Join。注意,如果此时显式指定了 Broadcast Join 也会自动切换至 Shuffle Join。(1)默认使用 Broadcast Join:EXPLAIN SELECT SUM(example_site_visit.cost) FROM example_site_visit JOIN example_site_visit2 WHERE example_site_visit.city = example_site_visit2.city;(2)显式使用 Broadcast Join:EXPLAIN SELECT SUM(example_site_visit.cost) FROM example_site_visit JOIN [broadcast] example_site_visit2 WHERE example_site_visit.city = example_site_visit2.city;Shuffle Join(Partitioned Join)如果当小表过滤后的数据量无法放入内存的话,此时 Join 将无法完成,通常的报错应该是首先造成内存超限。可以显式指定 Shuffle Join,也被称作 Partitioned Join。即将小表和大表都按照 Join 的 key 进行 Hash,然后进行分布式的 Join。这个对内存的消耗就会分摊到集群的所有计算节点上。SELECT SUM(example_site_visit.cost) FROM example_site_visit JOIN [shuffle] example_site_visit2 WHERE example_site_visit.city = example_site_visit2.city;Colocation JoinColocation Join 是在 Doris0.9 版本引入的功能,旨在为 Join 查询提供本性优化,来减少数据在节点上的传输耗时,加速查询。(1)原理Colocation Join 功能,是将一组拥有 CGS 的表组成一个 CG。保证这些表对应的数据分片会落在同一个 be 节点上,那么使得两表再进行 join 的时候,可以通过本地数据进行直接join,减少数据在节点之间的网络传输时间。Colocation Group(CG):一个 CG 中会包含一张及以上的 Table。在同一个 Group 内的 Table 有着相同的 Colocation Group Schema,并且有着相同的数据分片分布。Colocation Group Schema(CGS):用于描述一个 CG 中的 Table,和 Colocation 相关的通用 Schema 信息。包括分桶列类型,分桶数以及副本数等。一个表的数据,最终会根据分桶列值 Hash、对桶数取模的后落在某一个分桶内。假设一个 Table 的分桶数为 8,则共有 [0, 1, 2, 3, 4, 5, 6, 7] 8 个分桶(Bucket),我们称这样一个序列为一个 BucketsSequence。每个 Bucket 内会有一个或多个数据分片(Tablet)。当表为单分区表时,一个 Bucket 内仅有一个 Tablet。如果是多分区表,则会有多个。使用限制:建表时两张表的分桶列的类型和数量需要完全一致,并且桶数一致,才能保证多张表的数据分片能够一一对应的进行分布控制。同一个 CG 内所有表的所有分区(Partition)的副本数必须一致。如果不一致,可能出现某一个 Tablet 的某一个副本,在同一个 BE 上没有其他的表分片的副本对应。同一个 CG 内的表,分区的个数、范围以及分区列的类型不要求一致。(2)使用建两张表,分桶列都为 int 类型,且桶的个数都是 8 个。副本数都为默认副本数。CREATE TABLE `tbl1` ( `k1` date NOT NULL COMMENT "", `k2` int(11) NOT NULL COMMENT "", `v1` int(11) SUM NOT NULL COMMENT "" ) ENGINE=OLAP AGGREGATE KEY(`k1`, `k2`) PARTITION BY RANGE(`k1`) ( PARTITION p1 VALUES LESS THAN ('2019-05-31'), PARTITION p2 VALUES LESS THAN ('2019-06-30') ) DISTRIBUTED BY HASH(`k2`) BUCKETS 8 PROPERTIES ( "colocate_with" = "group1" ); CREATE TABLE `tbl2` ( `k1` datetime NOT NULL COMMENT "", `k2` int(11) NOT NULL COMMENT "", `v1` double SUM NOT NULL COMMENT "" ) ENGINE=OLAP AGGREGATE KEY(`k1`, `k2`) DISTRIBUTED BY HASH(`k2`) BUCKETS 8 PROPERTIES ( "colocate_with" = "group1" ); 编写查询语句,并查看执行计划explain SELECT * FROM tbl1 INNER JOIN tbl2 ON (tbl1.k2 = tbl2.k2);HASH JOIN 处 colocate 显示为 true,代表优化成功。查看 GroupSHOW PROC '/colocation_group';当 Group 中最后一张表彻底删除后(彻底删除是指从回收站中删除。通常,一张表通过DROP TABLE 命令删除后,会在回收站默认停留一天的时间后,再删除),该 Group 也会被自动删除。修改表 Colocate Group 属性ALTER TABLE tbl SET ("colocate_with" = "group2");如果该表之前没有指定过 Group,则该命令检查 Schema,并将该表加入到该 Group(Group 不存在则会创建)。如果该表之前有指定其他 Group,则该命令会先将该表从原有 Group 中移除,并加入新Group(Group 不存在则会创建)。删除表的 Colocation 属性ALTER TABLE tbl SET ("colocate_with" = "");其他操作:当对一个具有 Colocation 属性的表进行增加分区(ADD PARTITION)、修改副本数时,Doris 会检查修改是否会违反 Colocation Group Schema,如果违反则会拒绝。Bucket Shuffle JoinBucket Shuffle Join 是在 Doris 0.14 版本中正式加入的新功能。旨在为某些 Join 查询提供本地性优化,来减少数据在节点间的传输耗时,来加速查询。(1)原理Doris 支持的常规分布式 Join 方式包括了 shuffle join 和 broadcast join。这两种 join 都会导致不小的网络开销:举个例子,当前存在 A 表与 B 表的 Join 查询,它的 Join 方式为 HashJoin,不同 Join 类型的开销如下:Broadcast Join: 如果根据数据分布,查询规划出 A 表有 3 个执行的 HashJoinNode,那么需要将 B 表全量的发送到 3 个 HashJoinNode,那么它的网络开销是 3B,它的内存开销也是 3B。Shuffle Join: Shuffle Join 会将 A,B 两张表的数据根据哈希计算分散到集群的节点之中,所以它的网络开销为 A + B,内存开销为 B。在 FE 之中保存了 Doris 每个表的数据分布信息,如果 join 语句命中了表的数据分布列,使用数据分布信息来减少 join 语句的网络与内存开销,这就是 Bucket Shuffle Join,原理如下图:(2)使用设置 Session 变量,从 0.14 版本开始默认为 trueshow variables like '%bucket_shuffle_join%'; set enable_bucket_shuffle_join = true; 在 FE 进行分布式查询规划时,优先选择的顺序为 Colocate Join -> Bucket Shuffle Join -> Broadcast Join -> Shuffle Join。但是如果用户显式 hint 了 Join 的类型,如:select * from test join [shuffle] baseall on test.k1 = baseall.k1; 则上述的选择优先顺序则不生效。通过 explain 查看 join 类型EXPLAIN SELECT SUM(example_site_visit.cost) FROM example_site_visit JOIN example_site_visit2 ON example_site_visit.user_id = example_site_visit2.user_id; 在 Join 类型之中会指明使用的 Join 方式为:BUCKET_SHUFFLE。(3)注意事项Bucket Shuffle Join 只生效于 Join 条件为等值的场景,原因与 Colocate Join 类似,它们都依赖 hash 来计算确定的数据分布。在等值 Join 条件之中包含两张表的分桶列,当左表的分桶列为等值的 Join 条件时,它有很大概率会被规划为 Bucket Shuffle Join。由于不同的数据类型的 hash 值计算结果不同,所以 Bucket Shuffle Join 要求左表的分桶列的类型与右表等值 join 列的类型需要保持一致,否则无法进行对应的规划。Bucket Shuffle Join 只作用于 Doris 原生的 OLAP 表,对于 ODBC,MySQL,ES 等外表,当其作为左表时是无法规划生效的。对于分区表,由于每一个分区的数据分布规则可能不同,所以 Bucket Shuffle Join只能保证左表为单分区时生效。所以在 SQL 执行之中,需要尽量使用 where 条件使分区裁剪的策略能够生效。假如左表为 Colocate 的表,那么它每个分区的数据分布规则是确定的,Bucket Shuffle Join 能在 Colocate 表上表现更好。四种 Shuffle 方式对比Shuffle方式网络开销物理算子适用场景BroadCastN * T®Hash Join / Nest Loop Join通用ShuffleT(S) + T®Hash Join通用Bucket ShuffleT®Hash JoinJoin条件中存在左表的分布式列,且左表执行时为单分区Colocate0Hash JoinJoin条件中存在左表的分布式列,且左右表同属于一个Colocate GroupN : 参与 Join 计算的 Instance 个数T(关系) : 关系的 Tuple 数目上面这 4 种方式灵活度是从高到低的,它对这个数据分布的要求是越来越严格,但 Join 计算的性能也是越来越好的。Runtime FilterRuntime Filter 是在 Doris 0.15 版本中正式加入的新功能。旨在为某些 Join 查询在运行时动态生成过滤条件,来减少扫描的数据量,避免不必要的 I/O 和网络传输,从而加速查询。(1)原理Runtime Filter 在查询规划时生成,在 HashJoinNode 中构建,在 ScanNode 中应用。举个例子,当前存在 T1 表与 T2 表的 Join 查询,它的 Join 方式为 HashJoin,T1 是一张事实表,数据行数为 100000,T2 是一张维度表,数据行数为 2000,Doris join 的实际情况是:显而易见对 T2 扫描数据要远远快于 T1,如果我们主动等待一段时间再扫描 T1,等 T2将扫描的数据记录交给 HashJoinNode 后,HashJoinNode 根据 T2 的数据计算出一个过滤条件,比如 T2 数据的最大和最小值,或者构建一个 Bloom Filter,接着将这个过滤条件发给等待扫描 T1 的 ScanNode,后者应用这个过滤条件,将过滤后的数据交给 HashJoinNode,从而减少 probe hash table 的次数和网络开销,这个过滤条件就是 Runtime Filter,效果如下:如果能将过滤条件(Runtime Filter)下推到存储引擎,则某些情况下可以利用索引来直接减少扫描的数据量,从而大大减少扫描耗时,效果如下:可见,和谓词下推、分区裁剪不同,Runtime Filter 是在运行时动态生成的过滤条件,即在查询运行时解析 join on clause 确定过滤表达式,并将表达式广播给正在读取左表的ScanNode,从而减少扫描的数据量,进而减少 probe hash table 的次数,避免不必要的 I/O 和网络传输。Runtime Filter 主要用于优化针对大表的 join,如果左表的数据量太小,或者右表的数据量太大,则 Runtime Filter 可能不会取得预期效果。(2)使用指定 RuntimeFilter 类型set runtime_filter_type="BLOOM_FILTER,IN,MIN_MAX"; 建表CREATE TABLE test (t1 INT) DISTRIBUTED BY HASH (t1) BUCKETS 2 PROPERTIES("replication_num" = "1"); INSERT INTO test VALUES (1), (2), (3), (4); CREATE TABLE test2 (t2 INT) DISTRIBUTED BY HASH (t2) BUCKETS 2 PROPERTIES("replication_num" = "1"); INSERT INTO test2 VALUES (3), (4), (5); 查看执行计划EXPLAIN SELECT t1 FROM test JOIN test2 where test.t1 = test2.t2;可以看到:HASH JOIN生成了 ID 为 RF000 的 IN predicate,其中test2.t2的 key values 仅在运行时可知,在 OlapScanNode 使用了该 IN predicate 用于在读取test.t1`时过滤不必要的数据。通过 profile 查看效果set enable_profile=true; SELECT t1 FROM test JOIN test2 where test.t1 = test2.t2; 查看对应 fe 节点的 webui,可以查看查询内部工作的详细信息:http://hadoop1:8030/QueryProfile/可以看到每个 Runtime Filter 是否下推、等待耗时、以及 OLAP_SCAN_NODE 从prepare 到接收到 Runtime Filter 的总时长。RuntimeFilter:in: - HasPushDownToEngine: true - AWaitTimeCost: 0ns - EffectTimeCost: 2.76ms 在 profile 的 OLAP_SCAN_NODE 中可以查看 Runtime Filter 下推后的过滤效果和耗时。 - RowsVectorPredFiltered: 9.320008M (9320008) - VectorPredEvalTime: 364.39ms (3)具体参数说明大多数情况下,只需要调整 runtime_filter_type 选项,其他选项保持默认即可:包括 BLOOM_FILTER、IN、MIN_MAX(也可以通过数字设置),默认会使用 IN,部分情况下同时使用 Bloom Filter、MinMax Filter、IN predicate 时性能更高,每个类型含义如下:Bloom Filter: 有一定的误判率,导致过滤的数据比预期少一点,但不会导致最终结果不准确,在大部分情况下 Bloom Filter 都可以提升性能或对性能没有显著影响,但在部分情况下会导致性能降低。①Bloom Filter 构建和应用的开销较高,所以当过滤率较低时,或者左表数据量较少时,Bloom Filter 可能会导致性能降低。②目前只有左表的 Key 列应用 Bloom Filter 才能下推到存储引擎,而测试结果显示 Bloom Filter 不下推到存储引擎时往往会导致性能降低。③目前 Bloom Filter 仅在 ScanNode 上使用表达式过滤时有短路(short-circuit)逻辑,即当假阳性率(实际是假但误辨为真的情况)过高时,不继续使用 Bloom Filter,但当Bloom Filter 下推到存储引擎后没有短路逻辑,所以当过滤率较低时可能导致性能降低。MinMax Filter: 包含最大值和最小值,从而过滤小于最小值和大于最大值的数据,MinMax Filter 的过滤效果与 join on clause 中 Key 列的类型和左右表数据分布有关①当 join on clause 中 Key 列的类型为 int/bigint/double 等时,极端情况下,如果左右表的最大最小值相同则没有效果,反之右表最大值小于左表最小值,或右表最小值大于左表最大值,则效果最好。②当 join on clause 中 Key 列的类型为 varchar 等时,应用 MinMax Filter 往往会导致性能降低。IN predicate: 根据 join on clause 中 Key 列在右表上的所有值构建 IN predicate,使用构建的 IN predicate 在左表上过滤,相比 Bloom Filter 构建和应用的开销更低,在右表数据量较少时往往性能更高。①默认只有右表数据行数少于 1024 才会下推(可通过 session 变量中的runtime_filter_max_in_num 调整)。②目前 IN predicate 已实现合并方法。③当同时指定 In predicate 和其他 filter ,并且 in 的 过 滤 数 值 没 达 到runtime_filter_max_in_num 时,会尝试把其他 filter 去除掉。原因是 In predicate 是精确的过滤条件,即使没有其他 filter 也可以高效过滤,如果同时使用则其他 filter 会做无用功。目前仅在 Runtime filter 的生产者和消费者处于同一个 fragment 时才会有去除非 in filter 的逻辑。其他查询选项通常仅在某些特定场景下,才需进一步调整以达到最优效果。通常只在性能测试后,针对资源密集型、运行耗时足够长且频率足够高的查询进行优化。runtime_filter_mode: 用于调整 Runtime Filter 的下推策略,包括 OFF、LOCAL、GLOBAL三种策略,默认设置为 GLOBAL 策略runtime_filter_wait_time_ms: 左表的 ScanNode 等待每个 Runtime Filter 的时间,默认1000msruntime_filters_max_num: 每个查询可应用的 Runtime Filter 中 Bloom Filter 的最大数量,默认 10runtime_bloom_filter_min_size: Runtime Filter 中 Bloom Filter 的最小长度,默认 1048576(1M)runtime_bloom_filter_max_size: Runtime Filter 中 Bloom Filter 的最大长度,默认 16777216(16M)runtime_bloom_filter_size: Runtime Filter中Bloom Filter的默认长度,默认2097152(2M)runtime_filter_max_in_num: 如果 join 右表数据行数大于这个值,我们将不生成 IN predicate,默认 1024(4)注意事项只支持对 join on clause 中的等值条件生成 Runtime Filter,不包括 Null-safe 条件,因为其可能会过滤掉 join 左表的 null 值。不支持将 Runtime Filter 下推到 left outer、full outer、anti join 的左表;不支持 src expr 或 target expr 是常量;不支持 src expr 和 target expr 相等;不支持 src expr 的类型等于 HLL 或者 BITMAP;目前仅支持将 Runtime Filter 下推给 OlapScanNode;不支持 target expr 包含 NULL-checking 表达式,比如 COALESCE/IFNULL/CASE,因为当 outer join 上层其他 join 的 join on clause 包含 NULL-checking 表达式并生成 Runtime Filter 时,将这个 Runtime Filter 下推到 outer join 的左表时可能导致结果不正确;不支持 target expr 中的列(slot)无法在原始表中找到某个等价列;不支持列传导,这包含两种情况:一是例如 join on clause 包含 A.k = B.k and B.k = C.k 时,目前 C.k 只可以下推给B.k,而不可以下推给 A.k;二是例如 join on clause 包含 A.a + B.b = C.c,如果 A.a 可以列传导到 B.a,即 A.a和 B.a 是等价的列,那么可以用 B.a 替换 A.a,然后可以尝试将 Runtime Filter 下推给 B(如果 A.a 和 B.a 不是等价列,则不能下推给 B,因为 target expr 必须与唯一一个 join 左表绑定);Target expr 和 src expr 的类型必须相等,因为 Bloom Filter 基于 hash,若类型不等则会尝试将 target expr 的类型转换为 src expr 的类型;不支持 PlanNode.Conjuncts 生成的 Runtime Filter 下推,与 HashJoinNode 的eqJoinConjuncts 和 otherJoinConjuncts 不同,PlanNode.Conjuncts 生成的 Runtime Filter 在测试中发现可能会导致错误的结果,例如 IN 子查询转换为 join 时,自动生成的 join on clause将保存在 PlanNode.Conjuncts 中,此时应用 Runtime Filter 可能会导致结果缺少一些行Join ReorderJoin Reorder 功能可以通过代价模型自动帮助调整 SQL 中 Join 的顺序,以帮助获得最优的 Join 效率。 可通过会话变量开启:set enable_cost_based_join_reorder=true 原理:数据库一旦涉及到多表 Join,Join 的顺序对整个 Join 查询的性能是影响很大的。假设有三张表 Join,参考下面这张图,左边是 a 表跟 b 张表先做 Join,中间结果的有 2000 行,然后与 c 表再进行 Join 计算。接下来看右图,把 Join 的顺序调整了一下。把 a 表先与 c 表 Join,生成的中间结果只有 100,然后最终再与 b 表 Join 计算。最终的 Join 结果是一样的,但是它生成的中间结果有 20 倍的差距,这就会产生一个很大的性能 Diff 了。Doris 目前支持基于规则的 Join Reorder 算法。它的逻辑是:让大表、跟小表尽量做 Join,它生成的中间结果是尽可能小的。把有条件的 Join 表往前放,也就是说尽量让有条件的 Join 表进行过滤Hash Join 的优先级高于 Nest Loop Join,因为 Hash join 本身是比 Nest Loop Join 快很多的。Join的优化原则在做 Join 的时候,要尽量选择同类型或者简单类型的列,同类型的话就减少它的数据 Cast,简单类型本身 Join 计算就很快。尽量选择 Key 列进行 Join, 原因前面在 Runtime Filter 的时候也介绍了,Key 列在延迟物化上能起到一个比较好的效果。大表之间的 Join ,尽量让它 Co-location ,因为大表之间的网络开销是很大的,如果需要去做 Shuffle 的话,代价是很高的。合理的使用 Runtime Filter,它在 Join 过滤率高的场景下效果是非常显著的。但是它并不是万灵药,而是有一定副作用的,所以需要根据具体的 SQL 的粒度做开关。涉及到多表 Join 的时候,需要去判断 Join 的合理性。尽量保证左表为大表,右表为小表,然后 Hash Join 会优于 Nest Loop Join。必要的时可以通过 SQL Rewrite,利用sHint 去调整 Join 的顺序。去重HLL近似去重在实际的业务场景中,随着业务数据量越来越大,对数据去重的压力也越来越大,当数据达到一定规模之后,使用精准去重的成本也越来越高,在业务可以接受的情况下,通过近似算法来实现快速去重降低计算压力是一个非常好的方式,Doris 提供的 HyperLogLog(简称 HLL)是一种近似去重算法。HLL 的特点是具有非常优异的空间复杂度 O(mloglogn) , 时间复杂度为 O(n), 并且计算结果的误差可控制在 1%—2% 左右,误差与数据集大小以及所采用的哈希函数有关。HLL 是基于 HyperLogLog 算法的工程实现,用于保存 HyperLogLog 计算过程的中间结果,它只能作为表的 value 列类型、通过聚合来不断的减少数据量,以此来实现加快查询的目的,基于它得到的是一个估算结果,误差大概在1%左右,hll 列是通过其它列或者导入数据里面的数据生成的,导入的时候通过 hll_hash 函数来指定数据中哪一列用于生成 hll 列,它常用于替代 count distinct,通过结合 rollup 在业务上用于快速计算uv等。HLL_UNION_AGG(hll):此函数为聚合函数,用于计算满足条件的所有数据的基数估算HLL_CARDINALITY(hll):此函数用于计算单条hll列的基数估算HLL_HASH(column_name):生成HLL列类型,用于insert或导入的时候创建:create table test_hll( dt date, id int, name char(10), province char(10), os char(10), pv hll hll_union ) Aggregate KEY (dt,id,name,province,os) distributed by hash(id) buckets 10 PROPERTIES( "replication_num" = "1", "in_memory"="false" ); 使用 HLL 去重的时候,需要在建表语句中将目标列类型设置成HLL,聚合函数设置成HLL_UNIONHLL类型的列不能作为 Key 列使用用户不需要指定长度及默认值,长度根据数据聚合程度系统内控制导入数据:curl --location-trusted -u root: -H "label:label_test_hll_load" \ -H "column_separator:," \ -H "columns:dt,id,name,province,os, pv=hll_hash(id)" -T test_hll.csv http://fe_IP:8030/api/demo/test_hll/_stream_load数据如下:2022-05-05,10001,测试01,北京,windows 2022-05-05,10002,测试01,北京,linux 2022-05-05,10003,测试01,北京,macos 2022-05-05,10004,测试01,河北,windows 2022-05-06,10001,测试01,上海,windows 2022-05-06,10002,测试01,上海,linux 2022-05-06,10003,测试01,江苏,macos 2022-05-06,10004,测试01,陕西,windows 查询:HLL 列不允许直接查询原始值,只能通过 HLL 的聚合函数进行查询。求总的PVmysql> select HLL_UNION_AGG(pv) from test_hll; +---------------------+ | hll_union_agg(`pv`) | +---------------------+ | 4 | +---------------------+ 1 row in set (0.00 sec) 等价于: mysql> SELECT COUNT(DISTINCT pv) FROM test_hll; +----------------------+ | count(DISTINCT `pv`) | +----------------------+ | 4 | +----------------------+ 1 row in set (0.01 sec) 求每一天的PVmysql> select HLL_UNION_AGG(pv) from test_hll group by dt; +---------------------+ | hll_union_agg(`pv`) | +---------------------+ | 4 | | 4 | +---------------------+ 2 rows in set (0.01 sec) BITMAP精准去重Doris原有的Bitmap聚合函数设计比较通用,但对亿级别以上bitmap大基数的交并集计算性能较差。排查后端be的bitmap聚合函数逻辑,发现主要有两个原因。一是当bitmap基数较大时,如bitmap大小超过1g,网络/磁盘IO处理时间比较长;二是后端be实例在scan数据后全部传输到顶层节点进行求交和并运算,给顶层单节点带来压力,成为处理瓶颈。解决思路是:将bitmap列的值按照range划分,不同range的值存储在不同的分桶中,保证了不同分桶的bitmap值是正交的。当查询时,先分别对不同分桶中的正交bitmap进行聚合计算,然后顶层节点直接将聚合计算后的值合并汇总,并输出。如此会大大提高计算效率,解决了顶层单节点计算瓶颈问题。使用场景:符合对bitmap进行正交计算的场景,如在用户行为分析中,计算留存,漏斗,用户画像等。人群圈选:select orthogonal_bitmap_intersect_count(user_id, tag, 13080800, 11110200) from user_tag_bitmap where tag in (13080800, 11110200); 注:13080800、11110200代表用户标签 计算user_id的去重值:select orthogonal_bitmap_union_count(user_id) from user_tag_bitmap where tag in (bitmap交并差集合混合计算:select orthogonal_bitmap_expr_calculate_count(user_id, tag, '(833736|999777)&(1308083|231207)&(1000|20000-30000)') from user_tag_bitmap where tag in (833736,999777,130808,231207,1000,20000,30000); 注:1000、20000、30000等整形tag,代表用户不同标签 select orthogonal_bitmap_expr_calculate_count(user_id, tag, '(A:a/b|B:2\\-4)&(C:1-D:12)&E:23') from user_str_tag_bitmap where tag in ('A:a/b', 'B:2-4', 'C:1', 'D:12', 'E:23'); 注:'A:a/b', 'B:2-4'等是字符串类型tag,代表用户不同标签, 其中'B:2-4'需要转义成'B:2使用步骤:建表,增加hid列,表示bitmap列值id范围, 作为hash分桶列(1)建表建表时需要使用聚合模型,数据类型是 bitmap , 聚合函数是 bitmap_unionCREATE TABLE `user_tag_bitmap` ( `tag` bigint(20) NULL COMMENT "用户标签", `hid` smallint(6) NULL COMMENT "分桶id", `user_id` bitmap BITMAP_UNION NULL COMMENT "" ) ENGINE=OLAP AGGREGATE KEY(`tag`, `hid`) COMMENT "OLAP" DISTRIBUTED BY HASH(`hid`) BUCKETS 3 表schema增加hid列,表示id范围, 作为hash分桶列。注:hid数和BUCKETS要设置合理,hid数设置至少是BUCKETS的5倍以上,以使数据hash分桶尽量均衡。(2)数据导入LOAD LABEL user_tag_bitmap_test ( DATA INFILE('hdfs://abc') INTO TABLE user_tag_bitmap COLUMNS TERMINATED BY ',' (tmp_tag, tmp_user_id) SET ( tag = tmp_tag, hid = ceil(tmp_user_id/5000000), user_id = to_bitmap(tmp_user_id) ) ) 注意:5000000这个数不固定,可按需调整 ... 数据格式:11111111,1 11111112,2 11111113,3 11111114,4 ...注:第一列代表用户标签,由中文转换成数字load数据时,对用户bitmap值range范围纵向切割,例如,用户id在1-5000000范围内的hid值相同,hid值相同的行会分配到一个分桶内,如此每个分桶内到的bitmap都是正交的。可以利用桶内bitmap值正交特性,进行交并集计算,计算结果会被shuffle至top节点聚合。注:正交bitmap函数不能用在分区表,因为分区表分区内正交,分区之间的数据是无法保证正交的,则计算结果也是无法预估的。(3)使用bitmap_orthogonal_intersect:求bitmap交集函数语法:orthogonal_bitmap_intersect(bitmap_column, column_to_filter, filter_values)select BITMAP_COUNT(orthogonal_bitmap_intersect(user_id, tag, 13080800, 11110200)) from user_tag_bitmap where tag in (13080800, 11110200); 参数:第一个参数是Bitmap列,第二个参数是用来过滤的维度列,第三个参数是变长参数,含义是过滤维度列的不同取值。说明:查询规划上聚合分2层,在第一层be节点(update、serialize)先按filter_values为key进行hash聚合,然后对所有key的bitmap求交集,结果序列化后发送至第二层be节点(merge、finalize),在第二层be节点对所有来源于第一层节点的bitmap值循环求并集。orthogonal_bitmap_intersect_count:求bitmap交集count函数,语法同原版intersect_count,但实现不同语法:orthogonal_bitmap_intersect_count(bitmap_column, column_to_filter, filter_values)参数:第一个参数是Bitmap列,第二个参数是用来过滤的维度列,第三个参数开始是变长参数,含义是过滤维度列的不同取值说明:查询规划聚合上分2层,在第一层be节点(update、serialize)先按filter_values为key进行hash聚合,然后对所有key的bitmap求交集,再对交集结果求count,count值序列化后发送至第二层be节点(merge、finalize),在第二层be节点对所有来源于第一层节点的count值循环求sumorthogonal_bitmap_union_count:求bitmap并集count函数,语法同原版bitmap_union_count,但实现不同。语法:orthogonal_bitmap_union_count(bitmap_column)参数:参数类型是bitmap,是待求并集count的列说明:查询规划上分2层,在第一层be节点(update、serialize)对所有bitmap求并集,再对并集的结果bitmap求count,count值序列化后发送至第二层be节点(merge、finalize),在第二层be节点对所有来源于第一层节点的count值循环求sumorthogonal_bitmap_expr_calculate:求表达式bitmap交并差集合计算函数。语法:orthogonal_bitmap_expr_calculate(bitmap_column, filter_column, input_string)参数:第一个参数是Bitmap列,第二个参数是用来过滤的维度列,即计算的key列,第三个参数是计算表达式字符串,含义是依据key列进行bitmap交并差集表达式计算。表达式支持的计算符:& 代表交集计算,| 代表并集计算,- 代表差集计算, ^ 代表异或计算,\ 代表转义字符说明:查询规划上聚合分2层,第一层be聚合节点计算包括init、update、serialize步骤,第二层be聚合节点计算包括merge、finalize步骤。在第一层be节点,init阶段解析input_string字符串,转换为后缀表达式(逆波兰式),解析出计算key值,并在map<key, bitmap>结构中初始化;update阶段,底层内核scan维度列(filter_column)数据后回调update函数,然后以计算key为单位对上一步的map结构中的bitmap进行聚合;serialize阶段,根据后缀表达式,解析出计算key列的bitmap,利用栈结构先进后出原则,进行bitmap交并差集合计算,然后对最终的结果bitmap序列化后发送至第二层聚合be节点。在第二层聚合be节点,对所有来源于第一层节点的bitmap值求并集,并返回最终bitmap结果orthogonal_bitmap_expr_calculate_count:求表达式bitmap交并差集合计算count函数, 语法和参数同orthogonal_bitmap_expr_calculate。语法:orthogonal_bitmap_expr_calculate_count(bitmap_column, filter_column, input_string)说明:查询规划上聚合分2层,第一层be聚合节点计算包括init、update、serialize步骤,第二层be聚合节点计算包括merge、finalize步骤。在第一层be节点,init阶段解析input_string字符串,转换为后缀表达式(逆波兰式),解析出计算key值,并在map<key, bitmap>结构中初始化;update阶段,底层内核scan维度列(filter_column)数据后回调update函数,然后以计算key为单位对上一步的map结构中的bitmap进行聚合;serialize阶段,根据后缀表达式,解析出计算key列的bitmap,利用栈结构先进后出原则,进行bitmap交并差集合计算,然后对最终的结果bitmap的count值序列化后发送至第二层聚合be节点。在第二层聚合be节点,对所有来源于第一层节点的count值求加和,并返回最终count结果。SQL 函数(1)查看函数名:show builtin functions in test_db;(2)查看函数具体信息,比如查看 year 函数具体信息:show full builtin functions in test_db like 'year';官网:https://doris.apache.org/zh-CN/sql-reference/sql-functions/date-time-functions/convert_tz.html支持自定义的UDF和UDAF函数,具体可看官网。Laterval view 语法通 过 Lateral View 语 法 , 我 们 可 以 使 用 explod_bitmap 、 explode_split 、explode_jaon_array 等 Table Function 表函数,将 bitmap、String 或 Json Array 由一列展开成多行,以便后续可以对展开的数据进行进一步处理(如 Filter、Join 等)。创建测试表:CREATE TABLE test3 (k1 INT,k2 varchar(30)) DISTRIBUTED BY HASH (k1) BUCKETS 2 PROPERTIES("replication_num" = "1"); INSERT INTO test3 VALUES (1,''), (2,null), (3,','), (4,'1'),(5,'1,2,3'),(6,'a,b,c'); 设置参数开启set enable_lateral_view=true;explode_bitmap:展开一个 bitmap 类型select k1, e1 from test3 lateral view explode_bitmap(bitmap_from_string("1")) tmp1explode_split:将一个字符串按指定的分隔符分割成多个子串select k1, e1 from test3 lateral view explode_split(k2, ',') tmp1 as e1 order by k1, e1;explode_json_array:展开一个 json 数组select k1, e1 from test3 lateral view explode_json_array_int('[1,2,3]') tmp1 as e1 order by k1, e1; select k1, e1 from test3 lateral view explode_json_array_double('[1.0,2.0,3.0]') tmp1 as e1 order by k1, e1; select k1, e1 from test3 lateral view explode_json_array_string('[1,"b",3]') tmp1 as e1 order by k1, e1;
0
0
0
浏览量529
攻城狮无远

【Databand】日期时间函数

获取当前日期和时间Databend 使用 UTC 作为默认时区,并允许您将时区更改为当前地理位置。-- 查看时区 select timezone(); +-----------------+ | timezone() | +-----------------+ | UTC | +-----------------+ -- 修改时区 set timezone='asia/shanghai';Databend 常见的获取当前日期和时间函数如下:now() 返回 timestamp 数据类型,以“YYYY-MM-DD hh:mm:ss.fff”格式返回当前日期和时间。today() 返回 date 数据类型,以“YYYY-MM-DD”格式返回当前日期。yesterday() 返回 date 数据类型,以“YYYY-MM-DD”格式返回昨天日期,与 today() - 1 相同。tomorrow() 返回 date 数据类型,以“YYYY-MM-DD”格式返回明天日期,与 today() + 1 相同。select now(),today(),yesterday(),tomorrow(),today()+1 as tomorrow; +-------------------------+-------------+--------------+-------------+------------+ | now() | today() | yesterday() | tomorrow() | tomorrow | +-------------------------+-------------+--------------+-------------+------------+ | 2024-01-08 22:19:55.188 | 2024-01-08 | 2024-01-07 | 2024-01-09 | 2024-01-09 | +-------------------------+-------------+--------------+-------------+------------+日期格式化函数使用 to_date(expr[,format_text]) 可以将表达式转化为指定日期格式,转化为“YYYY-MM-DD”格式。如果给定两个参数,该函数会根据第二个字符串中指定的格式将第一个字符串转换为日期。语法和示例如下:-- 语法 to_date(expr[,format_text]) -- 示列 select to_date('2023-12-13') as dt;-- 转化日期成功 select to_date('20231213') as dt;-- 报错,不能转化,格式不对 select to_date('20231213','%Y%m%d') as dt;-- 转化日期成功 select to_date('2023/12/13','%Y/%m/%d') as dt;-- 同理,需要指定格式才能转化日期成功 select to_date(null) as dt;-- 输出 NULL,但是不建议日期显示 NULL,因为会存在问题 select to_date(ifnull(null,1)) as dt;-- 优化后,输出 1970-01-02, -- 如果同一列中日期存在多种格式怎么处理? with t as (select '2023-12-13' as dt union all select '2023/12/13' as dt union all select '20231213' as dt union all select '2023/12/13 00:00:00' as dt union all select null as dt) select dt, case when length(dt) = 8 then to_date(dt, '%Y%m%d') when length(dt) = 10 and dt like '%/%' then to_date(dt, '%Y/%m/%d') when length(dt) = 10 and dt like '%-%' then to_date(dt, '%Y-%m-%d') when length(dt) > 10 and dt like '%/%' then to_date(dt, '%Y/%m/%d %H:%M:%S') else to_date(ifnull(dt, 1)) end as d_std from t; +---------------------+------------+ | dt | dt_std | +---------------------+------------+ | NULL | 1970-01-02 | +---------------------+------------+ | 2023/12/13 00:00:00 | 2023-12-13 | +---------------------+------------+ | 20231213 | 2023-12-13 | +---------------------+------------+ | 2023/12/13 | 2023-12-13 | +---------------------+------------+ | 2023-12-13 | 1970-01-02 | +---------------------+------------+上面存在多种格式处理过程中,如果对 NULL 不进行处理,可能会得不出来结果,或者得出的结果显示错误,如下图:由此可见,Databend 日期处理函数语法和 Mysql 差异还是很大的,对于 Mysql 上面不管任何格式的日期,只要 date() 函数就能统一规范处理,而 Databend 则要针对不同的格式写不同的处理方式,这说明 Databend 对数据类型要求非常严格,在任何时候,不管是数据接入人员,亦或是数据开发人员,在建表过程中,设计字段都应指定准确的数据类型。使用 date_formt() 函数也可以格式化日期,但是只能格式化数据类型为date的表达式,即将日期值转换为特定的字符串格式。语法和示例如下:-- 语法 date_format(<date>, <format>) -- 示例 select date_format('20231213','%Y/%m/%d');-- 报错,原因是20231213不是日期类型! select date_format(to_date('20231213','%Y%m%d'),'%Y/%m/%d');-- 输出 2023/12/13日期加减运算使用 date_add() 函数对日期进行添加运算,返回与<date_or_time_expr>参数类型相同的值。语法:date_add(<unit>, <value>, <date_or_time_expr>)参数解释如下::必须具有以下值 year、quarter、month、day、hour、minute 和 second。:添加的时间单位数,value 可以为负数,相当于 date_sub() 函数。<date_or_time_expr>:date 或 timestamp 类型的值。使用 date_sub() 函数对日期进行减少运算,返回与 <date_or_time_expr> 参数类型相同的值。语法参数和 date_add() 函数一致。数据示例:select date_add(year, -1, now()) as up_year_time , date_add(day, 1, today()) as up_day , date_add(day, -1, today()) as down_day , date_sub(day, 1, today()) as down_day , date_sub(day, -1, today()) as up_day ; +--------------------------+------------+------------+------------+------------+ | up_year_time | up_day | down_day | down_day | up_day | +--------------------------+------------+------------+------------+------------+ | 2023-01-09 07:29:00.980 | 2024-01-09 | 2024-01-07 | 2024-01-07 | 2024-01-09 | +--------------------------+------------+------------+------------+------------+根据定义和示例,date_add() 和 date_sub() 函数掌握使用一个即可。使用 date_trunc() 函数将日期、时间或时间戳值截断到指定的精度。语法:date_trunc(<precision>, <date_or_time_expr>)参数解释如下:<precision>:必须具有以下值 year、quarter、month、day、hour、minute 和 second。<date_or_time_expr>:date 或 timestamp 类型的值。数据示例:select today() as dt , date_trunc(month, today()) as cur_month_begin_dt , date_add(month, 1, date_trunc(month, today()) - 1) as cur_month_end_dt ; +------------+--------------------+------------------+ | dt | cur_month_begin_dt | cur_month_end_dt | +------------+--------------------+------------------+ | 2024-01-08 | 2024-01-01 | 2024-01-31 | +------------+--------------------+------------------+通过 date_trunc() 和 date_add() 函数搭配使用,可以很好地计算出月初和月末。日期时间和时间戳转化使用 to_timestamp() 函数返回格式为“YYYY-MM-DD hh:mm:ss.fff”格式的时间戳。如果给定的字符串与此格式匹配,但没有时间部分,则会自动扩展到此模式。填充值为0。如果给定Unix时间戳,也会转化为日期时间格式。select now(),to_timestamp(now());-- 报错,原因是 to_timestamp() 只对字符串转化!!! select now(),to_timestamp(now()::vrchar) as t1,to_timestamp(1) as t2; +-------------------------+-------------------------+-------------------------+ | now() | t1 | t2 | +-------------------------+-------------------------+-------------------------+ | 2024-01-09 10:32:07.783 | 2024-01-09 10:32:07.783 | 1970-01-01 08:00:01.000 | +-------------------------+-------------------------+-------------------------+使用 to_unix_timestamp() 函数将日期/时间格式的时间戳转换为Unix时间戳格式。Unix时间戳表示自 1970年1月1日00:00:00 UTC 以来经过的秒数。select now(),to_unix_timestamp(now()) as unix,to_timestamp(1704767748) as t1; +-------------------------+------------+-------------------------+ | now() | unix | t2 | +-------------------------+------------+-------------------------+ | 2024-01-09 10:38:12.318 | 1704767892 | 2024-01-09 10:35:48.000 | +-------------------------+------------+-------------------------+日期时间各部分拆分使用 extract() 都可以检索出日期、时间或时间戳的指定部分,语法类似如下:extract( year | quarter | month | week | day | hour | minute | second | dow | doy from <date_or_time_expr> )其中,dow 表示一周的一天,1表示周一,7表示周日。doy 表示一年中的第几天。除了上面两个函数外,Databend 还有一些函数也能实现效果,如下:to_year():将带有时间(时间戳/日期时间)的日期或日期转换为年份数字。to_month():将带有时间(时间戳/日期时间)的日期或日期转换为月份数字。to_quarter():将带有时间(时间戳/日期时间)的日期或日期转换为季度数字。to_week_of_year():计算给定日期一年内的周数。to_day_of_month():将带有时间的日期或日期(时间戳/日期时间)转换为包含月份天数(1-31)的UInt8数字。to_day_of_week():将带有时间的日期或日期(时间戳/日期时间)转换为包含一周中日数的UInt8数字(周一为1,周日为7)。to_day_of_year():将日期或带有时间的日期(时间戳/日期时间)转换为包含一年中一天数字的UInt16数字(1-366)。to_hour():将带有时间(时间戳/日期时间)的日期转换为包含24小时时间(0-23)小时数的UInt8数字。to_minute():将带有时间的日期(时间戳/日期时间)转换为包含小时分钟数(0-59)的UInt8数字。to_second():将带有时间(时间戳/日期时间)的日期转换为包含分钟中秒数(0-59)的UInt8数字。数据示例:select now() as now_dt , extract(year from now()) as year_dt -- ,extract(quarter from now()) -- ,extract(week from now()) -- ,to_week_of_year(now()) , extract(doy from now()) as day_of_year1 , to_day_of_year(now()) as day_of_year2 , extract(day from now()) as day_of_month , to_day_of_month(now()) as day_of_month2 , extract(dow from now()) as day_of_week1 , to_day_of_week(now()) as day_of_week2 , extract(hour from now()) as hour_dt , extract(minute from now()) as minute_dt , extract(second from now()) as second_dt ;其中,被注释掉的官网虽然有函数语法,但是实际应用还不支持,但并不影响。Databend 还有些其他函数扩展,可能有时候能用上。to_yyyymm():将带有时间的日期或日期(时间戳/日期时间)转换为包含年份和月份编号的UInt32编号。to_yyyymmdd():将带有时间(时间戳/日期时间)的日期或日期转换为包含年份和月份编号(YYYY 10000 + MM 100 + DD)的UInt32数字。to_yyyymmddhhmmss():将带有时间(时间戳/日期时间)的日期或日期转换为包含年份和月份编号的UInt64数字(YYYY 10000000000 + MM 100000000 + DD 1000000 + hh 10000 + mm * 100 + ss)。日期时间加减运算前面介绍了 date_add() 和 date_sub() 函数,也可以使用他们对日期时间做加减运算。除此之外,Databend 有新的函数也可以适用,将时间间隔添加到日期或时间戳中,返回日期或时间戳类型的结果。add_years()add_quarters()add_months()add_days()add_hours()add_minutes()add_seconds()select add_years(now(), -1) , add_quarters(now(), 1) , add_months(now(), 1) , add_days(now(), 1) , add_hours(now(), 1) , add_minutes(now(), 1) , add_seconds(now(), 1) , date_add(year, 1, now()) , date_add(quarter, 1, now()) , date_add(month, 1, now()) , date_add(day, 1, now()) , date_add(hour, 1, now()) , date_add(minute, 1, now()) , date_add(second, 1, now()) ;根据示例可知,添加时间单位数可正数也可负数,其实 Databend 远不止这些函数,还有很多,但是我们只要选择最常用 date_add() 函数就能解决所有相关日期时间加减运算问题。实际应用扩展利用当前时间,自动生成文件名,减少手动书写带来的错误。select concat('guanfa_wang_',LEFT(to_yyyymmddhhmmss(add_minutes(now(),15))::varchar,12),'.sql') as file_name; +------------------------------+ | file_name | +------------------------------+ | guanfa_wang_202401091336.sql | +------------------------------+计算时间间隔。select cur_create_time , create_time , (to_unix_timestamp(cur_create_time) - to_unix_timestamp(create_time)) as second_gap , round((to_unix_timestamp(now()) - to_unix_timestamp(create_time)) / 60, 0) as minute_gap , round((to_unix_timestamp(now()) - to_unix_timestamp(create_time)) / 60 / 60, 0) as hour_gap from (select now() as cur_create_time , date_add(hour, -8, now()) as create_time) as t1; +-------------------------+-------------------------+------------+------------+------------+ | cur_create_time | create_time | second_gap | minute_gap | hour_gap | +-------------------------+-------------------------+------------+------------+------------+ | 2024-01-09 13:37:12.408 | 2024-01-09 05:37:12.408 | 28800 | 480 | 8 | +-------------------------+-------------------------+------------+------------+------------+总结本文基本覆盖了数据库中所有关于日期时间类计算,学习掌握常用的几种即可,函数都是定义出来的,主要是多去思考逻辑。如果你觉得掌握了以上函数应用,感兴趣的话可以操作实践一下 Databend 生成日期表,可参考 MySQL 日期表制作。参考资料:Databend Date & Time Functions:https://databend.rs/sql/sql-functions/datetime-functions/
0
0
0
浏览量513
攻城狮无远

【Databend】基础函数应用

数值函数使用频率较高的数值函数如下:abs(x):参数x的绝对值。ceil(x):参数x向上取整。floor(x):参数x向下取整。rand([n]):生成 [0,1)的浮点数。round(x,d):将参数x四舍五入到d小数位。truncate(x,d):返回数字x,截断为d小数位。使用示例:select ceil(-1.23) , floor(1.23) , rand() , rand(1) , round(0.123, 2) , truncate(1.223, 1); +-----------+-----------+------------------+------------------+---------------+-------------------+ |ceil(-1.23)|floor(1.23)| rand() | rand(1) |round(0.123, 2)|truncate(1.223, 1) | +-----------+-----------+------------------+------------------+---------------+-------------------+ | -1.0 | 1 |0.4990247756570755|0.7133693869548766| 0.12 | 1.2 | +-----------+-----------+------------------+------------------+---------------+-------------------+ 字符串函数length(str):以字节为单位返回字符串的长度。char_length(str):返回字符串str的长度,以字符为单位。多字节字符算作单个字符。这意味着对于包含五个2字节字符的字符串,length()返回10,而char_length()返回5。lower(str):将所有字符更改为小写。upper(str):将所有字符更改为大写。left(str,len):返回字符串 str 中最左边的 len 字符,如果任何参数是 null,则返回 null。right(str,len):返回字符串 str 中最右边的 len 字符,如果任何参数是 null,则返回 null。select length('databend') as len , char_length('databend') as char_len , lower('Databend') as lower_char , upper('Databend') as upper_char , left('Databend', 4) as left_char , right('Databend', 4) as right_char; +-------+----------+------------+------------+-----------+------------+ | len | char_len | lower_char | upper_char | left_char | right_char | +-------+----------+------------+------------+-----------+------------+ | 8 | 8 | databend | DATABEND | Data | bend | +-------+----------+------------+------------+-----------+------------+ trim([{both | leading | trailing} [remstr] from ] str):指定删除字符串的前导或尾随。如果省略删除字符串,则删除空格。repeat(str, count):重复字符串 str count 次数。lpad(str,len,padstr):左填充字符串 padstr 为长度为 len 个字符。如果str长于len,则返回值将缩短为len字符。rpad(str,len,padstr):右填充字符串padstr为len字符的长度。如果str长于len,则返回值将缩短为len字符。select trim(leading '?' from '???Databend???') as leading_trim , trim(trailing '?' from '???Databend???') as trailing_trim , trim(both '?' from '???Databend???') as both_trim , trim(' Databend ') as spaces_trim , repeat('Databend', 2) as repeat_char , lpad('36363', 10, '0') as lpad_char , lpad('36363', 10, '?') as rpad_char; +-------------+-------------+-----------+-------------+------------------+------------+------------+ |leading_trim |trailing_trim| both_trim | spaces_trim | repeat_char | lpad_char | rpad_char | +-------------+-------------+-----------+-------------+------------------+------------+------------+ | Databend??? | ???Databend | Databend | Databend | DatabendDatabend | 0000036363 | ?????36363 | +-------------+-------------+-----------+-------------+------------------+------------+------------+locate(substr, str, pos):返回 substr 子字符串在字符串 str 中从 pos 开始的第几个位置,pos 不写为0首次。substring(str,pos,len):从 pos 位置开始,返回字符串 str 中 len 个长度字符,默认为最长长度。inster(str,pos,len,new_str):子字符串从位置pos开始,len字符长被字符串newstr取代。replace(str,from_str,to_str):将字符串from_str的所有出现都替换为字符串to_str。select locate(' ', 'Databend Clound') as pos_char , substring('Databend Clound', locate(' ', 'Databend Clound')) as sub_char , insert('Databend Clound', length('Databend Clound'), 7, ' Server') as insert_char , replace('Databend Clound', 'Databend', 'Mysql') as replace_char1 , replace('Databend Clound', 'Databend', '') as replace_char2 , replace('www.mysql.com', 'mysql', '') as replace_char3; +----------+----------+-----------------------+---------------+---------------+---------------+ | pos_char | sub_char | insert_char | replace_char1 | replace_char2 | replace_char3 | +----------+----------+-----------------------+---------------+---------------+---------------+ | 9 | Clound | Databend Cloun Server | Mysqld Clound | d Clound | www.l.com | +----------+----------+-----------------------+---------------+---------------+---------------+从最后几列可以看出,Databend 和 Mysql 替换过程中始终不能完整替换,因此在使用过程中,多去测试看结果再实际应用。另外,Databend 不支持 Mysql 中的 substring_index()函数,但是可以发散思维,比如上面的 sub_char 实现,还有以下方法实现:select substring('Databend Clound',1,locate(' ','Databend Clound')-1) as sub_char; +------------+ | sub_char | +------------+ | Databend | +------------+concat(str1,str2,…):合并拼接字符串,返回varchar 数据类型值或 null 数据类型。concat(separator,str1,str2,…):根据分隔符 separator 合并字符串,返回varchar 数据类型值或 null 数据类型。select concat('data', 'bend') as concat_char1 , concat('data', 'bend', null) as concat_char2 , concat('data', 'bend', 1::varchar) as concat_char3 , concat_ws('、', 'data', 'bend') as concat_char4; +--------------+--------------+---------------+--------------+ | concat_char1 | concat_char2 | concat_char3 | concat_char4 | +--------------+--------------+---------------+--------------+ | databend | NULL | databend1 | data、bend | +--------------+--------------+---------------+--------------+逻辑函数ifnull(expr1,expr2):如果不是NULL,则返回expr1。否则返回expr2。它们必须具有相同的数据类型。greatest(values1,values2):从一组值中返回最大值。least((values1,values2):从一组值中返回最小值。if(cond1, expr1, [cond2, expr2, …], expr_else):如果cond1为TRUE,则返回expr1。否则,如果cond2为TRUE,则返回expr2,coalesce(x,…):从左到右检查是否传递了 null 参数,并返回第一个非 null 参数。select ifnull(null, 'a') as t1 , greatest(2, 3) as t2 , least(2, 3, 4) as t3 , if(1 > 2, false, true) as t4 , coalesce(null, 'Databend', 'Mysql', null) as t5 ;JSON 函数对于这类函数,主要针对一些特殊的数据类型 variant ,可以前往【Databend】数据类型查看说明。parse_json(str):将输入字符串解释为 json 文档,生成一个 variant 值。如果解析过程中发生错误,则返回NULL值。object_keys(variant):返回一个数组,其中包含输入变体对象中的键列表。get_path(variant, path_name):通过 path_name 从 variant 中提取值。如果其中一个参数是null则该值作为 variant 或 null 返回,path_name由字段名之前有句号(.)、冒号(:)或索引运算符([index])的串联组成。第一个字段名称不需要指定前导标识符。get(variant,index|field_name):根据 index 或 field_name 获取具体值,未获得返回 null。select parse_json('[-1, 12, 289, 2188, false]') as list_json , parse_json('{ "x" : "abc", "y" : false, "z": 10} ') as key_json , object_keys(parse_json('{"a": 1, "b": [1,2,3]}')) as get_keys , get_path(parse_json('{"k1":[0,1,2], "k2":{"k3":3,"k4":4}}'), 'k1[0]') as k1 , get_path(parse_json('{"k1":[0,1,2], "k2":{"k3":3,"k4":4}}'), 'k2:k3') as k3 , get_path(parse_json('{"k1":[0,1,2], "k2":{"k3":3,"k4":4}}'), 'k2.k4') as k4 , get_path(parse_json('{"k1":[0,1,2], "k2":{"k3":3,"k4":4}}'), 'k2.k5') as k5 , get(parse_json('[2.71, 3.14]'), 0) as get_list_value , get(parse_json('{"aa":1, "aa":2, "aa":3}'), 'aa') as get_key_values , get(parse_json('{"aa":1, "aa":2, "aa":3}'), 'aa') as get_null ; +------------------------+------------------------------+-----------+---+---+---+------+--------------+--------------+--------+ | list_json | key_json | get_keys | k1| k3| k4| k5 |get_list_value|get_key_values|get_null| +------------------------+------------------------------+-----------+---+---+---+------+--------------+--------------+--------+ | [-1,12,289,2188,false] | {"x":"abc","y":false,"z":10} | ["a","b"] | 0 | 3 | 4 | NULL | 2.71 | 1 | NULL | +------------------------+------------------------------+-----------+---+---+---+------+--------------+--------------+--------+json_path_query_array(variant, path_name):获取指定 variant 值的 path_name 路径返回的所有 json 项,并将结果包装成数组。json_path_query(variant, path_name):获取指定 variant 值的 path_name 路径返回的所有 json 项。with t1 as (select 'laptop' as name, '{"brand": "dell", "colors": ["black", "silver"], "price": 1200, "features": {"ram": "16gb", "storage": "512gb"}}' as details union all select 'smartphone' as name, '{"brand": "apple", "colors": ["white", "black"], "price": 999, "features": {"ram": "4gb", "storage": "128gb"}}' as details union all select 'headphones' as name, '{"brand": "sony", "colors": ["black", "blue", "red"], "price": 150, "features": {"battery": "20h", "bluetooth": "5.0"}}' as details) select name, json_path_query_array(parse_json(details), '$.features.*') as all_features from t1; +-----------+---------------------+ | name | all_features | +-----------+---------------------+ | Laptop | ["16GB", "512GB"] | +-----------+---------------------+ | Smartphone| ["4GB", "128GB"] | +-----------+---------------------+ | Headphones| ["20h", "5.0"] | +-----------+---------------------+ with t1 as (select 'laptop' as name, '{"brand": "dell", "colors": ["black", "silver"], "price": 1200, "features": {"ram": "16gb", "storage": "512gb"}}' as details union all select 'smartphone' as name, '{"brand": "apple", "colors": ["white", "black"], "price": 999, "features": {"ram": "4gb", "storage": "128gb"}}' as details union all select 'headphones' as name, '{"brand": "sony", "colors": ["black", "blue", "red"], "price": 150, "features": {"battery": "20h", "bluetooth": "5.0"}}' as details) select name, json_path_query(parse_json(details), '$.features.*') as all_features from t1; +------------+--------------+ | name | all_features | +------------+--------------+ | Laptop | "16GB" | | Laptop | "512GB" | | Smartphone | "4GB" | | Smartphone | "128GB" | | Headphones | "20h" | | Headphones | "5.0" | +------------+--------------+聚合函数主要介绍常用的几种,如下:avg(expr):返回expr的平均值。max(expr):返回expr的最大值。min(expr):返回expr的最小值。sum(expr):返回expr的汇总值。count([distinct] expr):返回expr的记录数。median(expr):计算数字数据序列的中位数。string_agg(expr [, delimiter]):将列的所有非null 值转换为 string,由分隔符分隔。-- 数据准备 create table if not exists program_languages ( id int, language_name varchar, score int ); insert into program_languages (id, language_name,score) values (1, 'python',80), (2, 'javascript',90), (3, 'java',75), (4, 'c#',95), (5, 'ruby',85); -- 指标计算 select avg(score) as avg_score , max(score) as max_score , min(score) as min_score , median(score) as median_score , sum(score) as total_score , count(distinct language_name) as language_cnt , string_agg(language_name, '、') as language_concat from program_languages; +-----------+-----------+-----------+--------------+-------------+--------------+-----------------------------------+ | avg_score | max_score | min_score | median_score | total_score | language_cnt | language_concat | +-----------+-----------+-----------+--------------+-------------+--------------+-----------------------------------+ | 85 | 95 | 75 | 85 | 425 | 5 | python、javascript、java、c#、ruby | +-----------+-----------+-----------+--------------+-------------+--------------+-----------------------------------+以上这些聚合函数 null 值都不计算在内。聚合函数 Databend 比 Mysql 多了一个 median() 可用于更好地计算中位数,其实还有一些其他聚合函数,有兴趣的可以自己扩展。总结Databend 作为新一代云原生数据仓库,提供了许多多样化的函数,函数与 Mysql 相比,并无较大差异,我们只要掌握基础常用哪些函数,基本上可以解决工作中大部分问题。参考资料:Databend Functions:https://docs.databend.com/sql/sql-functions/
0
0
0
浏览量657
攻城狮无远

【Databend】数据库和表操作

数据库操作基本语法:-- 创建数据库 create database if not exists database_name; -- 查看创建数据库语句 show create database database_name; -- 使用数据库 use database_name; -- 重命名数据库 alter database if exists old_database_name rename to new_database_name; -- 查看所有数据库 show databases [like '<pattern>' | where <expr>]; -- 删除数据库 drop database if exists database_name;示例:-- 创建数据库 create database if not exists test; -- 查看创建数据库语句 show create database test; -- 使用数据库 use test; -- 重命名数据库 alter database if exists test rename to test_db; -- 查看所有数据库 show databases [like '<pattern>' | where <expr>]; -- 删除数据库 drop database if exists test;SQL标识符:未引用和双引号的标识符未引用的标识符以字母(A-Z、a-z)或下划线(“_”)开头,可能由字母、下划线、数字(0-9)或美元符号(“$”)组成。双引号标识符可以包括广泛的字符,如数字(0-9)、特殊字符(如句号(.)、单引号(')、感叹号(!)、符号(@)、数字符号(#)、美元符号($)、百分比符号(%)、插入符号(^)和记号名称(&)、扩展 ASCII 和非 ASCII 字符以及空格。Databend 允许您控制标识符的外壳敏感性。unquoted_ident_case_sensitive:设置为1时,此选项保留未引用标识符的字符大小写,确保它们区分大小写。如果保留在默认值0,未引用的标识符将保持不区分大小写,转换为小写。quoted_ident_case_sensitive:通过将此选项设置为0,您可以指示双引号标识符不应保留字符的大小写,使其不区分大小写。双反引号(``)或双引号(")是等价的。数据表操作创建表基础语法:-- 常规创建 create [transient] table [if not exists] [db.]table_name ( <column_name> <data_type> [ not null | null] [ { default <expr> }] [as (<expr>) stored | virtual], <column_name> <data_type> [ not null | null] [ { default <expr> }] [as (<expr>) stored | virtual], ... ); -- 创建一个与现有表具有相同列定义的表 create table [if not exists] [db.]table_name like [db.]origin_table_name; -- 通过查询结果创建表 create table [if not exists] [db.]table_name as select query_sql;其中,transient 表示临时即创建临时表,stored 表示存储计算列,占用存储空间,virtual 表示虚拟计算列,不占用存储空间,访问时实时计算,类似于可视化工具 Power BI 和 Tableau 的度量值。创建存储计算列的示例:每当“价格”或“数量”列的值更新时,“total_price”列将自动重新计算并更新其存储值。create table if not exists products ( id int, price float64, quantity int, total_price float64 as (price * quantity) stored );创建虚拟计算列的示例:“full_name”列是根据“first_name”和“last_name”列的当前值动态计算的。它不会占用额外的存储空间。每当访问“first_name”或“last_name”值时,将计算并返回“full_name”列。create table if not exists employees ( id int, first_name varchar, last_name varchar, full_name varchar as (concat(first_name, ' ', last_name)) virtual );在存储计算列和虚拟计算列之间进行选择时,请考虑以下因素:存储空间:存储的计算列在表中占用了额外的存储空间,因为它们的计算值是物理存储的。如果您的数据库空间有限或希望最大限度地减少存储使用量,虚拟计算列可能是更好的选择。实时更新:存储的计算列在更新依赖列时立即更新其计算值。这可以确保您在查询时始终拥有最新的计算值。另一方面,虚拟计算列在查询期间动态计算其值,这可能会略微增加处理时间。数据完整性和一致性:存储的计算列保持即时的数据一致性,因为它们的计算值在写入操作时会更新。然而,虚拟计算列在查询期间实时计算其值,这意味着写入操作和后续查询之间可能存在短暂的不一致。创建复制表示例:根据已有表复制。create table if not exists employees_bak1 like employees; create table if not exists employees_bak2 as select * from employees where 1=2;-- 复制表结构 create table if not exists employees_bak2 as select * from employees where 1=1;-- 复制表结构,并且克隆数据创建临时表示例:一定时间或断开连接后自动删除。-- 创建临时表 create transient table visits ( visitor_id bigint ); -- 录入数据 insert into visits values(1); insert into visits values(2); insert into visits values(3); -- 查看数据 select * from visits; +-----------+ | visitor_id | +-----------+ | 1 | | 2 | | 3 | +-----------+删除表和恢复表清空表语法和示例:truncate table [db.]table_name; truncate table test;删除表基本语法:drop table [if exists] [<database_name>.]<table_name> [all]可选的“all”参数决定是否删除表的底层数据。如果省略“all”,则仅从元数据服务中删除表模式,使数据保持不变。在这种情况下,您可以使用 undrop table 命令恢复表。包括“all”将导致模式和基础数据的删除。虽然 undrop table 命令可以恢复模式,但它无法恢复表的数据。恢复表使用示例:-- 创建表 create table test(a int, b varchar); -- 录入数据 insert into test (a, b) values (1, 'example'); -- 删除表 drop table if exists test; -- 查看数据 select * from test; +---+-------+ | a | b | +---+-------+ | 1 |example| +---+-------+ -- 恢复表 undrop table test; -- 查看恢复的数据 select * from test; +---+-------+ | a | b | +---+-------+ | 1 |example| +---+-------+查看表查看所有或当前指定条件数据库中的表基本语法:show [full] tables [{from | in} <database_name>] [history] [like '<pattern>' | where <expr>];示例:-- 查看当前数据库下的表 show tables; -- 查看所有表 show full tables; -- 查看当前数据库下的表,结果将包括仍在保留期内(默认为24小时)的被删除的表。 show tables history; -- 查看指定条件的表 show tables like '%time'; show tables where data_size > 1000;查看创建表的语法和示例:show create table [database.]table_name; -- 示例 show create table test;查看当前或指定数据库中被删除的表语法和示例:show drop tables [from <database_name>] [like '<pattern>' | where <expr>]; show drop tables from default_db;查看表中列的信息语法和示例:show fields from [<database_name>.]<table_name>; desc [<database_name>.]<table_name>; show [full] columns {from | in} tbl_name [{from | in} db_name] [like '<pattern>' | where <expr>]; -- 示例 show columns from books from default;修改表常见修改表的语法:-- 重命名表 alter table [if exists] <name> rename to <new_table_name>; -- 添加列到指定位置 alter table [if exists] [database.]<table_name> add column <column_name> <data_type> [not null | null] [default <constant_value>] [first | after <column_name>] -- 添加计算列 alter table [if exists] [database.]<table_name> add column <column_name> <data_type> as (<expr>) stored | virtual; -- 将计算列转换为常规列 alter table [if exists] [database.]<table_name> modify column <column_name> drop stored; -- 重命名列 alter table [if exists] [database.]<table_name> rename column <column_name> to <new_column_name>; -- 修改一列或多列数据类型 alter table [if exists] [database.]<table_name> modify column <column_name> <new_data_type> [default <constant_value>][, column <column_name> <new_data_type> [default <constant_value>], ...]示例:-- 选择数据库 use test_db; -- 创建表 create table if not exists employees ( first_name varchar, last_name varchar, full_name varchar as (concat(first_name, ' ', last_name)) virtual ); -- 重命名表名 alter table if exists employees rename to dim_employees; -- 添加列 alter table dim_employees add column id int not null first;-- 位置为起始 alter table dim_employees add column email varchar null after last_name;-- 位置在其它列之后 alter table dim_employees add column age int after last_name; -- 重命名列 alter table dim_employees rename column email to new_email; -- 修改列 alter table products modify column full_name drop virtual,column age varchar(10) default '0'; -- 删除列 alter table dim_employees drop column new_email;总结Databend 的数据库和数据表操作跟简单的英语句子一样,特别好理解,与 Mysql 中的语法类似,只是有一些差异。业务可能随时发生调整,表结构操作语法还是要会,需要多练。参考资料:Databend DDL:https://databend.rs/sql/sql-commands/ddl/table/
0
0
0
浏览量515
攻城狮无远

【Databend】行列转化:数据透视和逆透视

数据准备学生学科得分等级测试数据如下:drop table if exists fact_suject_data; create table if not exists fact_suject_data ( student_id int null comment '编号', subject_level varchar null comment '科目等级', subject_level_json variant null comment '科目等级json数据' ); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (12,'china e,english d,math e','{"china": "e","english": "d","math": "e"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (2,'china b,english b','{"china": "b","english": "b"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (3,'english a,math c','{"english": "a","math": "c"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (4,'china c,math a','{"china": "c","math": "a"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (5,'china d,english a,math c','{"china": "d","english": "a","math": "c"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (6,'china c,english a,math d','{"china": "c","english": "a","math": "d"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (7,'china a,english e,math b','{"china": "a","english": "e","math": "b"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (8,'china d,english e,math e','{"china": "d","english": "e","math": "e"}'); insert into fact_suject_data(student_id, subject_level,subject_level_json) values (9,'china c,english e,math c','{"china": "c","english": "e","math": "c"}');利用上一篇 【Databend】行列转化:一行变多行和简单分列 文章一行变多行,得到如下效果数据:select t1.student_id , t1.subject_level , split_part(unnest(split(t1.subject_level, ',')), ' ', 1) as subject , split_part(unnest(split(t1.subject_level, ',')), ' ', 2) as level1 from fact_suject_data as t1 order by t1.student_id;数据透视Databend 中的 pivot 功能可以轻松实现数据透视,使用语法如下:select ... from ... pivot ( <aggregate_function> ( <pivot_column> ) for <value_column> in ( <pivot_value_1> [ , <pivot_value_2> ... ] ) ) [ ... ]参数解释如下:<aggregate_function>:用于组合来自 <pivot_column> 的分组值的聚合函数。<pivot_column>:将使用指定的 <aggregate_function> 聚合的列。<value_column>:其唯一值将成为数据透视结果集中的新列。<pivot_value_N>:来自<value_column>的唯一值,将成为透视结果集中的新列。with a as (select t1.student_id , t1.subject_level , split_part(unnest(split(t1.subject_level, ',')), ' ', 1) as subject , split_part(unnest(split(t1.subject_level, ',')), ' ', 2) as level1 from fact_suject_data as t1 order by t1.student_id) select * from a pivot (max(level1) for subject in ('china','math','english'));数据逆透视Databend 中 unpivot 功能通过将列转换为行,起到数据逆透视效果。它是一个关系运算符,接受两列(来自表或子查询)以及列列表,并为列表中指定的每列生成一行。使用语法如下:select ... from ... unpivot ( <value_column> for <name_column> in ( <column_list> ) ) [ ... ]参数解释:<value_column>:将存储从<column_list>中列出的列中提取的值的列。<name_column>:将存储提取值的列名称的列。<column_list>:要旋转的列列表,用逗号分隔。利用数据透视的结果,使用 unpivot 恢复原样实现数据逆透视。with a as (select t1.student_id , t1.subject_level , split_part(unnest(split(t1.subject_level, ',')), ' ', 1) as subject , split_part(unnest(split(t1.subject_level, ',')), ' ', 2) as level1 from fact_suject_data as t1 order by t1.student_id), b as (select * from a pivot (max(level1) for subject in ('china','math','english')) ) select * from b unpivot (level2 for subject in (`china`,`math`,`english`));总结Databend 的 pivot 和 unpivot 功能更好地实现数据的透视和逆透视,并且非常易读和分析大量数据,相较于 Mysql 实现数据透视 (case …when…) 和逆透视 (union all) 来说更简单易读,方法不闲多主要是解决实际问题,学习了解更多方法和工具,在面对问题时也能更好的应对,赶紧实操起来,当遇到也能很自信地说“这题我会”。参考资料:Databend Query Pivot:https://docs.databend.com/sql/sql-commands/query-syntax/query-pivotDatabend Query UnPivot:https://docs.databend.com/sql/sql-commands/query-syntax/query-unpivot
0
0
0
浏览量520
攻城狮无远

Databend 的安装配置和使用

介绍Databend 是一个内置在 Rust 中的开源、弹性和工作负载感知的云数据仓库,为 Snowflake 提供了具有成本效益的替代方案,专门对最大的数据集进行复杂分析而设计。性能:在存储对象上,能快速进行数据分析。没有索引和分区,但也能快速查询。利用数据级并行和指令级并行技术实现最佳性能。数据操作:支持原子操作,如SELECT、INSERT、DELETE、UPDATE、REPLACE、COPY和MERGE。提供高级功能,如时间旅行和多目录(Apache Hive / Apache Iceberg)。支持以 CSV、JSON 和 Parquet 等各种格式。支持半结构化数据类型,如 ARRAY、MAP 和 JSON 。支持类似 Git 的 MVCC 存储,以便于查询、克隆和恢复历史数据。对象存储:Amazon S3Azure Blob StorageGoogle Cloud StorageMinIOCephWasabiSeaweedFSCloudflare R2Tencent COSAlibaba OSSQingCloud QingStorDatabend 的高级架构由meta-service layer、query layer和storage layer组成。meta-service layer(元服务层)有效地支持多个租户,该层在系统中发挥着至关重要的作用:元数据管理:处理数据库、表、集群、事务等的元数据。安全性:管理安全环境的用户身份验证和授权。query layer(查询层)处理查询计算,由多个集群组成,每个集群包含多个节点。每个节点是查询层中的核心单元,由以下部分组成:规划师:使用来自的元素为SQL语句制定执行计划关系代数,结合了投影、滤波器和限位等运算符。优化器:基于规则的优化器应用预定义规则,如“谓词下推”和“未使用列的修剪”,以优化查询执行。处理器:遵循 Pull & Push 方法,根据规划师指令构建查询执行管道。处理器相互连接,形成一个可以分布在节点之间的管道,以提高性能。storage layer采用开源柱状格式 Parquet ,并引入了自己的表格格式,以提高查询性能。主要功能包括:二级索引:加快各种分析维度的数据位置和访问速度。复杂数据类型索引:旨在加快半结构化数据等复杂类型的数据处理和分析。段:Databend 有效地将数据组织成段,提高数据管理和检索效率。集群:在段内使用用户定义的集群密钥来简化数据扫描。安装和配置Databend 官网:https://databend.rs/doc/integrations/access-tool/mysqlDatabend 下载:https://databend.rs/download本地部署,使用 JDBC 驱动程序连接 Databend 步骤如下:1.根据官网下载地址,选择与自己电脑版本对应的,下载解压后可以看到以下系列文件。2.将文件 databend-query.toml 中的以下部分注释取消。3.在文件夹脚本中运行脚本**start.sh,打开进入该文件目录中终端执行 ./scripts/start.sh。4.在DBeaver中,选择数据库>驱动程序管理器以打开驱动程序管理器,然后单击新建以创建新驱动程序。根据上图片,添加设置相关信息如下图:5.在“库”选项卡上,单击“添加工件”,然后将以下内容复制并粘贴到“**依赖项声明”**文本框中,点击确定即可。<dependency> <groupId>com.databend</groupId> <artifactId>databend-jdbc</artifactId> <version>0.0.8</version> </dependency>6.使用前面添加的驱动,连接本地的 Databend 。相关扩展Databend支持的工具、平台和编程语言如下:可以查看对应的工具和编程语言,了解更多用法。参考资料Databend 官网Databend 下载Docker和本地部署通过JDBC连接到DatabendDatabend生态系统
0
0
0
浏览量515
攻城狮无远

Doris-04-数据导入和导出&数据备份和恢复

数据导入和导出数据导入导入(Load)功能就是将用户的原始数据导入到 Doris 中。导入成功后,用户即可通过Mysql 客户端查询数据。为适配不同的数据导入需求,Doris 系统提供了 6 种不同的导入方式。每种导入方式支持不同的数据源,存在不同的使用方式(异步,同步)。所有导入方式都支持 csv 数据格式。其中 Broker load 还支持 parquet 和 orc 数据格式。Broker load:通过 Broker 进程访问并读取外部数据源(如 HDFS)导入到 Doris。用户通过 Mysql协议提交导入作业后,异步执行。通过 SHOW LOAD 命令查看导入结果。Stream load:用户通过 HTTP 协议提交请求并携带原始数据创建导入。主要用于快速将本地文件或数据流中的数据导入到 Doris。导入命令同步返回导入结果。目前 Stream Load 支持两个数据格式:CSV(文本)和 JSON。Insert:类似 MySQL 中的 Insert 语句,Doris 提供 INSERT INTO tbl SELECT …; 的方式从Doris 的表中读取数据并导入到另一张表。或者通过 INSERT INTO tbl VALUES(…); 插入单条数据。Multi load:用户通过 HTTP 协议提交多个导入作业。Multi Load 可以保证多个导入作业的原子生效。Routine load:用户通过 MySQL 协议提交例行导入作业,生成一个常驻线程,不间断的从数据源(如Kafka)中读取数据并导入到 Doris 中。通过S3 协议直接导入:用户通过 S3 协议直接导入数据,用法和 Broker Load 类似。Broker load 是一个异步的导入方式,支持的数据源取决于 Broker 进程支持的数据源。用户需要通过 MySQL 协议创建 Broker load 导入,并通过查看导入命令检查导入结果。Binlog Load:提供了一种使Doris增量同步用户在Mysql数据库的对数据更新操作的CDC(Change Data Capture)功能。需要依赖canal作为中间媒介。导入方式支持的格式Broker LoadParquet,ORC,csv,gzipStream Loadcsv, gzip, jsonRoutine Loadcsv, jsonBroker Load(1)适用场景源数据在 Broker 可以访问的存储系统中,如 HDFS。数据量在几十到百 GB 级别。(2)基本原理用户在提交导入任务后,FE 会生成对应的 Plan 并根据目前 BE 的个数和文件的大小,将 Plan 分给多个 BE 执行,每个 BE 执行一部分导入数据。BE 在执行的过程中会从 Broker 拉取数据,在对数据 transform 之后将数据导入系统。所有 BE 均完成导入,由 FE 最终决定导入是否成功。(3)基本语法:LOAD LABEL db_name.label_name (data_desc, ...) WITH BROKER broker_name broker_properties [PROPERTIES (key1=value1, ... )] * data_desc: DATA INFILE ('file_path', ...) [NEGATIVE] INTO TABLE tbl_name [PARTITION (p1, p2)] [COLUMNS TERMINATED BY separator ] [(col1, ...)] [PRECEDING FILTER predicate] [SET (k1=f1(xx), k2=f2(xx))] [WHERE predicate] * broker_properties: (key1=value1, ...) 创建导入的详细语法执行 HELP BROKER LOAD 查看语法帮助。这里主要介绍 Broker load 的创建导入语法中参数意义和注意事项。Label:导入任务的标识。每个导入任务,都有一个在单 database 内部唯一的 Label。Label 是用户在导入命令中自定义的名称。通过这个 Label,用户可以查看对应导入任务的执行情况。Label 的另一个作用,是防止用户重复导入相同的数据。强烈推荐用户同一批次数据使用相同的 label。这样同一批次数据的重复请求只会被接受一次,保证了** At-Most-Once 语义当 Label 对应的导入作业状态为 CANCELLED 时,可以再次使用该 Label 提交导入作业。数据描述类参数:数据描述类参数主要指的是 Broker load 创建导入语句中的属于 data_desc 部分的参数。每组 data_desc 主要表述了本次导入涉及到的数据源地址,ETL 函数,目标表及分区等信息。下面主要对数据描述类的部分参数详细解释:多表导入:Broker load 支持一次导入任务涉及多张表,每个 Broker load 导入任务可在多个 data_desc 声明多张表来实现多表导入。每个单独的 data_desc 还可以指定属于该表的数据源地址。Broker load 保证了单次导入的多张表之间原子性成功或失败。negative:data_desc 中还可以设置数据取反导入。这个功能主要用于,当数据表中聚合列的类型都为 SUM 类型时。如果希望撤销某一批导入的数据。则可以通过 negative 参数导入同一批数据。Doris 会自动为这一批数据在聚合列上数据取反,以达到消除同一批数据的功能。partition:在 data_desc 中可以指定待导入表的 partition 信息,如果待导入数据不属于指定的partition 则不会被导入。同时,不在指定 Partition 的数据会被认为是错误数据。preceding filter predicate:用于过滤原始数据。原始数据是未经列映射、转换的数据。用户可以在对转换前的数据前进行一次过滤,选取期望的数据,再进行转换。set column mapping:在 data_desc 中的 SET 语句负责设置列函数变换,这里的列函数变换支持所有查询的等值表达式变换。如果原始数据的列和表中的列不一一对应,就需要用到这个属性。where predicate:在 data_desc 中的 WHERE 语句中负责过滤已经完成 transform 的数据,被 filter 的数据不会进入容忍率的统计中。如果多个 data_desc 中声明了同一张表的多个条件的话,则会merge 同一张表的多个条件,merge 策略是 AND 。导入作业参数:导入作业参数主要指的是 Broker load 创建导入语句中的属于 opt_properties 部分的参数。导入作业参数是作用于整个导入作业的。下面主要对导入作业参数的部分参数详细解释:timeout:导入作业的超时时间(以秒为单位),用户可以在 opt_properties 中自行设置每个导入的超时时间。导入任务在设定的 timeout 时间内未完成则会被系统取消,变成 CANCELLED。Broker load 的默认导入超时时间为 4 小时。通常情况下,用户不需要手动设置导入任务的超时时间。当在默认超时时间内无法完成导入时,可以手动设置任务的超时时间。推荐超时时间:总文件大小(MB) / 用户 Doris 集群最慢导入速度(MB/s) > timeout > ((总文件大小(MB) * 待导入的表及相关 Roll up 表的个数) / (10 * 导入并发数) )max_filter_ratio:导入任务的最大容忍率,默认为 0 容忍,取值范围是 0~1。当导入的错误率超过该值,则导入失败。如果用户希望忽略错误的行,可以通过设置这个参数大于 0,来保证导入可以成功。计算公式为:max_filter_ratio = (dpp.abnorm.ALL / (dpp.abnorm.ALL + dpp.norm.ALL ) ) dpp.abnorm.ALL 表示数据质量不合格的行数。如类型不匹配,列数不匹配,长度不匹配等等。dpp.norm.ALL 指的是导入过程中正确数据的条数。可以通过 SHOW LOAD 命令查询导入任务的正确数据量。原始文件的行数 = dpp.abnorm.ALL + dpp.norm.ALLexec_mem_limit:导入内存限制。默认是 2GB。单位为字节。strict_mode:Broker load 导入可以开启 strict mode 模式。开启方式为 properties (“strict_mode” = “true”) 。默认的 strict mode 为关闭。strict mode 模式的意思是:对于导入过程中的列类型转换进行严格过滤。严格过滤的策略如下:①对于列类型转换来说,如果 strict mode 为 true,则错误的数据将被 filter。这里的错误数据是指:原始数据并不为空值,在参与列类型转换后结果为空值的这一类数据。② 对于导入的某列由函数变换生成时,strict mode 对其不产生影响。③ 对于导入的某列类型包含范围限制的,如果原始数据能正常通过类型转换,但无法通过范围限制的,strict mode 对其也不产生影响。例如:如果类型是 decimal(1,0), 原始数据为 10,则属于可以通过类型转换但不在列声明的范围内。这种数据 strict 对其不产生影响。merge_type :数据的合并类型,一共支持三种类型 APPEND、DELETE、MERGE 其中,APPEND 是默认值,表示这批数据全部需要追加到现有数据中,DELETE 表示删除与这批数据 key 相同的所有行,MERGE 语义 需要与 delete 条件联合使用,表示满足 delete 条件的数据按照DELETE 语义处理其余的按照 APPEND 语义处理。(4)导入示例Doris 中创建表create table student_result ( id int , name varchar(50), age int , score decimal(10,4) ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 10; 文件上传 HDFS启动 HDFS 相关服务:hadoop fs -put student.csv / 导入数据csv 文件导入:LOAD LABEL test_db.student_result ( DATA INFILE("hdfs://my_cluster/student.csv") INTO TABLE `student_result` COLUMNS TERMINATED BY "," FORMAT AS "csv" (id, name, age, score) ) WITH BROKER broker_name (#开启了 HA 的写法,其他 HDFS 参数可以在这里指定 "dfs.nameservices" = "my_cluster", "dfs.ha.namenodes.my_cluster" = "nn1,nn2,nn3", "dfs.namenode.rpc-address.my_cluster.nn1" = "hadoop1:8020", "dfs.namenode.rpc-address.my_cluster.nn2" = "hadoop2:8020", "dfs.namenode.rpc-address.my_cluster.nn3" = "hadoop3:8020", "dfs.client.failover.proxy.provider" = "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider" ) PROPERTIES ( "timeout" = "3600" ); 通用文件格式写法:LOAD LABEL test_db.student_result ( DATA INFILE("hdfs://hadoop1:8020/student.csv") INTO TABLE `student_result` COLUMNS TERMINATED BY "," (c1, c2, c3, c4) set( id=c1, name=c2, age=c3, score=c4 ) ) WITH BROKER broker_name (#开启了 HA 的写法,其他 HDFS 参数可以在这里指定 "dfs.nameservices" = "my_cluster", "dfs.ha.namenodes.my_cluster" = "nn1,nn2,nn3", "dfs.namenode.rpc-address.my_cluster.nn1" = "hadoop1:8020", "dfs.namenode.rpc-address.my_cluster.nn2" = "hadoop2:8020", "dfs.namenode.rpc-address.my_cluster.nn3" = "hadoop3:8020", "dfs.client.failover.proxy.provider" = "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProx yProvider" ) PROPERTIES ( "timeout" = "3600" ); (5)查看导入Broker load 导入方式由于是异步的,所以用户必须将创建导入的 Label 记录,并且在查看导入命令中使用 Label 来查看导入结果。查看导入命令在所有导入方式中是通用的,mysql> show load order by createtime desc limit 1\G *************************** 1. row *************************** JobId: 76391 Label: label1 State: FINISHED Progress: ETL:N/A; LOAD:100% Type: BROKER EtlInfo: unselected.rows=4; dpp.abnorm.ALL=15; dpp.norm.ALL=28133376 TaskInfo: cluster:N/A; timeout(s):10800; max_filter_ratio:5.0E-5 ErrorMsg: N/A CreateTime: 2019-07-27 11:46:42 EtlStartTime: 2019-07-27 11:46:44 EtlFinishTime: 2019-07-27 11:46:44 LoadStartTime: 2019-07-27 11:46:44 LoadFinishTime: 2019-07-27 11:50:16 URL: http://192.168.1.1:8040/api/_load_error_log?file=__shard_4/error_ log_insert_stmt_4bb00753932c491a-a6da6e2725415317_4bb00753932c491a_a6da6e2725415317 JobDetails: {"Unfinished backends":{"9c3441027ff948a0- 8287923329a2b6a7":[10002]},"ScannedRows":2390016,"TaskNumber":1," All backends":{"9c3441027ff948a0- 8287923329a2b6a7":[10002]},"FileNumber":1,"FileSize":1073741824} 下面主要介绍了查看导入命令返回结果集中参数意义:JobId:导入任务的唯一 ID,每个导入任务的 JobId 都不同,由系统自动生成。与 Label 不同的是,JobId 永远不会相同,而 Label 则可以在导入任务失败后被复用。Label:导入任务的标识。State:导入任务当前所处的阶段。在 Broker load 导入过程中主要会出现 PENDING 和LOADING 这两个导入中的状态。如果 Broker load 处于 PENDING 状态,则说明当前导入任务正在等待被执行;LOADING 状态则表示正在执行中。导入任务的最终阶段有两个:CANCELLED 和 FINISHED,当 Load job 处于这两个阶段时,导入完成。其中 CANCELLED 为导入失败,FINISHED 为导入成功。Progress:导入任务的进度描述。分为两种进度:ETL 和 LOAD,对应了导入流程的两个阶段 ETL 和 LOADING。目前 Broker load 由于只有 LOADING 阶段,所以 ETL 则会永远显示为 N/ALOAD 的进度范围为:0~100%。LOAD 进度 = 当前完成导入的表个数 / 本次导入任务设计的总表个数 * 100%如果所有导入表均完成导入,此时 LOAD 的进度为 99% 导入进入到最后生效阶段,整个导入完成后,LOAD 的进度才会改为 100%。导入进度并不是线性的。所以如果一段时间内进度没有变化,并不代表导入没有在执行。Type:导入任务的类型。Broker load 的 type 取值只有 BROKER。EtlInfo:主要显示了导入的数据量指标 unselected.rows , dpp.norm.ALL 和 dpp.abnorm.ALL。用户可以根据第一个数值判断 where 条件过滤了多少行,后两个指标验证当前导入任务的错误率是否超过 max_filter_ratio。三个指标之和就是原始数据量的总行数。TaskInfo:主要显示了当前导入任务参数,也就是创建 Broker load 导入任务时用户指定的导入任务参数,包括:cluster,timeout 和 max_filter_ratio。ErrorMsg:在导入任务状态为 CANCELLED,会显示失败的原因,显示分两部分:type 和 msg,如果导入任务成功则显示 N/A。type 的取值意义:USER_CANCEL: 用户取消的任务ETL_RUN_FAIL:在 ETL 阶段失败的导入任务ETL_QUALITY_UNSATISFIED : 数 据 质 量 不 合 格 , 也 就 是 错 误 数 据 率 超 过 了max_filter_ratioLOAD_RUN_FAIL:在 LOADING 阶段失败的导入任务TIMEOUT:导入任务没在超时时间内完成UNKNOWN:未知的导入错误CreateTime/EtlStartTime/EtlFinishTime/LoadStartTime/LoadFinishTime这几个值分别代表导入创建的时间,ETL 阶段开始的时间,ETL 阶段完成的时间,Loading 阶段开始的时间和整个导入任务完成的时间。Broker load 导 入 由 于 没 有 ETL 阶 段 , 所 以 其 EtlStartTime, EtlFinishTime, LoadStartTime 被设置为同一个值。导入任务长时间停留在 CreateTime,而 LoadStartTime 为 N/A 则说明目前导入任务堆积严重。用户可减少导入提交的频率。LoadFinishTime - CreateTime = 整个导入任务所消耗时间LoadFinishTime - LoadStartTime = 整个 Broker load 导入任务执行时间 = 整个导入任务所消耗时间 - 导入任务等待的时间URL:导入任务的错误数据样例,访问 URL 地址既可获取本次导入的错误数据样例。当本次导入不存在错误数据时,URL 字段则为 N/A。JobDetails:显示一些作业的详细运行状态。包括导入文件的个数、总大小(字节)、子任务个数、已处理的原始行数,运行子任务的 BE 节点 Id,未完成的 BE 节点 Id。其中已处理的原始行数,每 5 秒更新一次。该行数仅用于展示当前的进度,不代表最终实际的处理行数。实际处理行数以 EtlInfo 中显示的为准。(6)取消导入当 Broker load 作业状态不为 CANCELLED 或 FINISHED 时,可以被用户手动取消。取消时需要指定待取消导入任务的 Label 。取消导入命令语法可执行 HELP CANCEL LOAD 查看。CANCEL LOAD [FROM db_name] WHERE LABEL=”load_label”; Stream LoadStream load 是一个同步的导入方式,用户通过发送 HTTP 协议发送请求将本地文件或数据流导入到 Doris 中。Stream load 同步执行导入并返回导入结果。用户可直接通过请求的返回体判断本次导入是否成功。(1)适用场景Stream load 主要适用于导入本地文件,或通过程序导入数据流中的数据。目前 Stream Load 支持两个数据格式:CSV(文本)和 JSON。(2)基本原理下图展示了 Stream load 的主要流程,省略了一些导入细节。Stream load 中,Doris 会选定一个节点作为 Coordinator 节点。该节点负责接数据并分发数据到其他数据节点。用户通过 HTTP 协议提交导入命令。如果提交到 FE,则 FE 会通过 HTTP redirect 指令将请求转发给某一个 BE。用户也可以直接提交导入命令给某一指定 BE。导入的最终结果由 Coordinator BE 返回给用户。(3)基本语法Stream load 通过 HTTP 协议提交和传输数据。这里通过 curl 命令展示如何提交导入。用户也可以通过其他 HTTP client 进行操作:curl --location-trusted -u user:passwd [-H ""...] -T data.file -XPUT http://fe_host:http_port/api/{db}/{table}/_stream_load 创建导入的详细语法帮助执行 HELP STREAM LOAD 查看, 下面主要介绍创建 Stream load 的部分参数意义。签名参数:user/passwdStream load 由于创建导入的协议使用的是 HTTP 协议,通过 Basic access authentication 进行签名。Doris 系统会根据签名验证用户身份和导入权限。导入任务参数Stream load 由于使用的是 HTTP 协议,所以所有导入任务有关的参数均设置在Header 中。格式为: -H “key1:value1”。下面主要介绍了 Stream load 导入任务参数的部分参数意义。Label:导入任务的标识column_separator:用于指定导入文件中的列分隔符,默认为\t。如果是不可见字符,则需要加\x 作为前缀,使用十六进制来表示分隔符。如 hive 文件的分隔符\x01,需要指定为-H “column_separator:\x01”。可以使用多个字符的组合作为列分隔符。line_delimiter:用于指定导入文件中的换行符,默认为\n。可以使用做多个字符的组合作为换行符。max_filter_ratio:导入任务的最大容忍率where:导入任务指定的过滤条件。Stream load 支持对原始数据指定 where 语句进行过滤。被过滤的数据将不会被导入,也不会参与 filter ratio 的计算,但会被计入num_rows_unselected。partition:待导入表的 Partition 信息,如果待导入数据不属于指定的 Partition 则不会被导入。这些数据将计入 dpp.abnorm.ALL。columns:待导入数据的函数变换配置,目前 Stream load 支持的函数变换方法包含列的顺序变化以及表达式变换,其中表达式变换的方法与查询语句的一致。列顺序变换例子:原始数据有三列(src_c1,src_c2,src_c3), 目前 doris 表也有三列(dst_c1,dst_c2,dst_c3)。如果原始表的 src_c1 列对应目标表 dst_c1 列,原始表的 src_c2 列对应目标表 dst_c2 列,原始表的 src_c3 列对应目标表 dst_c3 列,则写法如下:columns: dst_c1, dst_c2, dst_c3如果原始表的 src_c1 列对应目标表 dst_c2 列,原始表的 src_c2 列对应目标表 dst_c3 列,原始表的 src_c3 列对应目标表 dst_c1 列,则写法如下:columns: dst_c2, dst_c3, dst_c1表达式变换例子:原始文件有两列,目标表也有两列(c1,c2)但是原始文件的两列均需要经过函数变换才能对应目标表的两列,则写法如下:columns: tmp_c1, tmp_c2, c1 = year(tmp_c1), c2 = month(tmp_c2)其中 tmp_*是一个占位符,代表的是原始文件中的两个原始列。exec_mem_limit:导入内存限制。默认为 2GB,单位为字节。strict_modetwo_phase_commit:Stream load 导入可以开启两阶段事务提交模式。开启方式为在 HEADER 中声明two_phase_commit=true 。默认的两阶段批量事务提交为关闭。 两阶段批量事务提交模式的意思是:Stream load 过程中,数据写入完成即会返回信息给用户,此时数据不可见,事务状态为 PRECOMMITTED,用户手动触发 commit 操作之后,数据才可见。用户可以调用如下接口对 stream load 事务触发 commit 操作:curl -X PUT --location-trusted -u user:passwd -H "txn_id:txnId" -H "txn_operation:commit" http://fe_host:http_port/api/{db}/_stream_load_2pc 或 curl -X PUT --location-trusted -u user:passwd -H "txn_id:txnId" -H "txn_operation:commit" http://be_host:webserver_port/api/{db}/_stream_load_2pc 用户可以调用如下接口对 stream load 事务触发 abort 操作:curl -X PUT --location-trusted -u user:passwd -H "txn_id:txnId" -H "txn_operation:abort" http://fe_host:http_port/api/{db}/_stream_load_2pc 或 curl -X PUT --location-trusted -u user:passwd -H "txn_id:txnId" -H "txn_operation:abort" http://be_host:webserver_port/api/{db}/_stream_load_2pc (4)导入示例curl --location-trusted -u root -H "label:123" -H"column_separator:," -T student.csv -X PUT http://hadoop1:8030/api/test_db/student_result/_stream_load 由于 Stream load 是一种同步的导入方式,所以导入的结果会通过创建导入的返回值直接返回给用户。注意:由于 Stream load 是同步的导入方式,所以并不会在 Doris 系统中记录导入信息,用户无法异步的通过查看导入命令看到 Stream load。使用时需监听创建导入请求的返回值获取导入结果。(5)取消导入用户无法手动取消 Stream load,Stream load 在超时或者导入错误后会被系统自动取消。Stream Load 是一个同步的导入方式,用户通过发送 HTTP 协议将本地文件或数据流导入到Doris 中,Stream load 同步执行导入并返回结果。用户可以直接通过返回判断导入是否成功。Routine Load例行导入(Routine Load)功能为用户提供了一种自动从指定数据源进行数据导入的功能。(1)适用场景当前仅支持从 Kafka 系统进行例行导入,使用限制:支持无认证的 Kafka 访问,以及通过 SSL 方式认证的 Kafka 集群。支持的消息格式为 csv, json 文本格式。csv 每一个 message 为一行,且行尾不包含换行符。默认支持 Kafka 0.10.0.0(含)以上版本。如果要使用 Kafka 0.10.0.0 以下版本(0.9.0, 0.8.2, 0.8.1, 0.8.0),需要修改 be 的配置,将 kafka_broker_version_fallback 的值设置为要兼容的旧版本,或者在创建 routine load 的时候直接设置 property.broker.version.fallback的值为要兼容的旧版本,使用旧版本的代价是 routine load 的部分新特性可能无法使用,如根据时间设置 kafka 分区的 offset。(2)基本原理如上图,Client 向 FE 提交一个例行导入作业。FE 通过 JobScheduler 将一个导入作业拆分成若干个 Task。每个 Task 负责导入指定的一部分数据。Task 被 TaskScheduler 分配到指定的 BE 上执行。在 BE 上,一个 Task 被视为一个普通的导入任务,通过 Stream Load 的导入机制进行导入。导入完成后,向 FE 汇报。FE 中的 JobScheduler 根据汇报结果,继续生成后续新的 Task,或者对失败的Task 进行重试。整个例行导入作业通过不断的产生新的 Task,来完成数据不间断的导入。**(3)基本语法 **CREATE ROUTINE LOAD [db.]job_name ON tbl_name [merge_type] [load_properties] [job_properties] FROM data_source [data_source_properties] [db.]job_name:导入作业的名称,在同一个 database 内,相同名称只能有一个 job 在运行。tbl_name:指定需要导入的表的名称。merge_type:数据的合并类型,一共支持三种类型 APPEND、DELETE、MERGE 其中,APPEND 是默认值,表示这批数据全部需要追加到现有数据中,DELETE 表示删除与这批数据 key 相同的所有行,MERGE 语义 需要与 delete on 条件联合使用,表示满足 delete 条件的数据按照 DELETE 语义处理其余的按照 APPEND 语义处理 , 语法为 [WITH MERGE|APPEND|DELETE]load_properties:用于描述导入数据。语法: [column_separator], [columns_mapping], [where_predicates], [delete_on_predicates], [source_sequence], [partitions], [preceding_predicates]column_separator:指定列分隔符,如: COLUMNS TERMINATED BY ","这个只在文本数据导入的时候需要指定,JSON 格式的数据导入不需要指定这个参数。默认为:\t ;columns_mapping:指定源数据中列的映射关系,以及定义衍生列的生成方式。映射列:按顺序指定,源数据中各个列,对应目的表中的哪些列。对于希望跳过的列,可以指定一个不存在的列名。假设目的表有三列 k1, k2, v1。源数据有 4 列,其中第 1、2、4 列分别对应 k2, k1, v1。则书写如下:COLUMNS (k2, k1, xxx, v1)其中 xxx 为不存在的一列,用于跳过源数据中的第三列。衍生列:以 col_name = expr 的形式表示的列,我们称为衍生列。即支持通过 expr 计算得出目的表中对应列的值。 衍生列通常排列在映射列之后,虽然这不是强制的规定,但是 Doris 总是先解析映射列,再解析衍生列。 接上一个示例,假设目的表还有第 4 列 v2,v2 由 k1 和 k2 的和产生。则可以书写如下:COLUMNS (k2, k1, xxx, v1, v2 = k1 + k2);再举例,假设用户需要导入只包含 k1 一列的表,列类型为 int。并且需要将源文件中的对应列进行处理:将负数转换为正数,而将正数乘以 100。这个功能可以通过 case when 函数实现,正确写法应如下:COLUMNS (xx, k1 = case when xx < 0 then cast(-xx as varchar) else cast((xx + ‘100’) as varchar) end)where_predicates:用于指定过滤条件,以过滤掉不需要的列。过滤列可以是映射列或衍生列。 例如我们只希望导入 k1 大于 100 并且 k2 等于 1000 的列,则书写如下:WHERE k1 > 100 and k2 = 1000partitions:指定导入目的表的哪些 partition 中。如果不指定,则会自动导入到对应的 partition 中。示例:PARTITION(p1, p2, p3)delete_on_predicates:表示删除条件,仅在 merge type 为 MERGE 时有意义,语法与 where 相同source_sequence:只适用于 UNIQUE_KEYS,相同 key 列下,保证 value 列按照 source_sequence 列进行REPLACE, source_sequence 可以是数据源中的列,也可以是表结构中的一列。preceding_predicates:PRECEDING FILTER predicate。用于过滤原始数据。原始数据是未经列映射、转换的数据。用户可以在对转换前的数据前进行一次过滤,选取期望的数据,再进行转换。job_properties.。用于指定例行导入作业的通用参数。 语法:PROPERTIES ( "key1" = "val1", "key2" = "val2" )目前支持以下参数:desired_concurrent_number:期望的并发度。一个例行导入作业会被分成多个子任务执行。这个参数指定一个作业最多有多少任务可以同时执行。必须大于 0。默认为 3。 这个并发度并不是实际的并发度,实际的并发度,会通过集群的节点数、负载情况,以及数据源的情况综合考虑。一个作业,最多有多少 task 同时在执行。对于 Kafka 导入而言,当前的实际并发度计算如下:Min(partition num, desired_concurrent_number, alive_backend_num, Config.max_routine_load_task_concurrrent_num) 其中 Config.max_routine_load_task_concurrrent_num 是系统的一个默认的最大并发数限制。这是一个 FE 配置,可以通过改配置调整。默认为 5。其中 partition num 指订阅的 Kafka topic 的 partition 数量。alive_backend_num 是当前正常的 BE 节点数。max_batch_interval/max_batch_rows/max_batch_size这三个参数分别表示:① 每个子任务最大执行时间,单位是秒。范围为 5 到 60。默认为 10。② 每个子任务最多读取的行数。必须大于等于 200000。默认是 200000。③ 每个子任务最多读取的字节数。单位是字节,范围是 100MB 到 1GB。默认是100MB。这三个参数,用于控制一个子任务的执行时间和处理量。当任意一个达到阈值,则任务结束。 例如:"max_batch_interval" = "20", "max_batch_rows" = "300000", "max_batch_size" = "209715200" max_error_number:采样窗口内,允许的最大错误行数。必须大于等于 0。默认是 0,即不允许有错误行。采样窗口为 max_batch_rows * 10。即如果在采样窗口内,错误行数大于 max_error_number,则会导致例行作业被暂停,需要人工介入检查数据质量问题。 被 where 条件过滤掉的行不算错误行strict_mode:是否开启严格模式,默认为关闭。如果开启后,非空原始数据的列类型变换如果结果为NULL,则会被过滤。指定方式为 “strict_mode” = “true”timezone:指定导入作业所使用的时区。默认为使用 Session 的 timezone 参数。该参数会影响所有导入涉及的和时区有关的函数结果format:指定导入数据格式,默认是 csv,支持 json 格式jsonpaths:导入 json 方式分为:简单模式和匹配模式。如果设置了 jsonpath 则为匹配模式导入,否则为简单模式导入,具体可参考示例strip_outer_array:布尔类型,为 true 表示 json 数据以数组对象开始且将数组对象中进行展平,默认值是falsejson_root:json_root 为合法的 jsonpath 字符串,用于指定 json document 的根节点,默认值为""send_batch_parallelism:整型,用于设置发送批处理数据的并行度,如果并行度的值超过 BE 配置中的max_send_batch_parallelism_per_job , 那么作为协调点的BE 将 使 用max_send_batch_parallelism_per_job 的值data_source_properties:数据源的类型。当前支持:Kafka( "key1" = "val1", "key2" = "val2" ) 4)Kafka 导入示例在 doris 中创建对应表create table student_kafka ( id int, name varchar(50), age int ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 10; 启动 kafka 并准备数据bin/kafka-topics.sh --create \ --zookeeper hadoop1:2181/kafka \ --replication-factor 1 \ --partitions 1 \ --topic test_doris1 bin/kafka-console-producer.sh \ --broker-list hadoop1:9092,hadoop2:9092,hadoop3:9092 \ --topic test_doris 创建导入任务CREATE ROUTINE LOAD test_db.kafka_test ON student_kafka COLUMNS TERMINATED BY ",", COLUMNS(id, name, age) PROPERTIES ("desired_concurrent_number"="3", "strict_mode" = "false" ) FROM KAFKA ( "kafka_broker_list"= "hadoop1:9092,hadoop2:9092,hadoop3:9092", "kafka_topic" = "test_doris1", "property.group.id"="test_doris_group", "property.kafka_default_offsets" = "OFFSET_BEGINNING", "property.enable.auto.commit"="false" ); 查看表select * from student_kafka; 继续往 kafka 发送数据,查看表的变化(5)查看导入作业状态查看作业状态的具体命令和示例可以通过 HELP SHOW ROUTINE LOAD; 命令查看。查看任务运行状态的具体命令和示例可以通过 HELP SHOW ROUTINE LOAD TASK; 命令查看。只能查看当前正在运行中的任务,已结束和未开始的任务无法查看。(6)修改作业属性用户可以修改已经创建的作业。具体说明可以通过 HELP ALTER ROUTINE LOAD; 命令查看。或参阅 ALTER ROUTINE LOAD(7)作业控制用户可以通过 STOP/PAUSE/RESUME 三个命令来控制作业的停止,暂停和重启。可以通过 HELP STOP ROUTINE LOAD; HELP PAUSE ROUTINE LOAD; 以及 HELP RESUME ROUTINE LOAD; 三个命令查看帮助和示例。(8)其他说明例行导入作业和 ALTER TABLE 操作的关系例行导入不会阻塞 SCHEMA CHANGE 和 ROLLUP 操作。但是注意如果 SCHEMA CHANGE 完成后,列映射关系无法匹配,则会导致作业的错误数据激增,最终导致作业暂停。建议通过在例行导入作业中显式指定列映射关系,以及通过增加 Nullable 列或带Default 值的列来减少这类问题。删除表的 Partition 可能会导致导入数据无法找到对应的 Partition,作业进入暂停。例行导入作业和其他导入作业的关系(LOAD, DELETE, INSERT)例行导入和其他 LOAD 作业以及 INSERT 操作没有冲突。当执行 DELETE 操作时,对应表分区不能有任何正在执行的导入任务。所以在执行DELETE 操作前,可能需要先暂停例行导入作业,并等待已下发的 task 全部完成后,才可以执行 DELETE。例行导入作业和DROP DATABASE/TABLE 操作的关系当例行导入对应的 database 或 table 被删除后,作业会自动 CANCEL。kafka类型的例行导入作业和 kafka topic 的关系当用户在创建例行导入声明的 kafka_topic 在 kafka 集群中不存在时:如果用户 kafka 集群的 broker 设置了 auto.create.topics.enable = true,则kafka_topic 会先被自动创建,自动创建的 partition 个数是由用户方的 kafka 集群中的broker 配置 num.partitions 决定的。例行作业会正常的不断读取该 topic 的数据。如果用户 kafka 集群的 broker 设置了 auto.create.topics.enable = false, 则 topic 不会被自动创建,例行作业会在没有读取任何数据之前就被暂停,状态为 PAUSED。所以,如果用户希望当 kafka topic 不存在的时候,被例行作业自动创建的话,只需要将用户方的 kafka 集群中的 broker 设置 auto.create.topics.enable = true 即可。在网络隔离的环境中可能出现的问题在有些环境中存在网段和域名解析的隔离措施,所以需要注意:创建 Routine load 任务中指定的 Broker list 必须能够被 Doris 服务访问Kafka 中如果配置了 advertised.listeners, advertised.listeners 中的地址必须能够被Doris 服务访问关于指定消费的 Partition 和 OffsetDoris 支持指定 Partition 和 Offset 开始消费。新版中还支持了指定时间点进行消费的功能。这里说明下对应参数的配置关系。有三个相关参数:kafka_partitions:指定待消费的 partition 列表,如:“0, 1, 2, 3”。kafka_offsets:指定每个分区的起始 offset,必须和 kafka_partitions 列表个数对应。如:“1000, 1000, 2000, 2000”property.kafka_default_offset:指定分区默认的起始 offset。在创建导入作业时,这三个参数可以有以下组合:STOP 和 PAUSE 的区别FE 会自动定期清理 STOP 状态的 ROUTINE LOAD,而 PAUSE 状态的则可以再次被恢复启用。Binlog LoadBinlog Load 提供了一种使 Doris 增量同步用户在 Mysql 数据库的对数据更新操作的CDC(Change Data Capture)功能。(1)适用场景INSERT/UPDATE/DELETE 支持。过滤 Query。暂不兼容 DDL 语句(2)基本原理在第一期的设计中,Binlog Load 需要依赖 canal 作为中间媒介,让 canal 伪造成一个从节点去获取 Mysql 主节点上的 Binlog 并解析,再由 Doris 去获取 Canal 上解析好的数据,主要涉及 Mysql 端、Canal 端以及 Doris 端,总体数据流向如下:如上图,用户向 FE 提交一个数据同步作业。FE 会为每个数据同步作业启动一个 canal client,来向 canal server 端订阅并获取数据。client 中的 receiver 将负责通过 Get 命令接收数据,每获取到一个数据 batch,都会由 consumer 根据对应表分发到不同的 channel,每个 channel 都会为此数据 batch 产生一个发送数据的子任务 Task。在 FE 上,一个 Task 是 channel 向 BE 发送数据的子任务,里面包含分发到当前channel 的同一个 batch 的数据。channel 控制着单个表事务的开始、提交、终止。一个事务周期内,一般会从 consumer获取到多个 batch 的数据,因此会产生多个向 BE 发送数据的子任务 Task,在提交事务成功前,这些 Task 不会实际生效。满足一定条件时(比如超过一定时间、达到提交最大数据大小),consumer 将会阻塞并通知各个 channel 提交事务。当且仅当所有 channel 都提交成功,才会通过 Ack 命令通知 canal 并继续获取并消费数据。如果有任意 channel 提交失败,将会重新从上一次消费成功的位置获取数据并再次提交(已提交成功的 channel 不会再次提交以保证幂等性)。整个数据同步作业中,FE 通过以上流程不断的从 canal 获取数据并提交到 BE,来完成数据同步。(3)配置 MySQL 端在 MySQLCluster 模式的主从同步中,二进制日志文件(Binlog)记录了主节点上的所有数据变化,数据在 Cluster 的多个节点间同步、备份都要通过 Binlog 日志进行,从而提高集群的可用性。架构通常由一个主节点(负责写)和一个或多个从节点(负责读)构成,所有在主节点上发生的数据变更将会复制给从节点。注意:目前必须要使用 Mysql 5.7 及以上的版本才能支持 Binlog Load 功能。打开 mysql 的二进制 binlog 日志功能,编辑 my.cnf 配置文件[mysqld] log-bin = mysql-bin # 开启 binlog binlog-format=ROW # 选择 ROW 模式 binlog-do-db=test #指定具体要同步的数据库,也可以不设置 开启 GTID 模式 [可选]一个全局事务 Id(global transaction identifier)标识出了一个曾在主节点上提交过的事务,在全局都是唯一有效的。开启了 Binlog 后,GTID 会被写入到 Binlog 文件中,与事务一一对应。编辑 my.cnf 配置文件。gtid-mode=on // 开启 gtid 模式 enforce-gtid-consistency=1 // 强制 gtid 和事务的一致性 在 GTID 模式下,主服务器可以不需要 Binlog 的文件名和偏移量,就能很方便的追踪事务、恢复数据、复制副本。在 GTID 模式下,由于 GTID 的全局有效性,从节点将不再需要通过保存文件名和偏移量来定位主节点上的 Binlog 位置,而通过数据本身就可以定位了。在进行数据同步中,从节点会跳过执行任意被识别为已执行的 GTID 事务。GTID 的表现形式为一对坐标, source_id 标识出主节点,transaction_id 表示此事务在主节点上执行的顺序(最大 263-1)。重启 MySQL 使配置生效sudo systemctl restart mysqld 创建用户并授权set global validate_password_length=4; set global validate_password_policy=0; GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%' IDENTIFIED BY 'canal' ; 准备测试表CREATE TABLE `test`.`tbl1` ( `a` int(11) NOT NULL COMMENT "", `b` int(11) NOT NULL COMMENT "" ) insert into test.tbl1 values(1,1),(2,2),(3,3);(4)配置 Canal 端Canal 是属于阿里巴巴 otter 项目下的一个子项目,主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费,用于解决跨机房同步的业务场景,建议使用 canal 1.1.5及以上版本。下载地址:https://github.com/alibaba/canal/releases上传并解压 canal deployermkdir /opt/module/canal-1.1.5 tar -zxvf canal.deployer-1.1.5.tar.gz -C /opt/module/canal-1.1.5 在 conf 文件夹下新建目录并重命名一个 canal 服务中可以有多个 instance,conf/下的每一个目录即是一个实例,每个实例下面都有独立的配置文件mkdir /opt/module/canel-1.1.5/conf/doris-load 拷贝配置文件模板 cp /opt/module/canel-1.1.5/conf/example/instance.properties /opt/module/canel-1.1.5/conf/doris-load 修改 conf/canal.properties 的配置canal.destinations = doris-load 修改 instance 配置文件vim /opt/module/canel-1.1.5/conf/doris-load/instance.properties ## canal instance serverId canal.instance.mysql.slaveId = 1234 ## mysql address canal.instance.master.address = hadoop1:3306 ## mysql username/password canal.instance.dbUsername = canal canal.instance.dbPassword = canal 启动sh bin/startup.sh 验证启动成功cat logs/doris-load/doris-load.log 注意:canal client 和 canal instance 是一一对应的,Binlog Load 已限制多个数据同步作业不能连接到同一个 destination。(5)配置目标表Doris 创建与 Mysql 对应的目标表CREATE TABLE `binlog_test` ( `a` int(11) NOT NULL COMMENT "", `b` int(11) NOT NULL COMMENT "" ) ENGINE=OLAP UNIQUE KEY(`a`) COMMENT "OLAP" DISTRIBUTED BY HASH(`a`) BUCKETS 8; Binlog Load 只能支持 Unique 类型的目标表,且必须激活目标表的 Batch Delete 功能。开启 SYNC 功能在 fe.conf 中将 enable_create_sync_job 设为 true,不想修改配置文件重启,可以执行如下:使用 root 账号登陆 ADMIN SET FRONTEND CONFIG ("enable_create_sync_job" = "true"); (6)基本语法创建数据同步作业的的详细语法可以连接到 Doris 后,执行 HELP CREATE SYNC JOB; 查看语法帮助。CREATE SYNC [db.]job_name ( channel_desc, channel_desc ... ) binlog_desc job_name:job_name 是数据同步作业在当前数据库内的唯一标识,相同 job_name 的作业只能有一个在运行。channel_desc:channel_desc 用来定义任务下的数据通道,可表示 MySQL 源表到 doris 目标表的映射关系。在设置此项时,如果存在多个映射关系,必须满足 MySQL 源表应该与 doris 目标表是一一对应关系,其他的任何映射关系(如一对多关系),检查语法时都被视为不合法。FROM mysql_db.src_tbl INTO des_tbl [partitions] [columns_mapping] column_mapping主要指MySQL源表和doris目标表的列之间的映射关系,如果不指定,FE 会默认源表和目标表的列按顺序一一对应。但是我们依然建议显式的指定列的映射关系,这样当目标表的结构发生变化(比如增加一个 nullable 的列),数据同步作业依然可以进行。否则,当发生上述变动后,因为列映射关系不再一一对应,导入将报错。binlog_desc:binlog_desc 中的属性定义了对接远端 Binlog 地址的一些必要信息,目前可支持的对接类型只有 canal 方式,所有的配置项前都需要加上 canal 前缀。FROM BINLOG ( "key1" = "value1", "key2" = "value2" ) canal.server.ip: canal server 的地址canal.server.port: canal server 的端口canal.destination: 前文提到的 instance 的字符串标识canal.batchSize: 每批从 canal server 处获取的 batch 大小的最大值,默认 8192canal.username: instance 的用户名canal.password: instance 的密码canal.debug: 设置为 true 时,会将 batch 和每一行数据的详细信息都打印出来,会影响性能。(7)示例创建同步作业:CREATE SYNC test_db.job1 ( FROM test.tbl1 INTO binlog_test ) FROM BINLOG ( "type" = "canal", "canal.server.ip" = "hadoop1", "canal.server.port" = "11111", "canal.destination" = "doris-load", "canal.username" = "canal", "canal.password" = "canal" ); 查看作业状态:查看作业状态的具体命令和示例可以通过 HELP SHOW SYNC JOB; 命令查看。# 展示当前数据库的所有数据同步作业状态。 SHOW SYNC JOB; # 展示数据库 `test_db` 下的所有数据同步作业状态。 SHOW SYNC JOB FROM `test_db`;返回结果集的参数意义如下:State:作业当前所处的阶段。作业状态之间的转换如下图所示:作业提交之后状态为PENDING,由FE调度执行启动canal client后状态变成RUNNING,用户可以通过 STOP/PAUSE/RESUME 三个命令来控制作业的停止,暂停和恢复,操作后作业状态分别为 CANCELLED/PAUSED/RUNNING。作业的最终阶段只有一个 CANCELLED,当作业状态变为 CANCELLED 后,将无法再次恢复。当作业发生了错误时,若错误是不可恢复的,状态会变成 CANCELLED,否则会变成 PAUSED。Channel:作业所有源表到目标表的映射关系。Status:当前 binlog 的消费位置(若设置了 GTID 模式,会显示 GTID),以及 doris 端执行时间相比 mysql 端的延迟时间。JobConfig:对接的远端服务器信息,如 canal server 的地址与连接 instance 的 destination。MySQL 表继续插入数据,观察 Doris 的表控制作业:用户可以通过 STOP/PAUSE/RESUME 三个命令来控制作业的停止,暂停和恢复# 停止名称为 `job_name` 的数据同步作业 STOP SYNC JOB [db.]job_name # 暂停名称为 `job_name` 的数据同步作业 PAUSE SYNC JOB [db.]job_name # 恢复名称为 `job_name` 的数据同步作业 RESUME SYNC JOB `job_name Insert IntoInsert Into 语句的使用方式和 MySQL 等数据库中 Insert Into 语句的使用方式类似。但在 Doris 中,所有的数据写入都是一个独立的导入作业。所以这里将 Insert Into 也作为一种导入方式介绍。主要的 Insert Into 命令包含以下两种:INSERT INTO tbl SELECT ... INSERT INTO tbl (col1, col2, ...) VALUES (1, 2, ...), (1,3, ...); 其中第二种命令仅用于 Demo,不要使用在测试或生产环境中。Insert Into 命令需要通过 MySQL 协议提交,创建导入请求会同步返回导入结果。(1)语法INSERT INTO table_name [partition_info] [WITH LABEL label] [col_list] [query_stmt] [VALUES]; WITH LABEL:INSERT 操作作为一个导入任务,也可以指定一个 label。如果不指定,则系统会自动指定一个 UUID 作为 label。该功能需要 0.11+ 版本。注意:建议指定 Label 而不是由系统自动分配。如果由系统自动分配,但在 Insert Into 语句执行过程中,因网络错误导致连接断开等,则无法得知 Insert Into 是否成功。而如果指定 Label,则可以再次通过 Label 查看任务结果。示例:INSERT INTO tbl2 WITH LABEL label1 SELECT * FROM tbl3; INSERT INTO tbl1 VALUES ("qweasdzxcqweasdzxc"), ("a"); 注意:当需要使用 CTE(Common Table Expressions) 作为 insert 操作中的查询部分时,必须指定 WITH LABEL 和 column list 部分。示例INSERT INTO tbl1 WITH LABEL label1 WITH cte1 AS (SELECT * FROM tbl1), cte2 AS (SELECT * FROM tbl2) SELECT k1 FROM cte1 JOIN cte2 WHERE cte1.k1 = 1; INSERT INTO tbl1 (k1) WITH cte1 AS (SELECT * FROM tbl1), cte2 AS (SELECT * FROM tbl2) SELECT k1 FROM cte1 JOIN cte2 WHERE cte1.k1 = 1; (2)SHOW LAST INSERT一些语言的 MySQL 类库中很难获取返回结果的中的 json 字符串。因此,Doris 还提供了 SHOW LAST INSERT 命令来显式的获取最近一次 insert 操作的结果。当执行完一个 insert 操作后,可以在同一 session 连接中执行 SHOW LAST INSERT。该命令会返回最近一次 insert 操作的结果,如:mysql> show last insert\G *************************** 1. row *************************** TransactionId: 64067 Label: insert_ba8f33aea9544866-8ed77e2844d0cc9b Database: default_cluster:db1 Table: t1 TransactionStatus: VISIBLE LoadedRows: 2 FilteredRows: 0 该命令会返回 insert 以及对应事务的详细信息。因此,用户可以在每次执行完 insert 操作后,继续执行 show last insert 命令来获取 insert 的结果。注意:该命令只会返回在同一 session 连接中,最近一次 insert 操作的结果。如果连接断开或更换了新的连接,则将返回空集。S3 Load从0.14 版本开始,Doris 支持通过 S3 协议直接从支持 S3 协议的在线存储系统导入数据。本文档主要介绍如何导入 AWS S3 中存储的数据。也支持导入其他支持 S3 协议的对象存储系统导入,如百度云的 BOS、阿里云的OSS和腾讯云的 COS 等。(1)适用场景源数据在 支持 S3 协议的存储系统中,如 S3,BOS 等。数据量在 几十到百 GB 级别。(2)准备工作准备AK 和 SK 首先需要找到或者重新生成 AWS Access keys,可以在 AWS console 的 My Security Credentials 找到生成方式;准备 REGION 和 ENDPOINT REGION 可以在创建桶的时候选择也可以在桶列表中查看到。ENDPOINT 可以通过如下页面通过 REGION 查到 AWS 文档。(3)示例导入方式和 Broker Load 基本相同,只需要将 WITH BROKER broker_name () 语句替换成如下部分WITH S3 ( "AWS_ENDPOINT" = "AWS_ENDPOINT", "AWS_ACCESS_KEY" = "AWS_ACCESS_KEY", "AWS_SECRET_KEY"="AWS_SECRET_KEY", "AWS_REGION" = "AWS_REGION" )完整示例如下:LOAD LABEL example_db.exmpale_label_1 ( DATA INFILE("s3://your_bucket_name/your_file.txt") INTO TABLE load_test COLUMNS TERMINATED BY "," ) WITH S3 ( "AWS_ENDPOINT" = "AWS_ENDPOINT", "AWS_ACCESS_KEY" = "AWS_ACCESS_KEY", "AWS_SECRET_KEY"="AWS_SECRET_KEY", "AWS_REGION" = "AWS_REGION" ) PROPERTIES ( "timeout" = "3600" ); 数据导出Export导出数据导出是 Doris 提供的一种将数据导出的功能。该功能可以将用户指定的表或分区的数据以文本的格式,通过 Broker 进程导出到远端存储上,如 HDFS/BOS 等。(1)基本原理用户提交一个 Export 作业后。Doris 会统计这个作业涉及的所有 Tablet。然后对这些Tablet 进行分组,每组生成一个特殊的查询计划。该查询计划会读取所包含的 Tablet 上的数据,然后通过 Broker 将数据写到远端存储指定的路径中,也可以通过 S3 协议直接导出到支持 S3 协议的远端存储上。a. 调度方式:用户提交一个 Export 作业到 FE。FE 的 Export 调度器会通过两阶段来执行一个 Export 作业:b. 查询计划拆分Export 作业会生成多个查询计划,每个查询计划负责扫描一部分 Tablet。每个查询计划扫描的 Tablet 个数由 FE 配置参数 export_tablet_num_per_task 指定,默认为 5。即假设一共 100 个 Tablet,则会生成 20 个查询计划。用户也可以在提交作业时,通过作业属性tablet_num_per_task 指定这个数值。c. 查询计划执行一个作业的多个查询计划顺序执行。一个查询计划扫描多个分片,将读取的数据以行的形式组织,每 1024 行为一个 batch,调用 Broker 写入到远端存储上。查询计划遇到错误会整体自动重试 3 次。如果一个查询计划重试 3 次依然失败,则整个作业失败。Doris 会首先在指定的远端存储的路径中,建立一个名为 __doris_export_tmp_12345 的临时目录(其中 12345 为作业 id)。导出的数据首先会写入这个临时目录。每个查询计划会生成一个文件,文件名示例:export-data-c69fcf2b6db5420f-a96b94c1ff8bccef-1561453713822其中 c69fcf2b6db5420f-a96b94c1ff8bccef 为查询计划的 query id。1561453713822 为文件生成的时间戳。当所有数据都导出后,Doris 会将这些文件 rename 到用户指定的路径中。(2)基本语法Export 的详细命令可以通过 HELP EXPORT 查看:EXPORT TABLE db1.tbl1 PARTITION (p1,p2) [WHERE [expr]] TO "hdfs://host/path/to/export/" PROPERTIES ( "label" = "mylabel", "column_separator"=",", "columns" = "col1,col2", "exec_mem_limit"="2147483648", "timeout" = "3600" ) WITH BROKER "hdfs" ( "username" = "user", "password" = "passwd" ); label:本次导出作业的标识。后续可以使用这个标识查看作业状态。column_separator:列分隔符。默认为 \t。支持不可见字符,比如 ‘\x07’。columns:要导出的列,使用英文状态逗号隔开,如果不填这个参数默认是导出表的所有列。line_delimiter:行分隔符。默认为 \n。支持不可见字符,比如 ‘\x07’。exec_mem_limit: 表示 Export 作业中,一个查询计划在单个 BE 上的内存使用限制。默认 2GB。单位字节。timeout:作业超时时间。默认 2 小时。单位秒。tablet_num_per_task:每个查询计划分配的最大分片数。默认为 5(3)导出示例启动 hadoop 集群执行导出export table example_site_visit2 to "hdfs://mycluster/doris-export" PROPERTIES ( "label" = "mylabel", "column_separator"="|", "timeout" = "3600" ) WITH BROKER "broker_name" ( #HDFS 开启 HA 需要指定,还指定其他参数 "dfs.nameservices"="mycluster", "dfs.ha.namenodes.mycluster"="nn1,nn2,nn3", "dfs.namenode.rpc-address.mycluster.nn1"= "hadoop1:8020", "dfs.namenode.rpc-address.mycluster.nn2"= "hadoop2:8020", "dfs.namenode.rpc-address.mycluster.nn3"="hadoop3:8020", "dfs.client.failover.proxy.provider.mycluster"="org.apache.hadoop .hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider" ); 导出之后查看 hdfs 对应路径,会多出许多文件(4)查询导出作业状态提交作业后,可以通过 SHOW EXPORT 命令查询导出作业状态。结果举例如下:JobId: 14008 Label: mylabel State: FINISHED Progress: 100% TaskInfo: {"partitions":["*"],"exec mem limit":2147483648,"column separator":",","line delimiter":"\n","tablet num":1,"broker":"hdfs","coord num":1,"db":"default_cluster:db1","tbl":"tbl3"} Path: bos://bj-test-cmy/export/ CreateTime: 2019-06-25 17:08:24 StartTime: 2019-06-25 17:08:28 FinishTime: 2019-06-25 17:08:34 Timeout: 3600 ErrorMsg: N/A JobId:作业的唯一 IDLabel:自定义作业标识State:作业状态:PENDING:作业待调度EXPORTING:数据导出中FINISHED:作业成功CANCELLED:作业失败Progress:作业进度。该进度以查询计划为单位。假设一共 10 个查询计划,当前已完成 3 个,则进度为 30%。TaskInfo:以 Json 格式展示的作业信息:db:数据库名tbl:表名partitions:指定导出的分区。* 表示所有分区。exec mem limit:查询计划内存使用限制。单位字节。column separator:导出文件的列分隔符。line delimiter:导出文件的行分隔符。tablet num:涉及的总 Tablet 数量。broker:使用的 broker 的名称。coord num:查询计划的个数。Path:远端存储上的导出路径。CreateTime/StartTime/FinishTime:作业的创建时间、开始调度时间和结束时间。Timeout:作业超时时间。单位是秒。该时间从 CreateTime 开始计算。ErrorMsg:如果作业出现错误,这里会显示错误原因(5)注意事项不建议一次性导出大量数据。一个 Export 作业建议的导出数据量最大在几十 GB。过大的导出会导致更多的垃圾文件和更高的重试成本。如果表数据量过大,建议按照分区导出。在 Export 作业运行过程中,如果 FE 发生重启或切主,则 Export 作业会失败,需要用户重新提交。如果 Export 作业运行失败,在远端存储中产生的 doris_export_tmp_xxx 临时目录,以及已经生成的文件不会被删除,需要用户手动删除。如果 Export 作业运行成功,在远端存储中产生的 __doris_export_tmp_xxx 目录,根据远端存储的文件系统语义,可能会保留,也可能会被清除。比如在百度对象存储(BOS)中,通过 rename 操作将一个目录中的最后一个文件移走后,该目录也会被删除。如果该目录没有被清除,用户可以手动清除。当 Export 运行完成后(成功或失败),FE 发生重启或切主,则 SHOW EXPORT 展示的作业的部分信息会丢失,无法查看。Export 作业只会导出 Base 表的数据,不会导出 Rollup Index 的数据。Export 作业会扫描数据,占用 IO 资源,可能会影响系统的查询延迟。查询结果导出SELECT INTO OUTFILE 语句可以将查询结果导出到文件中。目前支持通过 Broker 进 程, 通过 S3 协议, 或直接通过 HDFS 协议,导出到远端存储,如 HDFS,S3,BOS,COS(腾讯云)上。(1)语法语法如下:query_stmt INTO OUTFILE "file_path" [format_as] [properties] file_pathfile_path 指向文件存储的路径以及文件前缀。如 hdfs://path/to/my_file_。最终的文件名将由 my_file_,文件序号以及文件格式后缀组成。其中文件序号由 0 开始,数量为文件被分割的数量。如:my_file_abcdefg_0.csvmy_file_abcdefg_1.csvmy_file_abcdegf_2.csv[format_as]FORMAT AS CSV 指定导出格式。默认为 CSV。[properties]指定相关属性。目前支持通过 Broker 进程, 或通过 S3 协议进行导出。Broker 相关属性需加前缀 broker.。具体参阅 Broker 文档。HDFS 相关属性需加前缀 hdfs. 其中 hdfs.fs.defaultFS 用于填写 namenode 地址和端口。属于必填项。S3 协议则直接执行 S3 协议配置即可。示例:("broker.prop_key" = "broker.prop_val", ...) or ("hdfs.fs.defaultFS" = "xxx", "hdfs.hdfs_user" = "xxx") or ("AWS_ENDPOINT" = "xxx", ...) 其它属性:("key1" = "val1", "key2" = "val2", ...)目前支持以下属性:column_separator:列分隔符,仅对 CSV 格式适用。默认为 \t。line_delimiter:行分隔符,仅对 CSV 格式适用。默认为 \n。max_file_size:单个文件的最大大小。默认为 1GB。取值范围在 5MB 到 2GB 之间。超过这个大小的文件将会被切分。schema:PARQUET 文件 schema 信息。仅对 PARQUET 格式适用。导出文件格式为 PARQUET 时,必须指定 schema。(2)并发导出并发导出的条件:默认情况下,查询结果集的导出是非并发的,也就是单点导出。如果用户希望查询结果集可以并发导出,需要满足以下条件:session variable ‘enable_parallel_outfile’ 开启并发导出:set enable_parallel_outfile = true;导出方式为 S3 , 或者 HDFS,而不是使用 broker;查询可以满足并发导出的需求,比如顶层不包含 sort 等单点节点。(后面会举例说明,哪种属于不可并发导出结果集的查询)满足以上三个条件,就能触发并发导出查询结果集了。并发度 = be_instacne_num * parallel_fragment_exec_instance_num验证结果集被并发导出。用户通过 session 变量设置开启并发导出后,如果想验证当前查询是否能进行并发导出,则可以通过下面这个方法.explain select xxx from xxx where xxx into outfile "s3://xxx" format as csv properties ("AWS_ENDPOINT" = "xxx", ...); 对查询进行 explain 后,Doris 会返回该查询的规划,如果发现 RESULT FILE SINK 出现在 PLAN FRAGMENT 1 中,就说明导出并发开启成功了。如果 RESULT FILE SINK 出现在PLAN FRAGMENT 0 中,则说明当前查询不能进行并发导出(当前查询不同时满足并发导出的三个条件)。(3)使用示例**示例一:**使用 broker 方式,将简单查询结果导出SELECT * FROM example_site_visit INTO OUTFILE "hdfs://hadoop1:8020/doris-out/broker_a_" FORMAT AS CSV PROPERTIES ( "broker.name" = "broker_name", "column_separator" = ",", "line_delimiter" = "\n", "max_file_size" = "100MB" ); 最终生成文件如如果不大于 100MB,则为:result_0.csv。如果大于 100MB,则可能为 result_0.csv, result_1.csv, ...。**示例二:**使用 broker 方式,指定导出格式为 PARQUETSELECT city, age FROM example_site_visit INTO OUTFILE "hdfs://hadoop1:8020/doris-out/broker_b_" FORMAT AS PARQUET PROPERTIES ( "broker.name" = "broker_name", "schema"="required,byte_array,city;required,int32,age" );查询结果导出到 parquet 文件需要明确指定schema。**示例三:**使用 HDFS 方式导出SELECT * FROM example_site_visit INTO OUTFILE "hdfs://doris-out/hdfs_" FORMAT AS CSV PROPERTIES ( "hdfs.fs.defaultFS" = "hdfs://hadoop1:8020", "hdfs.hdfs_user" = "atguigu", "column_separator" = "," );最终生成文件如如果不大于 100MB,则为:result_0.csv。如果大于 100MB,则可能为 result_0.csv, result_1.csv, ...。**示例四:**使用 HDFS 方式导出,开启并发导出set enable_parallel_outfile = true; EXPLAIN SELECT * FROM example_site_visit INTO OUTFILE "hdfs://doris-out/hdfs_" FORMAT AS CSV PROPERTIES ( "hdfs.fs.defaultFS" = "hdfs://hadoop1:8020", "hdfs.hdfs_user" = "atguigu", "column_separator" = "," );**示例五:**将 CTE 语句的查询结果导出到文件 hdfs://path/to/result.txt。默认导出格式为CSV。使用my_broker并设置 HDFS 高可用信息。使用默认的行列分隔符。WITH x1 AS (SELECT k1, k2 FROM tbl1), x2 AS (SELECT k3 FROM tbl2) SELEC k1 FROM x1 UNION SELECT k3 FROM x2 INTO OUTFILE "hdfs://path/to/result_" PROPERTIES ( "broker.name" = "my_broker", "broker.username"="user", "broker.password"="passwd", "broker.dfs.nameservices" = "my_ha", "broker.dfs.ha.namenodes.my_ha" = "my_namenode1, my_namenode2", "broker.dfs.namenode.rpc-address.my_ha.my_namenode1" = "nn1_host:rpc_port", "broker.dfs.namenode.rpc-address.my_ha.my_namenode2" = "nn2_host:rpc_port", "broker.dfs.client.failover.proxy.provider" = "org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProx yProvider" );最终生成文件如如果不大于 1GB,则为:result_0.csv。如果大于 1GB,则可能为 result_0.csv, result_1.csv, ...。**示例六:**将 UNION 语句的查询结果导出到文件bos://bucket/result.txt。指定导出格式为PARQUET。使用my_broker并设置 HDFS 高可用信息。PARQUET 格式无需指定列分割符。导出完成后,生成一个标识文件。SELECT k1 FROM tbl1 UNION SELECT k2 FROM tbl1 INTO OUTFILE "bos://bucket/result_" FORMAT AS PARQUET PROPERTIES ( "broker.name" = "my_broker", "broker.bos_endpoint" = "http://bj.bcebos.com", "broker.bos_accesskey" = "xxxxxxxxxxxxxxxxxxxxxxxxxx", "broker.bos_secret_accesskey" = "yyyyyyyyyyyyyyyyyyyyyyyyyy", "schema"="required,int32,k1;required,byte_array,k2" );**示例七:**将 select 语句的查询结果导出到文件 cos://${bucket_name}/path/result.txt。指定导出格式为 csv。导出完成后,生成一个标识文件。select k1,k2,v1 from tbl1 limit 100000 into outfile "s3a://my_bucket/export/my_file_" FORMAT AS CSV PROPERTIES ( "broker.name" = "hdfs_broker", "broker.fs.s3a.access.key" = "xxx", "broker.fs.s3a.secret.key" = "xxxx", "broker.fs.s3a.endpoint" = "https://cos.xxxxxx.myqcloud.com/", "column_separator" = ",", "line_delimiter" = "\n", "max_file_size" = "1024MB", "success_file_name" = "SUCCESS" )最终生成文件如如果不大于 1GB,则为:my_file_0.csv。如果大于 1GB,则可能为 my_file_0.csv, result_1.csv, ...。在 cos 上验证:① 不存在的 path 会自动创建② access.key/secret.key/endpoint 需要和 cos 的同学确认。尤其是 endpoint 的值,不需要填写 bucket_name。**示例八:**使用 s3 协议导出到 bos,并且并发导出开启:set enable_parallel_outfile = true; select k1 from tb1 limit 1000 into outfile "s3://my_bucket/export/my_file_" format as csv properties ( "AWS_ENDPOINT" = "http://s3.bd.bcebos.com", "AWS_ACCESS_KEY" = "xxxx", "AWS_SECRET_KEY" = "xxx", "AWS_REGION" = "bd" )最终生成的文件前缀为 my_file_{fragment_instance_id}_。**示例九:**使用 s3 协议导出到 bos,并且并发导出 session 变量开启。注意:但由于查询语句带了一个顶层的排序节点,所以这个查询即使开启并发导出的session 变量,也是无法并发导出的。set enable_parallel_outfile = true; select k1 from tb1 order by k1 limit 1000 into outfile "s3://my_bucket/export/my_file_" format as csv properties ( "AWS_ENDPOINT" = "http://s3.bd.bcebos.com", "AWS_ACCESS_KEY" = "xxxx", "AWS_SECRET_KEY" = "xxx", "AWS_REGION" = "bd" )mysqldump 导出Doris 1.0 支持通过 mysqldump 工具导出数据或者表结构,下面几种操作:导出 test 数据库中的 user 表:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test_db --tables user > dump1.sql 导出 test_db 数据库中的 user 表结构:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test_db --tables user --no-data > dump2.sql 导出 test_db 数据库中所有表:mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --databases test_db 导出所有数据库和表mysqldump -h127.0.0.1 -P9030 -uroot --no-tablespaces --all-databases 导出的结果可以重定向到文件中,之后可以通过 source 命令导入到 Doris 中source /opt/module/doris-1.0.0/dump1.sql 数据备份及恢复Doris 支持将当前数据以文件的形式,通过 broker 备份到远端存储系统中。之后可以通过 恢复 命令,从远端存储系统中将数据恢复到任意 Doris 集群。通过这个功能,Doris 可以支持将数据定期的进行快照备份。也可以通过这个功能,在不同集群间进行数据迁移。该功能需要 Doris 版本 0.8.2+使用该功能,需要部署对应远端存储的 broker。如 BOS、HDFS 等。可以通过 SHOW BROKER; 查看当前部署的 broker。简要原理说明备份(Backup)备份操作是将指定表或分区的数据,直接以 Doris 存储的文件的形式,上传到远端仓库中进行存储。当用户提交 Backup 请求后,系统内部会做如下操作:(1)快照及快照上传快照阶段会对指定的表或分区数据文件进行快照。之后,备份都是对快照进行操作。在快照之后,对表进行的更改、导入等操作都不再影响备份的结果。快照只是对当前数据文件产生一个硬链,耗时很少。快照完成后,会开始对这些快照文件进行逐一上传。快照上传由各个 Backend 并发完成。(2)元数据准备及上传数据文件快照上传完成后,Frontend 会首先将对应元数据写成本地文件,然后通过broker 将本地元数据文件上传到远端仓库。完成最终备份作业。恢复(Restore)恢复操作需要指定一个远端仓库中已存在的备份,然后将这个备份的内容恢复到本地集群中。当用户提交 Restore 请求后,系统内部会做如下操作:(1)在本地创建对应的元数据这一步首先会在本地集群中,创建恢复对应的表分区等结构。创建完成后,该表可见,但是不可访问。(2)本地 snapshot这一步是将上一步创建的表做一个快照。这其实是一个空快照(因为刚创建的表是没有数据的),其目的主要是在 Backend 上产生对应的快照目录,用于之后接收从远端仓库下载的快照文件。(3)下载快照远端仓库中的快照文件,会被下载到对应的上一步生成的快照目录中。这一步由各个Backend 并发完成。(4)生效快照快照下载完成后,我们要将各个快照映射为当前本地表的元数据。然后重新加载这些快照,使之生效,完成最终的恢复作业。最佳实践(1)备份当前支持最小分区(Partition)粒度的全量备份(增量备份有可能在未来版本支持)。如果需要对数据进行定期备份,首先需要在建表时,合理的规划表的分区及分桶,比如按时间进行分区。然后在之后的运行过程中,按照分区粒度进行定期的数据备份。(2)数据迁移用户可以先将数据备份到远端仓库,再通过远端仓库将数据恢复到另一个集群,完成数据迁移。因为数据备份是通过快照的形式完成的,所以,在备份作业的快照阶段之后的新的导入数据,是不会备份的。因此,在快照完成后,到恢复作业完成这期间,在原集群上导入的数据,都需要在新集群上同样导入一遍。建议在迁移完成后,对新旧两个集群并行导入一段时间。完成数据和业务正确性校验后,再将业务迁移到新的集群。(3)重点说明备份恢复相关的操作目前只允许拥有 ADMIN 权限的用户执行。一个 Database 内,只允许有一个正在执行的备份或恢复作业。备份和恢复都支持最小分区(Partition)级别的操作,当表的数据量很大时,建议按分区分别执行,以降低失败重试的代价。因为备份恢复操作,操作的都是实际的数据文件。所以当一个表的分片过多,或者一个分片有过多的小版本时,可能即使总数据量很小,依然需要备份或恢复很长时间。用户可以通过 SHOW PARTITIONS FROM table_name; 和 SHOW TABLET FROM table_name; 来查看各个分区的分片数量,以及各个分片的文件版本数量,来预估作业执行时间。文件数量对作业执行的时间影响非常大,所以建议在建表时,合理规划分区分桶,以避免过多的分片。当通过 SHOW BACKUP 或者 SHOW RESTORE 命令查看作业状态时。有可能会在 TaskErrMsg 一列中看到错误信息。但只要 State 列不为 CANCELLED,则说明作业依然在继续。这些 Task 有可能会重试成功。当然,有些 Task 错误,也会直接导致作业失败。如果恢复作业是一次覆盖操作(指定恢复数据到已经存在的表或分区中),那么从恢复作业的 COMMIT 阶段开始,当前集群上被覆盖的数据有可能不能再被还原。此时如果恢复作业失败或被取消,有可能造成之前的数据已损坏且无法访问。这种情况下,只能通过再次执行恢复操作,并等待作业完成。因此,我们建议,如无必要,尽量不要使用覆盖的方式恢复数据,除非确认当前数据已不再使用。备份(1)创建一个远端仓库路径CREATE REPOSITORY `hdfs_ods_dw_backup` WITH BROKER `broker_name` ON LOCATION "hdfs://hadoop1:8020/tmp/doris_backup" PROPERTIES ( "username" = "", "password" = "" )(2)执行备份BACKUP SNAPSHOT [db_name].{snapshot_name} TO `repository_name` ON ( `table_name` [PARTITION (`p1`, ...)], ... ) PROPERTIES ("key"="value", ...);示例:BACKUP SNAPSHOT test_db.backup1 TO hdfs_ods_dw_backup ON ( table1 );(3)查看备份任务SHOW BACKUP [FROM db_name](4)查看远端仓库镜像语法:SHOW SNAPSHOT ON `repo_name` [WHERE SNAPSHOT = "snapshot" [AND TIMESTAMP = "backup_timestamp"]];示例一:查看仓库 hdfs_ods_dw_backup 中已有的备份:SHOW SNAPSHOT ON hdfs_ods_dw_backup; 示例二:仅查看仓库 hdfs_ods_dw_backup 中名称为 backup1 的备份:SHOW SNAPSHOT ON hdfs_ods_dw_backup WHERE SNAPSHOT = "backup1";示例三:查看仓库 hdfs_ods_dw_backup 中名称为 backup1 的备份,时间版本为 “2021-05-05-15-34-26” 的详细信息:SHOW SNAPSHOT ON hdfs_ods_dw_backup WHERE SNAPSHOT = "backup1" AND TIMESTAMP = "2021-05-05-15-34-26";(5)取消备份取消一个正在执行的备份作业语法:CANCEL BACKUP FROM db_name;示例:取消 test_db 下的 BACKUP 任务CANCEL BACKUP FROM test_db;恢复将之前通过 BACKUP 命令备份的数据,恢复到指定数据库下。该命令为异步操作。提交成功后,需通过 SHOW RESTORE 命令查看进度。仅支持恢复 OLAP 类型的表支持一次恢复多张表,这个需要和你对应的备份里的表一致(1)使用用法RESTORE SNAPSHOT [db_name].{snapshot_name} FROM `repository_name` ON ( `table_name` [PARTITION (`p1`, ...)] [AS `tbl_alias`], ... ) PROPERTIES ("key"="value", ...);说明:同一数据库下只能有一个正在执行的 BACKUP 或 RESTORE 任务。ON 子句中标识需要恢复的表和分区。如果不指定分区,则默认恢复该表的所有分区。所指定的表和分区必须已存在于仓库备份中可以通过 AS 语句将仓库中备份的表名恢复为新的表。但新表名不能已存在于数据库中。分区名称不能修改。可以将仓库中备份的表恢复替换数据库中已有的同名表,但须保证两张表的表结构完全一致。表结构包括:表名、列、分区、Rollup 等等。可以指定恢复表的部分分区,系统会检查分区 Range 或者 List 是否能够匹配。PROPERTIES 目前支持以下属性:“backup_timestamp” = “2018-05-04-16-45-08”:指定了恢复对应备份的哪个时间版本,必填。该信息可以通过 SHOW SNAPSHOT ON repo; 语句获得。“replication_num” = “3”:指定恢复的表或分区的副本数。默认为 3。若恢复已存在的表或分区,则副本数必须和已存在表或分区的副本数相同。同时,必须有足够的host 容纳多个副本。“timeout” = “3600”:任务超时时间,默认为一天。单位秒。“meta_version” = 40:使用指定的 meta_version 来读取之前备份的元数据。注意,该参数作为临时方案,仅用于恢复老版本 Doris 备份的数据。最新版本的备份数据中已经包含 meta version,无需再指定。(2)使用示例示例一从 example_repo 中恢复备份 snapshot_1 中的表 backup_tbl 到数据库 example_db1,时间版本为 “2021-05-04-16-45-08”。恢复为 1 个副本:RESTORE SNAPSHOT example_db1.`snapshot_1` FROM `example_repo` ON ( `backup_tbl` ) PROPERTIES ( "backup_timestamp"="2021-05-04-16-45-08", "replication_num" = "1" ); 示例二从 example_repo 中恢复备份 snapshot_2 中的表 backup_tbl 的分区 p1,p2,以及表backup_tbl2 到数据库 example_db1,并重命名为 new_tbl,时间版本为 “2021-05-04-17-11-01”。默认恢复为 3 个副本:RESTORE SNAPSHOT example_db1.`snapshot_2` FROM `example_repo` ON ( `backup_tbl` PARTITION (`p1`, `p2`), `backup_tbl2` AS `new_tbl` ) PROPERTIES ( "backup_timestamp"="2021-05-04-17-11-01" );演示RESTORE SNAPSHOT test_db.backup1 FROM `hdfs_ods_dw_backup` ON ( table1 AS table_restore ) PROPERTIES ( "backup_timestamp"="2022-04-01-16-45-19" );(3)查看恢复任务可以通过下面的语句查看数据恢复的情况:SHOW RESTORE [FROM db_name](4)取消恢复下面的语句用于取消一个正在执行数据恢复的作业:CANCEL RESTORE FROM db_name;当取消处于 COMMIT 或之后阶段的恢复左右时,可能导致被恢复的表无法访问。此时只能通过再次执行恢复作业进行数据恢复。删除远端仓库该语句用于删除一个已创建的仓库。仅 root 或 superuser 用户可以删除仓库。这里的用户是指 Doris 的用户 语法:DROP REPOSITORY `repo_name`;说明:删除仓库,仅仅是删除该仓库在 Doris 中的映射,不会删除实际的仓库数据。删除后,可以再次通过指定相同的 broker 和 LOCATION 映射到该仓库。
0
0
0
浏览量268
攻城狮无远

【Databend】多表联结,你不会还没有掌握吧!

概述和数据准备多表联结是两个或多个表的列合并到一个结果集中。Databend 中支持的连接类型有 inner join 、cross join 、natural join 、left join 、right join 、left anti join 、right anti join 、full outer join。在这里我们只讲最常见的几种,其中 inner join 、left join 、right join 最常用。数据准备:drop table if exists vip_info; create table if not exists vip_info ( client_id int, region varchar ); drop table if exists purchase_records; create table if not exists purchase_records ( client_id int, item varchar, qty int ); drop table if exists gift; create table if not exists gift ( gift varchar ); insert into vip_info values (101, 'Toronto'), (102, 'Quebec'), (103, 'Vancouver'); insert into purchase_records values (100, 'Croissant', 2000), (102, 'Donut', 3000), (103, 'Coffee', 6000), (106, 'Soda', 4000); insert into gift values ('Croissant'), ('Donut'), ('Coffee'), ('Soda'); 内连接内连接使用 inner join 语法,其中 inner 可以省略,返回满足结果集中连接条件的行。当列相同时,可以将 on 转化成 using 来简化语法。需求:根据准备的数据,返回VIP客户的购买记录。select t2.client_id , t2.item , t2.qty from vip_info as t1 join purchase_records as t2 using (client_id);交叉连接交叉连接使用 cross join 语法,也可以使用 inner join 不加连接条件实现,返回一个结果集,该结果集包括第一个表中的每一行,与第二个表中的每一行连接。需求:将每个礼品选项分配给每个VIP客户。-- 方法一:使用 cross join select t1.*,t2.* from vip_info as t1 cross join gift as t2; -- 方法二:使用 inner join 但是没有加匹配条件 select t1.*,t2.* from vip_info as t1 join gift as t2;左连接左连接使用 left join 语法,返回左表中的所有记录,以及右表中的匹配记录。如果没有匹配,结果是右侧的 NULL 记录。需求:返回所有VIP客户的购买记录,如果VIP客户没有购买,购买记录将为 NULL。select t1.*,t2.* from vip_info as t1 left join purchase_records as t2 using (client_id); 右连接左连接使用 right join 语法,返回右表中的所有记录,以及左表中匹配的记录。如果没有匹配,结果是左侧的 NULL 记录。需求:返回所有客户的购买记录,如果不是VIP客户,VIP客户信息将为 NULL。select t1.*,t2.* from vip_info as t1 right join purchase_records as t2 on t1.client_id = t2.client_id; 左反和右反连接左侧反连接使用 left anti join 语法,从左侧表中返回右侧表中没有匹配行的行。这是 Databend 的语法,建议使用 left join 配合 where 实现。-- 方法一:使用 left anti join 实现,只能返回 t1 表的信息 select * from vip_info as t1 left anti join purchase_records as t2 on t1.client_id = t2.client_id; -- 方法二:使用 left join 配合 where 实现 select t1.*, t2.* from vip_info as t1 left join purchase_records as t2 on t1.client_id = t2.client_id where t2.client_id is null; +-----------+---------+-----------+------+-----+ | client_id | region | client_id | item | qty | +-----------+---------+-----------+------+-----+ | 101 | Toronto | NULL | NULL | NULL| +-----------+---------+-----------+------+-----+ 右反连接使用 right anti join 语法,从右表返回左表中没有匹配行的行。这是 Databend 的语法,建议使用 left join 配合 where 实现。-- 方法一:使用 left anti join 实现,只能返回 t1 表的信息 select * from vip_info as t1 right anti join purchase_records as t2 on t1.client_id = t2.client_id; -- 方法二:使用 left join 配合 where 实现 select t1.*, t2.* from vip_info as t1 right join purchase_records as t2 on t1.client_id = t2.client_id where t1.client_id is null; +-----------+---------+-----------+-----------+-----+ | client_id | region | client_id | item | qty | +-----------+---------+-----------+-----------+-----+ | NULL | NULL | 100 | Croissant | 2000| +-----------+---------+-----------+-----------+-----+ | NULL | NULL | 106 | Soda | 4000| +-----------+---------+-----------+-----------+-----+全连接全连接使用 full outer join 返回两个表中的所有行,在可以匹配的地方匹配行,并在不存在匹配行的地方放置NULL。-- 方法一:使用 full outer join 语法 select t1.*, t2.* from vip_info as t1 full outer join purchase_records as t2 on t1.client_id = t2.client_id; -- 方法二:使用 left join、right join 和 union 实现 select t1.*, t2.* from vip_info as t1 left join purchase_records as t2 on t1.client_id = t2.client_id union select t1.*, t2.* from vip_info as t1 right join purchase_records as t2 on t1.client_id = t2.client_id;总结多表联结语法很简单,不同数据库可能语法不一样,但只要掌握 inner join、left join 、right join 三种语法,配合 where 或者 union 基本上可以解决所有相关的多表连接问题,赶紧实践动起来。参考资料:Databend JOIN :https://docs.databend.com/guides/query/join
0
0
0
浏览量553
攻城狮无远

【Databend】分组集:教你如何快速分组汇总

分组集定义和数据准备分组集是多个分组的并集,用于在一个查询中,按照不同的分组列对集合进行聚合运算,等价于对单个分组使用"union all",计算多个结果集的并集。Databend 常见的分组集有三种 grouping sets 、rollup 、cube 。数据准备:drop table if exists sales_data; create table if not exists sales_data ( region varchar(255), product varchar(255), sales_amount int ); insert into sales_data (region, product, sales_amount) values ('North', 'WidgetA', 200), ('North', 'WidgetB', 300), ('South', 'WidgetA', 400), ('South', 'WidgetB', 100), ('West', 'WidgetA', 300), ('West', 'WidgetB', 200);group by grouping setsgroup by grouping sets 是 group by 子句的强大扩展,允许在单个语句中计算多个 group by子句,组集是一组维度列。效果等同于同一结果集中两个或多个 group by 操作的 union all:group by grouping sets((a))等同于单分组集操作 group by a。group by grouping sets((a),(a,b))等同于 group by a union all group by a,b。基础语法:select ... from ... [ ... ] group by grouping sets ( groupset [ , groupset [ , ... ] ] ) [ ... ] -- groupset ::= { <column_alias> | <position> | <expr> }其中,column_alias 表示列的别名,position 表示 select 中列的位置,expr 表示当前范围内表上的任何表达式。根据准备的数据,需求是统计区域销量和产品销量。-- 方法一:使用 group by grouping sets 语法 select region , product , sum(sales_amount) as total_sales from sales_data group by grouping sets(region, product) order by region, product; -- 方法二:使用 union all select region ,null as product , sum(sales_amount) as total_sales from sales_data group by region union all select null as region , product , sum(sales_amount) as total_sales from sales_data group by product;根据准备的数据,需求是在原数据的基础上,统计区域销量和产品销量。select region , product , sum(sales_amount) as total_sales from sales_data group by grouping sets(region, product,(region, product)) order by region, product;group by rollupgroup by rollup 子句会在分组的基础上产生小计行以及总计行,语法如下:select ... from ... [ ... ] group by rollup ( grouprollup [ , grouprollup [ , ... ] ] ) [ ... ] -- grouprollup ::= { <column_alias> | <position> | <expr> }其中,column_alias 表示列的别名,position 表示 select 中列的位置,expr 表示当前范围内表上的任何表达式。根据准备的数据,需求是在原数据的基础上,统计区域下产品销量小计和总计数据。-- 方法一:使用 group by rollup 语法 select region , product , sum(sales_amount) as total_sales from sales_data group by rollup(region, product) order by region, product; -- 方法二:union all select region , product , sum(sales_amount) as total_sales from sales_data group by region,product union all select region ,null as product , sum(sales_amount) as total_sales from sales_data group by region union all select null as region , null as product , sum(sales_amount) as total_sales from sales_data order by region, product;这种汇总方式在分析看板里经常看到,比如 Power BI 和 Tableau 中做表格时,可以选择小计和总计。可以看到使用 group by rollup 子句能快速实现汇总,代码也简洁。group by cubegroup by cube 子句类似 group by rollup 子句,除了生成 group by rollup 子句的所有行外,还会多一些维度,对所有列交叉分组汇总。select ... from ... [ ... ] group by cube ( groupcube [ , groupcube [ , ... ] ] ) [ ... ] -- groupcube ::= { <column_alias> | <position> | <expr> }其中,column_alias 表示列的别名,position 表示 select 中列的位置,expr 表示当前范围内表上的任何表达式。根据准备的数据,需求是在原数据基础上分析所有可能情况的销售汇总。-- 方法一:使用 group by cube 语法 select region , product , sum(sales_amount) as total_sales from sales_data group by cube(region, product) order by region, product; -- 方法二:使用 group by grouping sets 子句和 union all 结合 select region , product , sum(sales_amount) as total_sales from sales_data group by grouping sets(region, product,(region, product)) union all select null as region , null as product , sum(sales_amount) as total_sales from sales_data order by region, product;总结Databend 中 grouping sets、rollup、cube 都是对 group by 的扩展,相对于 union all 来看,代码较简洁,效率也高,可以试着在实际工作中多用用,如果不支持或者理不清,使用 union all 实现的效果也是一样的。参考资料:Databend Group Bys:https://docs.databend.com/guides/query/groupby/
0
0
0
浏览量1017
攻城狮无远

Databend 技巧与实践指南

本专栏将介绍 Databend 的安装配置和使用、数据类型、数据库和表操作、日期时间函数、基础函数应用、行列转化(一行变多行和简单分列,数据透视和逆透视)、merge 的用法、多表联结以及分组集等核心话题。通过学习本专栏,读者将能够掌握 Databend 的基础和高阶技巧和最佳实践,更好地应用 Databend 来解决实际问题
0
0
0
浏览量1193
攻城狮无远

Doris 全方位解析

本专栏为 Doris 用户提供系统梳理和实践指导,深入介绍 Doris 的架构原理、数据表操作、查询语言、数据导入与导出、集成与优化、监控与报警、索引机制等核心内容。通过详细的示例和操作步骤,助 readers 快速掌握 Doris 的各项功能,提升数据处理和分析能力。在学习过程中, readers 将获得自主构建数据管道和优化系统性能的能力,为企业带来更高效、可靠的数据服务。
0
0
0
浏览量1296
攻城狮无远

《云数据湖》第五章:优化云数据湖架构以提升性能

第五章:优化云数据湖架构以提升性能简约是终极的复杂性。 ——莱昂纳多·达·芬奇性能可以简单地定义为工作完成的及时性。话虽如此,在云服务方面,性能可能是最复杂的术语之一,因为没有单一的性能度量标准。在本章中,我们将逐步介绍性能的各个方面,建立对性能的良好理解,包括云数据湖中性能衡量的各个维度,以及帮助优化和调整云数据湖以实现最佳性能的策略。我们还将使用Klodars Corporation作为示例,阐述这些概念和策略。衡量性能的基本原理当谈论性能时,可以有一定的自信地说,你是在考虑与速度相关的事物,例如一个跑者创下个人纪录冲过终点线。共同的目标是努力完成任务并实现期望的结果,满足或超越观众的期望。类似地,在云数据湖中,性能指的是为任务设定目标并确保任务在设定的目标内完成的过程。任务的性能有两个方面,任何性能指标都需要包含这两个要素:响应时间任务完成需要多长时间?吞吐量产生了多少输出?让我们以之前在“我们日常生活中的规模”中看到的制作三明治的例子为例。在那一部分中,我们看到了制作三明治的两种架构——端到端执行方法(如图5-1所示)和流水线方法(如图5-2所示)。让我们以这些例子来尝试衡量性能。性能的目标和指标正如我所说,性能取决于两个因素:响应时间和吞吐量。为了衡量性能,我们需要设定一个目标。在三明治制作的例子中,目标是制作五个三明治。接下来,我们需要确定一组相对容易测量的指标。为了简单起见,我们假设制作三明治的工人速度相同,并且在任务之间没有过渡时间。我们将考虑可变和恒定的指标:可变指标 以下指标是可变的,即您可以通过添加或删除来调整或更改的指标:工人数量 = 五个烤面包机数量 = 一个,可以容纳四片面包花生酱罐数量 = 一个果酱罐数量 = 一个面包袋数量 = 一个袋子数量 = 五个(需要制作的三明治数量)恒定指标 这些指标在您改变可变指标时保持相对恒定。在这个例子中,可变指标是完成每个步骤所需的时间:步骤1,烤面包:30秒步骤2,涂抹花生酱:5秒步骤3,涂抹果酱:5秒步骤4,将它们组合在一起:1秒步骤5,装袋:4秒由于资源有限,会出现某人需要等待资源释放后才能执行任务的情况,因此你会发现五个三明治将在不同的时间完成。现在,你是否想知道这本书中是否意外添加了烹饪书籍的页面?如果是的话,不用担心。在云数据湖架构中,存储、网络和计算等组件与烤面包机或花生酱罐类似,它们需要一定的时间来执行操作,这对于大数据处理场景非常重要。通过优化执行操作的时间并控制这些组件的单位,您可以优化整体的云数据湖性能。我在解释这些概念时以制作三明治为背景的目的是使它们易于可视化,并在云数据湖架构的背景下应用。
0
0
0
浏览量1321
攻城狮无远

《云数据湖》第二章:云上的大数据架构

第二章 云上的大数据架构大数据可能意味着更多的信息,但也意味着更多的虚假信息。 ---纳西姆·塔勒布正如我们在第1章中学到的那样,关于云数据湖有两个关键要点,为本章奠定了基础:数据湖方法从存储和处理任何类型的数据开始,无论数据的来源、大小或结构如何,从而使组织能够从许多不同的数据源中提取高价值洞察力,这些数据具有可变的价值密度(即信噪比)。在云上构建您的数据湖涉及到一种解聚架构,您将不同的IaaS、PaaS和SaaS组件组合在一起。重要的是要记住,在构建云数据湖解决方案时,您还有很多架构选项,每种架构都有其独特的优势。这篇关于Future.com的文章提供了现代数据架构的综合概述。在本章中,我们将深入探讨一些常见的架构模式,了解它们是什么以及它们各自的优势如何适用于一个名为Klodars Corporation的虚构组织。为什么Klodars Corporation选择迁移到云端Klodars Corporation是一家在太平洋西北地区销售雨具和其他用品的蓬勃发展的公司。其业务的快速增长推动了其迁移到云端的原因如下:在本地系统上运行的数据库无法再与业务的快速增长相适应。随着业务的增长,团队也在扩大。销售和市场团队发现他们的应用程序变得越来越慢,有时甚至会超时,这是因为同时使用系统的用户数量增加。市场部门希望在社交媒体上更好地定位其营销活动,并正在探索利用影响力人物的想法,但不知道从何处开始或如何进行。销售部门无法迅速扩大与分布在三个州的客户的合作,因此很难确定首先要与哪种零售客户和批发分销商合作。投资者对业务增长表示赞赏,并询问首席执行官如何扩展Klodars Corporation的业务范围。首席执行官需要制定扩展战略。软件开发团队的积极领导者Alice向Klodars Corporation的首席执行官和首席技术官提出了一个想法,即探索云计算,并了解其他企业如何利用数据湖方法解决他们所面临的挑战。她还收集了一些数据,展示了云数据湖方法可以带来的机遇,包括以下内容:云计算可以根据公司不断增长的需求进行弹性扩展,由于按消耗付费,因此不需要过度配置硬件以预算应对高峰季节,并且硬件在其他时间不需要闲置。基于云的数据湖和数据仓库可以进行扩展,以支持不断增长的并发用户数量。云数据湖提供了处理来自各种来源的数据的工具和服务,例如网站点击流、零售分析、社交媒体信息,甚至天气数据,因此公司可以更好地了解其营销活动。Klodars Corporation可以雇佣数据分析师和数据科学家来处理市场趋势,以帮助提供有价值的信号,从而协助其扩展战略。首席执行官对这种方法完全认同,并希望尝试云数据湖解决方案。在公司的发展过程中,保持现有业务的运行非常重要,同时开始尝试云计算方法。让我们看看不同的云架构如何为Klodars Corporation带来独特的优势,并帮助满足其快速增长和扩展带来的需求。云数据湖架构的基本原理在部署云数据湖架构之前,重要的是要了解构成云数据湖架构基础并作为构建模块的四个关键组件。这些组件包括:数据本身数据湖存储处理数据的大数据分析引擎云数据仓库对于数据的多样性需要提一下我们已经提到数据湖支持各种类型的数据,但是这种多样性实际上指的是什么呢?让我们以之前提到的数据为例,具体是库存和销售数据集。从逻辑上讲,这些数据是表格化的,也就是由行和列组成,可以在表格中表示。然而,实际上,这些表格数据的表示方式取决于生成数据的源头。大体上来说,在大数据处理中,有三个广泛的数据类别:结构化数据这是一组具有定义结构(行和列)并且遵循严格预定义模式的格式。一个经典的例子是关系数据库(如SQL)中的数据,它看起来像图2-1所示。数据存储在专门定制的二进制格式中,用于关系数据库,并经过优化以存储表格数据(以行和列组织的数据)。这些格式是专有的,为特定系统量身定制。数据的消费者,无论是用户还是应用程序,都了解这个结构和模式,并依赖它们编写他们的应用程序。不符合规则的数据将被丢弃,不会存储在数据库中。关系数据库引擎还将这些数据存储在经过优化的二进制格式中,以便高效存储和处理。半结构化数据这是一组格式,其中存在一定的结构,但它的定义比较宽松,如果需要的话,可以灵活地自定义结构。JSON和XML就是半结构化数据的示例。图2-2展示了销售商品ID的半结构化数据的表示形式。半结构化数据格式的强大之处在于其灵活性。一旦你开始设计一个模式,然后确定需要一些额外的数据,你可以添加带有额外字段的数据,而不会违反任何结构的限制。读取数据的现有引擎将继续正常工作,而新的引擎可以包含新字段。同样,当不同的源发送类似的数据(例如,销售点系统和网站遥测都可以发送销售信息),你可以利用灵活的模式支持这些多个来源。非结构化数据这指的是没有对数据存储方式施加任何限制的格式,可以是类似社交媒体帖子上的评论这样简单的自由形式备注,也可以是复杂的MPEG4视频或PDF文档。非结构化数据可能是最难处理的格式,因为它需要定制编写的解析器才能理解并从数据中提取正确的信息。与此同时,从一般用途的对象存储角度来看,非结构化数据可能是最容易存储的格式,因为它没有任何限制。例如,想象一下社交媒体帖子中的图片,卖家可以为商品添加标签,并在有人购买该商品后添加另一个标签表示已售出。处理引擎需要处理图像以了解已售出的商品,然后处理标签以了解价格和购买者是谁。虽然这并非不可能,但需要花费大量的工作来理解数据,并且质量较低,因为它依赖于人工标记。然而,这扩大了灵活性的视野,可以用于开展各种销售渠道。例如,你可以编写一个引擎来处理社交媒体中的图片,以了解在给定地区以何种价格由哪个房地产经纪人出售的房屋,如图2-3所示。云数据湖存储云数据湖存储的非常简单的定义是一种作为云服务提供的服务,可作为各种数据(结构化、非结构化和半结构化)的中央存储库,并能支持大规模的数据和事务。当我说“大规模”时,可以想象一个支持存储数百PB的数据和每秒数十万个事务的存储系统,并且可以在数据和事务继续增长时实现弹性扩展。在大多数公共云服务提供中,数据湖存储作为PaaS服务提供,也称为对象存储服务。数据湖存储服务提供丰富的数据管理功能,例如分层存储(不同层级的成本不同,可以将很少使用的数据移动到成本较低的层级)、具有不同程度复制的高可用性和灾难恢复以及丰富的安全模型,允许管理员控制各种消费者的访问权限。让我们来看一些最受欢迎的云数据湖存储服务提供:Amazon S3(简单存储服务)亚马逊提供的S3是一个大规模的对象存储服务,建议将其作为构建基于AWS的数据湖架构的存储解决方案。在S3中存储的实体(结构化和非结构化数据集)被称为对象,对象被组织到称为存储桶的容器中。S3还允许用户通过使用共同前缀(将其视为虚拟目录)将对象组合在一起进行组织。管理员可以通过在存储桶或前缀级别应用访问策略来控制对S3的访问权限。此外,数据操作员可以向对象添加标签,这些标签实际上是键值对,可以用作标签或标签,让您可以通过指定标签检索对象。亚马逊S3还提供了丰富的数据管理功能,以管理数据的成本,并增加安全性保证。要了解有关S3的更多信息,您可以访问文档页面。Azure Data Lake Storage(ADLS)ADLS是Microsoft Azure提供的一种存储解决方案,它在其通用对象存储服务(Azure Blob存储)上提供了一个带有层次化命名空间的本地文件系统。根据ADLS产品网站的介绍,ADLS是一个用于摄取、处理和可视化的单一存储平台,支持最常见的分析框架。您可以创建一个ADLS账户,其中将在“启用层次化命名空间”选项中选择是。ADLS提供了一个称为容器的组织单元,以及具有目录和文件来组织数据的本地文件系统。您可以访问文档页面以了解有关ADLS的更多信息。Google Cloud Storage(GCS)GCS是由GCP提供的对象存储服务,并被推荐作为数据湖存储解决方案。类似于S3,Google中的数据被称为对象,并以存储桶进行组织。您可以在文档页面上了解更多关于GCS的信息。云数据存储服务具有从各种来源加载数据的能力,包括本地存储解决方案,并与实时数据摄取服务集成,该服务连接到诸如物联网传感器之类的数据源。它们还与支持传统应用程序的本地系统和服务集成。此外,有许多数据处理引擎可以处理存储在数据湖存储服务中的数据。这些数据处理引擎属于多个类别:公共云提供的PaaS解决方案(例如,AWS的EMR,Azure的HDInsight和Azure Synapse Analytics,以及GCP的Dataproc)其他软件公司开发的PaaS解决方案,例如Databricks、Dremio、Talend、Informatica和ClouderaSaaS解决方案,例如Microsoft Power BI、Tableau和Looker您还可以预配IaaS解决方案,如虚拟机(VMs),并运行自己的软件发行版,如Apache Spark,来查询数据湖。 需要注意的一个重要点是,在数据湖架构中,计算和存储是分离的,您可以在数据湖中运行一个或多个处理引擎,而无需移动数据。一些流行的数据仓库包括Amazon Redshift、Google BigQuery和Snowflake Data Cloud。这些数据仓库既提供计算能力又提供存储空间,虽然某些情况下数据仓库支持查询存储在独立数据湖存储中的数据,但最常见的用例是使用最优化的路径:查询以专有数据格式存储在数据仓库中的数据。最近,数据仓库开始支持开放数据格式,如Apache Iceberg,这是一个非常有前景的趋势,它在方向上支持数据湖仓库架构。在本章中,我们还将更详细地介绍数据湖仓库架构。大数据分析引擎到目前为止,我们了解到大数据分析是对结构化、半结构化和非结构化数据进行处理。现在让我们来探索一下这个处理过程的实际情况。当我们谈论在数据湖上进行大数据分析时,所进行的处理很可能是下面描述的其中一种,或者是它们的派生形式之一。MapReduce大数据和分析处理的起源可以追溯到一个改变我们工作方式的事物的出现:搜索引擎。搜索引擎主要通过从互联网上的所有来源抓取文本数据并构建一个巨大的关键字索引来工作。当用户搜索一个关键字时,搜索引擎会根据这个索引对数据进行排名,并向用户提供一个有序的结果集。虽然搜索引擎的设计本身需要一本书来详细解释,我们在这里不会详细讨论,但它们证明了需要处理大量数据并将其简化为可搜索的索引的需求,从而诞生了一个称为MapReduce的编程模型。MapReduce本质上是一个编程模型及其相关实现,它接受一组键值对作为输入,并生成一组键值对作为输出。听起来很简单,不是吗?问题在于规模——在包含数百万条记录的数据集上进行这种转换。正如Jeffrey Dean和Sanjay Ghemawat在他们的论文《MapReduce: Simplified Data Processing on Large Clusters》中所描述的,MapReduce有两个阶段,顾名思义。在映射阶段,数据按照键进行组织,并使用逻辑将相似的值归为一组,生成中间的键值对。规约阶段处理这些相似的数据集,生成一组经过筛选的结果,同样是键值对。举个例子,让我们以Twitter数据为例,目标是了解在一组大型Feed中每个用户被提及的次数(图2-4中显示了一个较小的样本)。这里有一组计算单元在工作:多个工作单元在分配给它们的数据集上运行,并且有一个主要的编排单元负责协调工作单元之间的操作。计算单元可以是虚拟机、进程或线程,具体取决于实现方式。将大量的Twitter数据Feed分解成较小的集合(在这个例子中是一个Feed)并分配给工作单元,在这里它们将提及映射为计数,并生成一组输出的键值对,如图2-4所示。然后将这些数据值发送给另一组工作单元进行规约,以生成每个用户的提及次数。主要优势在于,这种编程模型可以将大型数据集有效地分布在一组工作单元中,并具有可预测的分布机制。Apache HadoopApache是一个开源组织,他们有一个名为Apache Nutch的开源网络搜索项目,即使在今天仍在使用。2005年,作为Apache Nutch的一部分,Doug Cutting和Mike Cafarella创建了Apache Hadoop,这是一套用于分布式处理大型数据集的工具、库和组件,其核心处理逻辑使用了MapReduce。Apache Hadoop由四个主要组件组成:Hadoop通用模块:支持其他模块的通用库集合Hadoop分布式文件系统(HDFS) :用于大型数据集的分布式存储系统Hadoop MapReduce :用于大规模处理大型数据集的编程模型和实现Hadoop YARN :用于作业调度和资源管理的框架,可以在机群中分发工作和数据Hadoop奠定了一个坚实的基础,孕育了许多其他开源项目,例如Apache Hive、Apache Pig、Apache Storm和Apache Mahout,用于构建更多分布式大数据处理的框架和编程模型。这里提供了所有Hadoop项目和工具的详细索引,示例见图2-5中的Hadoop生态系统表格。Hadoop使大数据处理生态系统商品化,而像Hortonworks和Cloudera这样的供应商销售他们的Hadoop分发版本,客户可以在本地或云上安装。公共云提供商还提供基于Hadoop的处理的打包版本作为PaaS解决方案,例如AWS的Elastic MapReduce(EMR)和Microsoft Azure的HDInsight。在所有这些不同的Hadoop提供中,你可能会想知道该选择哪个。虽然有很多原因,比如对供应商的熟悉程度、销售和市场关系等,但几个技术关键因素对客户的选择起到了重要作用:在混合环境下运行的客户,即既有本地部署又有云部署,或者在多云环境下运行的客户,会选择由独立软件供应商(ISV)提供的Hadoop解决方案,如Cloudera或Hortonworks,以便其实现在所有环境中都能工作。更喜欢将其大数据平台与其他原生云服务紧密集成的客户,选择公共云提供商(如AWS、Azure和GCP)提供的解决方案。愿意投资于强大技术团队并希望节省供应商成本的客户,会通过分叉开源存储库并构建自己的平台来创建自己的Hadoop版本。这在其他开源解决方案(如Apache Spark)中也同样适用。 可以说,Hadoop通过提供一套综合的工具为大数据处理奠定了数据湖架构的基础,包括用于批处理的MapReduce、用于实时处理的Apache Storm以及用于在Hadoop架构上查询数据的Apache Hive。Apache SparkApache Spark孵化于加州大学伯克利分校的AMPLab,专注于大数据分析。Apache Spark的目标是提供一种灵活的编程模型,具有MapReduce的容错性和规模,用于分布式数据处理,同时支持更广泛的应用程序,如依赖于数据的迭代处理和实时处理的机器学习,以提供即时见解。与Hadoop类似,Spark使用底层存储层;然而,并没有规定必须使用HDFS存储;Spark支持云对象存储服务甚至本地存储。同样,Spark使用集群管理器,也支持各种选项,如从Hadoop诞生的YARN和同样孵化于加州大学伯克利分校的Apache Mesos。最近,随着Kubernetes和容器(简单定义为包含代码、应用程序运行时和运行代码所需的其他组件的即用软件包)在云原生开发中的日益流行,Spark在Kubernetes上也得到了广泛应用。Spark的关键区别在于Spark核心引擎,它构建在作为弹性分布式数据集(RDDs)的数据集的基本抽象上,而无需将中间数据集存储到持久性存储中,仍然保持容错性。这种模型极大地提高了基于Spark的应用程序的性能,并为批处理、交互式查询(Spark SQL)、数据科学(MLlib)、实时处理(Spark Streaming)和最近引入的图处理(GraphX)提供了统一的编程模型。Apache Spark的易用性和日益增长的认可度帮助在各个行业中将大数据处理商品化。你可以使用Spark作为独立分发版,也可以利用公共云提供商提供的Spark(如Amazon EMR、Azure Synapse Analytics或Google Cloud Dataproc),或者使用由Spark的发明者创建的Databricks等软件提供商提供的Spark。图2-6演示了Spark的各种技术组件以及它们如何相互层叠,以提供在机器学习、实时和批处理流式处理中的一致的编程模型。实时流处理管道实时流处理指的是对数据进行摄取、处理和消费,重点是追求速度,接近实时的结果。想象一下,当你在旅行时,你从你喜爱的美食评论应用程序收到了关于附近餐厅的实时通知,你可以去探索。这里有一个实时处理管道在工作,它从你的移动设备中获取你的位置信息,并将其与你的个人资料和其他相关数据结合起来,实时提供个性化的推荐。另一个例子是你手机上的导航应用在你通常的路线上有交通拥堵时建议你选择另一条路。在这种情况下,有一个实时处理管道将实时交通数据与地图结合起来,为你的目的地提供最佳路线建议。实时流处理管道涉及到从源头以非常高的速率到达的数据,换句话说,这些数据就像雨水或瀑布一样源源不断地流入系统中。这些数据可能是不断从诸如GPS之类的源头实时流入的,或者可能是由物联网传感器(如家庭自动化系统或工业设备)发出的事件。这些数据往往非常小,通常为几千字节(KB)。 实时流处理管道的处理部分涉及处理实时流数据,有时将其与非实时数据结合,重点是低延迟,通常在毫秒级。实时处理应用程序的典型场景是提供接近实时的洞察力,以便消费者能够迅速采取行动,例如当系统处理系统日志并在出现问题时实时发出警报的系统。实时数据处理技术在处理高速率和吞吐量的数据进入系统时考虑以下几个方面:传递保证实时流处理技术提供传递保证,确定实时数据的处理方式。至少一次保证确保数据将至少被处理一次,可能会多次处理以应对故障。至多一次保证确保数据将最多被处理一次,避免重复处理。精确一次保证确保数据将被精确处理一次,这是非常理想的,但也非常难以实现。容错性实时流处理技术需要确保在集群或底层基础设施发生故障时具有弹性,并能够从故障出现的地方继续处理。状态处理实时流处理框架提供状态管理功能,记录已处理的消息数量或最后处理的消息是什么。实时流数据可以以多种方式进行消费:如展示社交媒体趋势图的可视化、安全事件检测等警报系统,甚至基于浏览模式的实时推荐等智能应用行为。图2-7展示了实时流数据管道的架构。构建实时数据管道的多种技术也可供选择。Apache Kafka是一个重要的技术,用于实时流数据的摄取和存储,具有高吞吐量和可扩展性。Amazon Kinesis和Azure Event Hub是基于Apache Kafka构建的云原生PaaS解决方案。Apache Storm和Apache Flink是流行的开源技术,提供实时数据处理能力。Apache Kafka还提供Kafka Streams用于实时流处理。云数仓云数据仓库是在公共云上以托管服务(PaaS)形式提供的企业数据仓库,具有针对数据摄取、分析处理和商业智能分析的优化集成。商业智能分析指支持可视化和交互式查询功能的工具。云数据仓库的提供旨在通过将基础架构从用户中抽象出来,弹性扩展以满足客户不断增长的需求,并承诺比传统的本地数据仓库具有更快的性能和更低的拥有成本。让我们来看一些最受欢迎的云数据仓库提供商:Amazon RedshiftAmazon Redshift是公共云上首个受欢迎的云数据仓库提供商。您可以配置一个Redshift集群,并指定所需的计算节点数量。根据产品文档,该集群可以支持PB级的数据。您可以使用流行的查询语言PostgreSQL来查询Redshift集群中的数据。要了解更多关于Redshift的信息,您可以访问产品页面。Redshift还宣布了在不复制数据的情况下在不同的Redshift集群之间共享数据的能力,以促进数据和洞察力的共享。Google BigQuery与Redshift不同,您在Google BigQuery中无需配置数据仓库集群,它是一个完全无服务器、高度可扩展的数据仓库解决方案,完全将集群管理的细节与客户隔离开来。此外,BigQuery还具有BigQuery Omni等功能,允许您在其他云(如AWS和Azure)上使用BigQuery计算服务。Azure SynapseAnalytics Azure Synapse Analytics是在Microsoft Azure上提供的统一分析平台。与Redshift类似,您可以配置一个数据仓库集群,并为您的场景指定所需的节点数量。您还可以在同一体验中配置Spark集群以进行分析场景。此外,您还可以在SQL或Spark中运行无服务器查询。使用无服务器查询,您可以简单地提交作业而无需配置集群,类似于BigQuery。Azure Synapse Analytics还在同一体验中与其他Azure服务集成,如Azure Machine Learning、Azure Cognitive Services和Power BI。Snowflake Data CloudSnowflake数据仓库是一种托管的数据仓库解决方案,可在所有公共云(AWS、Amazon和GCP)上使用。Snowflake被设计为一个真正可扩展的解决方案,它作为一个单一服务提供,而实现则在分离的计算和存储架构上运行,使其在计算或存储维度上高度可扩展,而不会增加成本。这种分离还可以让您启动不同的虚拟仓库,这些仓库可以访问相同的数据,为不同的查询场景提供隔离。Snowflake还提供表级和对象级的数据共享给其他Snowflake账户。在本节中,我对云数据湖架构的四个组成部分进行了高级概述:数据、数据湖存储、计算引擎和云数据仓库。我还概述了常用的服务和技术,并提供了深入了解的链接。在下一节中,我将介绍代表这些构建块可以组装成解决方案的不同云数据湖架构。在我们阅读本书的同时,数据湖和数据仓库的提供商正在快速创新,模糊了它们之间的界限。我们将在数据湖仓库架构模式中进一步讨论这一点。云数据湖架构中的数据可以用于多种目的。然而,有两种常见的组织中的消费模式:商业智能数据被商业智能分析师用于创建仪表板或处理交互式查询,以回答明确定义的关键业务问题,并处理高度结构化的数据。数据科学和机器学习数据被数据科学家和机器学习工程师用于进行探索性分析和实验工作,以回答没有明确定义的规则集且需要多次迭代才能改进的复杂问题。在这里涉及的数据假设没有结构。现代数仓架构在现代数据仓库架构中,数据湖和数据仓库和平共处,各自担任不同的角色。数据湖作为低成本存储用于大量数据,并支持数据科学和机器学习等探索性场景。数据仓库存储高价值的数据,并为企业的仪表板提供支持。它也被商业智能用户用于查询高度结构化的数据,以获取有关业务的洞察力。参考架构首先,数据从各种来源(如本地数据库、社交媒体数据源等)被摄取到数据湖中。然后,利用像Hadoop和Spark这样的大数据分析框架对数据进行转换,通过聚合和筛选多个数据集来生成具有高价值的结构化数据。接下来,将这些数据加载到云数据仓库中,用于生成各种仪表盘,包括供商业智能分析师使用的交互式仪表盘,他们可以使用他们非常熟悉的SQL工具进行查询。此外,数据湖还为数据科学家提供了一整套探索性分析以及将机器学习模型反馈到应用程序中的场景。图2-8展示了现代数据仓库架构的简化表示。在这里,你可能会自然而然地提出一些问题:为什么不直接使用云数据仓库?为什么需要在两者之间加入数据湖?如果我只有结构化数据,是否真的需要数据湖?我可以说,这些是很好的问题。以下是为什么在这种架构中需要数据湖的几个原因:数据湖的成本远低于数据仓库,并可作为长期数据存储库。请记住,数据湖通常用于存储大量的数据(数十或数百PB),因此成本差异是实质性的。数据湖支持各种现代化的数据科学和机器学习工具和框架,可以用于实现全新的场景。数据湖可以为未来的扩展需求提供可扩展性设计。例如,您可以使用初始的数据湖架构每晚从本地系统加载数据,并为商业智能用户发布报表或仪表盘。同样的架构可以扩展支持实时数据摄取,而无需重新设计解决方案。各种形式和结构的数据对组织来说越来越重要。即使您今天专注于结构化数据,如前面的示例所示,您可能会发现各种类型的数据(例如天气、社交媒体数据等)都具有价值。如果你还没有注意到,这里有一个需要记住的使用模式的区别:当你将数据加载到数据仓库时,你使用的是提取、转换和加载(ETL)模式,即从源中提取数据,将其转换为数据仓库所需的格式,然后加载到数据仓库中。而在数据湖中,你遵循提取、加载和转换(ELT)模式,即从源中提取数据,按原样加载到数据湖中,然后进行转换处理。现代数据仓库架构的示例应用案例让我们重新考虑我们的模型公司Klodars Corporation。它将利用现代数据仓库架构,开始将数据从其运营数据库加载到数据湖中。它可以停止在本地系统上存储备份,并将每日备份存储在数据湖中,保留一年的备份(或更长时间,如果需要)。在此过程中,Klodars的服务器上的运营数据库将继续为现有应用程序提供服务,从而确保公司运营的连续性。此外,Klodars还计划加载与雨具和冬季装备相关的社交媒体数据,以分析模式。该架构还将使公司能够使用实时摄取技术(如Apache Kafka)将其他数据(如点击流)实时加载到数据湖存储中。准备好数据集后,数据工程团队将使用Apache Spark等工具处理来自数据库转储和网站点击流的结构化数据,生成显示购物和销售趋势的高价值数据。团队还将处理社交媒体数据流,提取与雨具和冬季装备相关的数据,以及这些数据所指示的任何相关购买行为。该架构将使数据工程团队能够定期生成关于销售趋势、库存和供应、网站浏览趋势以及与雨具和冬季装备相关的社交媒体趋势的高价值数据(例如,每日生成)。然后,将这些数据加载到数据仓库中,并定期刷新(例如,每日)。存储在数据仓库中的数据是非常高价值的结构化数据。业务分析师将使用这些高价值数据构建仪表盘,显示按季度或按月的销售趋势,以便销售团队可以了解其销售的趋势并为即将到来的时间段制定预测。业务分析师还可以根据地区、销售人员覆盖范围、合作伙伴和其他属性对数据进行分析,以便领导团队了解增长驱动因素,并根据数据制定关于公司扩张策略的决策。营销团队通过在数据仓库上运行交互式查询来使用社交媒体和网站浏览趋势,以了解下一轮有针对性的营销活动的发展方向。团队还可以通过将营销活动与销售结果相关联来了解其营销活动的影响。影响并不止于此。Klodars现在已经组建了一个数据科学团队,他们可以基于现有数据集(如销售、社交媒体趋势和网站浏览趋势)寻找有趣的关联和影响,这些关联和影响无法通过手动分析进行处理。团队可以将其他数据集引入数据湖中,例如天气数据、关于滑雪等冬季活动的数据等,以向领导团队展示有趣的见解。这些数据可以反馈给数据工程团队,加载到数据仓库中,供领导、营销和销售团队使用。图2-9展示了Klodars Corporation的现代数据仓库架构的表示。借助现代数据仓库架构,Klodars Corporation通过依靠数据,能够根据客户的增长需求进行适当的重点领域扩展。其现代数据仓库战略使公司能够在保持现有业务运作的同时进行创新工作。将现有应用逐步迁移到现代化的云架构中,使团队有时间来深思熟虑地设计和实施这一转变。现代数仓架构的好处现代数据仓库具有重要优势,可以帮助业务分析师利用熟悉的商业智能工具集(基于SQL)进行数据消费,并实现原本在本地数据仓库实现中无法实现的更现代化的数据科学和机器学习场景。这主要通过数据湖来实现,数据湖作为一个非孤立的数据存储库,支持使用云原生服务进行高级数据科学和机器学习场景,同时保留了熟悉的数据仓库,如面向商业智能用户的基于SQL的接口。此外,数据管理员可以通过数据仓库将对数据的访问隔离给商业智能团队,使用熟悉的数据仓库访问控制方法。运行在本地的应用程序也可以逐步迁移到云端,完全消除维护两套基础设施的需求。此外,通过将运营数据备份到数据湖中,企业可以降低总体成本,并延长数据备份的时间。然而,这种方法也存在一些挑战。数据工程师和管理员仍然需要维护两套基础设施:一个数据湖和一个数据仓库。在数据湖中存储各种类型的数据的灵活性也带来了挑战。管理数据湖并确保数据质量的保证是数据工程师和管理员现在必须解决的重大问题,这是他们之前没有遇到的问题。如果数据没有得到妥善管理,数据湖也有可能变成一个数据沼泽,就像在一堆干草中找针一样隐藏了洞察力。如果商业智能用户或业务决策者需要新的数据集,他们必须依赖数据工程师来处理这些数据并将其加载到数据仓库中,这引入了一个关键路径。此外,如果数据科学家在数据仓库中发现了一个有趣的数据片段,他们想要将其包括在探索性分析中,他们需要将其重新加载到数据湖中,以不同的数据格式和不同的数据存储方式,增加了共享的复杂性。数据湖仓架构数据湖仓(Data Lakehouse),是由Databricks广泛使用的一个行业热词。根据451 Research的研究分析师Malav Parekh的博客文章,Amazon在发布Redshift Spectrum时首次使用了“lake house”(湖宅)这个术语,将“lake”和“house”之间加了一个空格。该术语在行业中逐渐流行起来是在2020年1月Databricks的一篇博客文章中,称数据湖仓是一种新的开放式架构,结合了数据湖和数据仓库的最佳元素。我清楚地记得2020年的Data and AI Summit的主题演讲,Ali Ghodsi宣布了数据湖仓作为一种新的范式,并介绍了Delta Lake。有多个关于Delta Lake的会议,参会者排起长队沿着会议大厅的走廊。数据湖仓架构的不断增长的受欢迎程度和生态系统支持了这种新范式的主张。数据湖仓架构可以简单解释为一个单一平台,结合了两个功能:数据湖用于分析处理、数据科学和机器学习场景。数据仓库用于SQL交互查询和商业智能场景。换句话说,它指的是在数据湖上运行SQL和商业智能场景。这个概念具有以下三个吸引人的特点:数据湖比数据仓库便宜得多,使得数据湖仓更具成本效益。无需将数据从数据湖复制或移动到数据仓库,无需进行数据移动。通过消除分离的体验和平台,数据科学家和商业智能团队可以自由共享数据集。数据湖仓架构的出现在行业中引起了广泛关注,并为组织提供了更加灵活和高效的数据分析和查询方案。数据湖仓的参考架构图2-10展示了数据湖仓架构的简化表示。请注意,现在您可以在单一平台上运行所有场景,包括商业智能和数据科学,并且无需使用云数据仓库。那么,如果我们已经有了在数据湖上运行商业智能场景的选项,为什么一开始就没有这样做呢?简单的答案是因为数据湖本身并不真正适合支持商业智能查询,而且有各种技术使得数据湖仓成为现实。请记住,数据仓库依赖于高度结构化的数据以实现更快的查询处理和支持涉及连接和聚合的复杂查询,而数据湖则是高度可扩展的对象存储服务,用于存储和处理数据,对结构不做任何假设。让我们更详细地看一下,数据仓库具有以下优势:ACID兼容的事务数据仓库确保事务符合ACID的标准,这一特性对于保证通常存储在仓库中的高价值数据的完整性至关重要。这种完整性非常重要,因为这些数据用于支持涉及公司收入和运营的关键操作的查询和仪表板。例如,销售预测仪表板为组织设定收入目标。ACID是指事务的四个关键属性:原子性确保当事务完成时,整个事务作为一个单元成功。例如,如果您在查询中请求客户的详细信息,并要求包括姓名、年龄、位置和收入潜力,您将获得所有的详细信息,而不仅仅是年龄和位置。一致性确保遵循所有适当的数据验证规则,并且只写入允许的数据。如果验证不成功,则数据库将在事务之前回滚到之前的状态。例如,如果您想要将新的客户记录添加到数据库中,您有正确的姓名和年龄,但位置无效,整个事务将失败。隔离性确保在处理并发事务时,一个事务不会影响另一个事务。例如,如果两个用户尝试向数据库中添加相同的客户,第一个用户成功,而第二个用户会因为客户已经存在而收到错误。持久性确保在成功的事务之后,数据是可用的。例如,当您成功地将客户添加到数据库中时,即使发生断电或硬件故障,您也可以确保客户数据是完整的。针对SQL进行优化大多数商业智能和数据分析工具及生态系统都针对SQL进行了优化,而数据仓库提供了一个针对SQL进行优化的查询引擎,支持这些场景。数据湖提供以下优势:存储和处理非结构化数据的能力大多数涉及高级分析、数据科学和机器学习的新兴场景都依赖于处理非结构化数据。数据湖对数据的结构或模式不做任何假设。在从数据湖读取数据时,您可以在读取时定义数据的模式。低成本数据湖是高度优化的存储系统,为用户提供了低成本的拥有权,并且可以存储任意量的数据,无需担心不断增长的费用。丰富的数据管理数据湖提供了一系列功能来帮助管理数据,正如我们在前面的部分所看到的。这些功能包括分层存储、数据复制和数据共享能力。尽管将数据湖和数据仓库统一为一个体系结构具有吸引力,但数据湖的优势是数据仓库的不足之处,反之亦然,这长期以来一直妨碍着数据湖架构的发展。然而,随着组织中对数据湖的日益采用以及在数据湖上运行的各种场景的增加,人们开始积极关注并为实现数据湖架构的关键技术做出贡献。其中一些技术包括源于Databricks的Delta Lake、源于Netflix的Apache Iceberg和源于Uber的Apache Hudi。尽管这些技术本身不同,并从不同的角度解决问题,但它们有一个共同点:它们定义了存储在数据湖中的数据。这种数据格式为在数据湖上提供数据仓库的保证(接近ACID的一致性、元数据处理、模式强制和演化)奠定了基础。它们通过三个关键组件来实现这一点:开放的文件格式定义数据的元数据层理解这些文件格式和元数据层的计算引擎有了这些组件,它们可以将存储在数据湖中的非结构化数据作为对象或文件,并将其以表格的新逻辑形式呈现出来。表格是指以逻辑行和列组织的数据,如图2-11所示。数据格式我们已经确定数据格式对于数据湖架构至关重要,但为什么会这样呢?正如我们之前所看到的,数据仓库中的数据具有关于完整性的强大保证;为了使数据湖中的数据具备类似的保证,将数据限制在一些关键规则下是非常重要的。可以类比为,在教室里,孩子需要遵守一定的规则,以创造一个有利于学习的环境,但同样的孩子可以在公园里自由探索。想象一下,如果你在公园里建立一个教室,你需要做些什么;开放的数据格式试图确保数据在非结构化环境中受到一定规则的限制,而这种环境在这里就是数据湖存储。数据格式对于数据湖架构至关重要,原因如下:存储的数据需要遵守由元数据定义的模式(描述数据集的表格结构的数据)。这里的模式指的是数据的定义表示或描述。存储的数据针对查询进行了优化,特别是为了支持主要使用类似SQL查询的BI用例。这种优化对于支持与数据仓库相当的查询性能至关重要。实际上,解决这些需求还有一个非常好的好处,即这些数据往往具有高度可压缩性,从而实现更快的性能和更低的成本,这意味着你可以同时拥有这两方面的好处。数据湖架构中使用了诸如Delta Lake、Apache Iceberg和Apache Hudi之类的专用格式。我们将在第6章中对它们进行更详细的讨论。它们都源自一种基本的数据格式,即Apache Parquet,这是Apache Hadoop生态系统中使用的列存储数据格式。让我们进行一个小的绕道,了解一下列格式意味着什么。请记住,我们正在讨论的是表格数据,其中数据按行和列组织,如图2-12所示。当涉及到如何在数据湖中存储这些数据时,直觉上可能会认为你将一个记录(即一行)一起存储。而在列格式中,数据以列为导向的方式进行存储,具有相似列值的数据被存储在一起。正是这种对相似数据的捆绑使得列格式(如Apache Parquet)具有高度可压缩性。图2-12提供了相同数据在行导向和列导向结构中存储的表示形式。我们将在第4章中更详细地讨论Apache Parquet。开放数据技术使用Apache Parquet作为其底层数据格式,以便利用Apache Parquet的优化来优化查询。元数据元数据简单地指的是关于数据的数据。例如,如果您有一个包含1000行的表,存储为每个数据集的100行块,那么每个块都与描述存储的数据相关的元数据相关联,比如这个块包含第101至200行,其中包含以A-B开头的姓氏列的值。此外,还在表级别存储了指向不同块的指针的元数据。这些元数据对于最终用户来说并不是很重要,但对于操作数据的计算引擎非常重要。这些计算引擎读取元数据,然后获取相关的数据。像Apache Iceberg、Delta Lake和Apache Hudi这样的技术都有自己的元数据版本,用于确定数据在不同的Parquet文件中的存储和组织方式,哪些数据正在进行更新以及何时进行更新,以便提供数据完整性和一致性,并与计算引擎进行握手,以优化特定的场景。虽然它们都是合适的选择,但每个选项都是根据特定目的进行设计的,因此在设计架构时您需要考虑这一点。Databricks的Delta Lake针对在数据湖上运行高性能的SQL查询进行了优化,利用元数据进行智能数据跳过,只读取为提供查询所需的数据。Uber开源的Apache Hudi主要设计用于支持增量更新,并具有列格式的快速查询性能。Netflix开源的Apache Iceberg主要用于刷新数据集(例如,支持对像S3这样的追加存储系统上的现有数据进行更新),并可由众多计算引擎(如Apache Spark、Trino(Presto SQL)、Apache Hive和Apache Flink)进行读取,程度各不相同。计算引擎与数据仓库不同,数据湖架构中的计算和存储优化并一起作为一个服务提供。在运行数据湖架构时,需要选择合适的计算引擎,以利用用于优化数据存储的数据格式和元数据提供的优化功能。换句话说,数据被优化为以表格形式写入存储,您需要一个计算引擎来理解和读取表格,以有效地查询数据。例如,对于由Databricks开发的Delta Lake格式,其计算组件(即Spark引擎)针对操作Delta表格进行了优化,并通过缓存提供更快的性能以及通过Bloom过滤器索引实现有效的数据跳过。我们将在第6章中对这些计算引擎进行更详细的讨论。数据湖架构的示例应用场景Klodars Corporation将利用数据湖从其操作数据源加载数据到数据湖存储中,类似于我们在现代数据仓库架构中所看到的。让我们更详细地看看这种架构对业务的影响。数据工程团队将使用诸如Apache Spark之类的工具处理来自数据库转储和网站点击流的结构化数据,以生成随时间变化的购物和销售趋势等高价值数据。团队还将处理社交媒体信息流,提取与雨衣和冬季装备相关的数据,以及这些信息流指示的任何相关购买信息。现在让我们来看看如何处理这些提取出的数据。数据工程团队将按计划(例如每日)生成关于销售趋势、库存和供应、网站浏览趋势以及雨衣和冬季装备周围的社交媒体趋势等高价值数据。现在,业务分析师可以开始使用他们熟悉的基于SQL的工具以及像Presto这样的现代查询工具来查询这些数据,而无需移动数据。类似于现代数据仓库模式,数据科学家可以带上自己的数据集,例如天气数据,并探索已经存在于数据湖中的数据。数据湖架构通过消除存储相同数据的两个位置,相比现代数据仓库,提供了一个重要优势。假设数据科学团队利用其新的数据集(例如天气数据)构建了一个将销售与天气相关联的新数据集。由于每个人都使用相同的数据存储和可能相同的数据格式,因此业务分析师可以立即开始进行更深入的分析。同样,如果业务分析生成了特定的筛选数据集,数据科学家可以开始使用该数据集进行分析。花点时间思考一下这意味着什么以及其影响是什么。这完全扩展了不同类别的数据平台使用者之间的洞察力交叉,促进了洞察力的交流。共享平台意味着BI分析师和数据科学家生成的数据对于两者都可用,以进一步创新,从而将数据的价值提升数倍。Klodars Corporation的数据湖架构示例如图2-13所示。数据湖架构的优势和挑战数据湖仓提供了一个关键优势,即能够直接在数据湖上运行高性能的BI/SQL场景,与其他探索性数据科学和机器学习场景并行进行。正如我们在用例中看到的那样,这也促进了数据平台的各个用户部门之间的共享,产生了新的场景。此外,与数据仓库相比,数据湖具有非常高的成本效益。然而,也存在挑战。正如我们在架构部分看到的那样,构建数据湖仓需要精心设计和架构,选择合适的数据格式和计算引擎以实现最优化的解决方案,从而提供更快的性能。如果没有正确的规划,许多问题可能会出现,我们将在第4章中详细讨论这些问题。数据仓库可以直接提供这种优化路径,但它们并不是真正开放的。尽管我没有预测未来的能力,但鉴于数据湖仓架构的快速发展速度,我相信在未来几年中,这个领域将会迅速创新,从而实现简化的端到端体验。数仓和非结构化数据如果数据湖可以开始支持数据仓库场景,那么数据仓库是否也可以开始支持数据湖场景呢?令人惊讶的答案是肯定的。正如我们在前面的部分中看到的那样,Azure Synapse Analytics为Spark、机器学习和SQL提供了统一的数据平台。Google BigQuery支持存储非结构化数据,并原生支持Parquet格式;它还支持查询存储在GCS中的数据。Snowflake最近也推出了对非结构化数据的支持。无论是数据湖支持数据仓库,还是数据仓库支持数据湖,我们当前的创新明确表明,统一的数据平台和无障碍的数据平台是未来的发展方向。Data Mesh在2019年,Thoughtworks的新兴技术总监Zhamak Dehghani撰写了一篇关于数据网格的文章,奠定了数据网格架构的基础。数据网格架构使得组织能够以分散化的方式运行数据基础设施和操作,从而在整个组织中实现数据和洞察力的民主化。让我们看看为什么这种分散化的数据网格对于组织来说如此重要或相关。到目前为止,我们已经讨论了数据湖作为组织的数据和智能的中央存储库,以及技术选择。在架构中的体现方式是作为一个由中央团队管理的基础设施。现在让我们看看在组织中谁负责设计和操作数据湖。数据的提取和处理由一个中央团队管理,通常被称为数据平台团队、数据基础设施团队、数据工程团队或者其他类似的名称。在本节中,我们将称这个团队为数据平台团队。数据平台团队通常拥有以下角色:数据平台架构设计满足组织需求的计算和存储组件的基础设施。数据管理在云上组织数据集;应用数据管理策略,确保数据符合组织在数据保留和数据驻留方面的合规需求。数据治理控制谁可以访问哪些数据,提供目录,使数据平台的使用者可以发现数据集,并管理审计。数据摄取通常负责从各种源头(如本地系统、物联网等)摄取数据,以及可能的数据准备,使其可以被使用。在某些情况下,数据平台团队倾向于将这种摄取工作委托给数据湖的使用者们。换句话说,数据基础设施是由一个中央团队管理的整体单元,而组织的其他部门则专注于消费场景:商业智能、数据科学或其他需求。随着基于数据的场景数量的增加和组织的扩大,这个通常是一个精简团队的数据平台团队很容易被来自整个组织的请求淹没,并成为数据的关键路径,从而引入了瓶颈。数据网格架构呼吁进行一种文化转变,将数据视为可以在组织间共享的产品,而不是需要收集和处理的资产/实体数据。这种文化转变意味着什么呢?在这一点上,我想引用Zhamak Dehghani在她的书《数据网格》(O'Reilly)中提出的一组重要原则:在组织上,责任的转变从一个做所有事情的中央数据平台组织到一个分散的模型,其中每个业务领域都有专门的人员专注于数据需求。在架构上,从一个庞大的中央数据仓库或数据湖的单体实现转变为数据湖和数据仓库的分布式网格,通过共享洞察和数据仍然对数据进行单一逻辑表示。在技术上,从将数据视为独立实体和平台转变为将数据和业务代码作为一个整体进行集成的解决方案。在运营上,从中央运营模型对数据治理的自上而下指令转变为一种联邦模型,其中每个业务领域都拥有并尊重组织的政策。在原则上,从将数据视为需要收集的资产转变为作为产品为用户服务和提供愉悦体验的产品。参考架构数据网格依赖于分布式架构,由多个领域组成。每个领域都是数据及其相关存储和计算组件的独立单元。当一个组织包含多个产品单位,每个单位都有自己的数据需求时,每个产品团队拥有一个由产品团队独立运营和管理的领域。其角色和责任包括以下内容:中央数据平台团队制定并维护计算、存储和数据组件架构的一套蓝图/参考模式。产品团队实施这些蓝图,以使其领域能够运营。这使得产品团队/领域可以使用其选择的基础设施或技术。例如,一个单位可以在AWS上使用数据湖架构,另一个单位可以在Microsoft Azure上实现现代数据仓库架构,同时仍然共享数据和洞察力。关键原则在于,领域中的数据在组织内共享,并在符合合规和治理要求的边界内,遵循无隔离逻辑数据湖的原则,仍然促进数据和洞察力的共享。数据网格架构的示意图如图2-15所示。数据网格架构的示例用例Klodars Corporation 在其软件产品和团队较小的时候运营良好。然而,随着业务的增长和在更多地区的推出,团队和组织规模显著扩大,中央数据平台无法再满足需求的扩展。此外,由于 Klodars Corporation 收购了使用不同技术堆栈的其他公司,将它们整合成一个统一的单位变得困难。Alice 和她的团队在中央数据平台上决定实施数据网格架构。Klodars Corporation 的中央数据平台团队发布了架构,连同部署和配置脚本,以自动设置数据领域,并建立数据治理、合规和数据共享基础设施。Klodars Corporation 拥有销售、营销和客户成功团队,它们实施自己的数据领域,并与其他组织共享洞察力。销售团队发现现代数据仓库架构适合他们的需求,因为他们大量使用操作数据库;客户成功团队发现数据湖架构更适合他们的需求,因为多样化的数据来源可以使他们的商业智能和数据科学团队受益。数据网格模式使 Klodars Corporation 能够给予其数据领域选择的自由,同时促进数据的共享,保持统一数据平台的特点。此外,Klodars Corporation 收购的公司能够保留其现有的数据湖,只需进行微调。当 Klodars Corporation 想要扩展到冬季装备时,它可以与合作伙伴的滑雪公司共享洞察力,以促进更好的合作关系,进一步扩展数据网格架构。Klodars Corporation 正在迅速增长,并希望将业务扩展到欧洲,该地区具有独特的数据驻留和其他合规要求。它可以设置特定于欧盟(EU)的领域,同时遵守 EU 的特定要求,而无需进行大量的开发或重建工作。在未来,当 Klodars Corporation 收购其他公司时,它可以将所收购公司的数据平台作为领域引入到现有的数据网格中。Klodars Corporation 的数据网格架构示例如图2-16所示。数据网格架构的挑战和优势数据网格具有独特的价值主张,不仅提供基础设施和场景的规模扩展,还有助于改变组织围绕数据的文化,正如我们在前面的部分所看到的。数据网格架构提供以下优点,正如使用案例所示:实现自助架构,能够适应组织的增长和数据多样性为领域提供架构和平台的选择灵活性在整个组织中推广数据文化,而不仅仅是小团队的角色,避免瓶颈的产生当然,这种方法也存在挑战。首先,这依赖于各个产品团队是否有熟练的软件开发人员可用,而这并不总是情况。其次,数据湖架构本身就带来了数据和生态系统多样性的复杂性;添加分布式层级增加了这种复杂性。尽管如此,提前投资于这个领域可以为组织的成功打下基础,并且基于数据网格的不断增长的流行度,我可以做出一个有根据的猜测,即在简化此架构的部署和管理方面将会有快速创新。那对我而言什么才是合适的架构?在本节中,我们讨论了三种主要的热门云数据湖架构:现代数据仓库架构,这在组织中普遍存在数据湖仓架构,使得可以直接在数据湖上运行 BI 场景数据网格架构,提供了一种分散式的管理和操作云数据湖的方法那么,你如何确定选择哪种架构?你如何知道你是正确的?尽管我们都是在实践中不断学习,但有一套基本原则可以帮助你朝着正确的方向前进。了解你的客户就像每个项目一样,首先确定你必须按照优先顺序满足的目标以及你的客户群体。根据你的组织需求,你可以从以下一个或多个客户群体开始:BI/数据分析师:为他们准备数据集以供在数据湖上进行分析。这可以通过运行定期作业从各种来源获取数据并进行数据处理生成数据集来实现。数据科学家/探索性分析:建立一个基础设施,使数据科学家可以将自己的数据集带到数据湖进行分析。你还可以选择管理来自已知来源的数据摄取,并在数据湖上为他们提供数据集。我知道有些客户在继续运行数据仓库的同时开始他们的数据湖之旅;他们没有技术负债或现有的兼容性,并从头开始建立数据湖来支持组织的第一批场景。我也知道有些客户通过解决数据湖上的 BI 用户需求开始他们的数据湖之旅;在这种情况下,他们的目标是现代化数据基础架构以支持新的场景,同时保持对现有流程的支持,因此他们有一定的余地进行重构,但优先级是确保系统正常运行。我还见过一些客户将数据湖用作备份计划,同时继续运行他们的本地系统;在这种情况下,他们的云架构必须是本地系统的复制品,并且他们将在以后的阶段考虑现代化。你可以符合其中一种情况,或者有自己的场景。最重要的是,了解你的客户是第一步:与你的客户和业务决策者交流,了解数据在你的组织中的作用,并展示其潜力。了解你的业务驱动因素虽然新技术非常迷人,也是我继续从事我的工作的原因,但我们始终需要记住,技术只是达到目标的手段,每个决策都需要以你试图为组织解决的问题为基础。有许多业务驱动因素会引导组织采用云数据湖。让我们来看看其中的几个:成本迁移到云数据湖可以保证你的总拥有成本(TCO)降低,根据我的经验,这仍然是组织采用云数据湖方法的主要动因之一。确保在架构决策上与降低成本的目标保持一致。新场景尽管一些组织已经有现有的数据基础设施,但它们有动力转向云数据湖,以利用不断增长的现代技术生态系统,如机器学习或实时分析,以区分其业务和产品。如果这是你的动力,那么你倾向于通过这些新场景提供价值,并应相应地定义目标。你是要通过新的营销活动增加采用率,还是通过智能产品提供价值?再次,将你的技术选择与这些目标相衡量。时间尽管组织迁移到云的动机可能是成本、现代化场景和其他因素,但时间有时会决定技术和架构选择。我见过一些客户制定了迁移到云的路线图,同时他们的本地硬件或软件许可证的支持即将终止。然后你的技术/架构选择将受到时间限制的制约。考虑你的增长和未来场景尽管客户需求和业务驱动力定义了你技术和架构决策的优先级,你需要确保所选择的设计不会束缚你的发展空间。举个例子,如果你的数据湖基础设施是由市场部门的需求驱动的,他们需要运行个性化的营销活动并更好地了解你的客户细分,那么你将设计第一个版本的数据湖架构以满足这些需求。确保你专注于从客户系统和社交媒体数据源中获取数据,并生成可供业务分析师使用的数据集,以便他们选择他们想要为其定制活动的优先级较高的细分群体。然而,你的设计应该预见到当这个第一个场景取得成功后,会有更多的场景和更多的客户。我曾经与一些客户合作,他们总是假定数据工程团队将是唯一能访问数据湖中数据的团队,并没有实施正确的安全和访问控制,结果发现各种场景迅速增长,每个人都能访问所有数据,并造成了意外的数据删除。因此,即使你目前只有一个客户,也要考虑如何设计一个多用户系统,重点关注数据组织、安全和治理。我们将在第三章中详细讨论这些问题。设计考虑因素当我与客户讨论他们的数据湖解决方案时,他们经常问我推荐最便宜或最快的方法,我的回答总是“这取决于情况”,我带着微笑给出这个回答。鉴于云数据湖解决方案的灵活性和多样性,以及软件和平台的生态系统,选择合适的方案和方法几乎就像规划你的家庭预算一样。虽然我们可以做一些笼统的陈述,比如“Costco的价格很优惠!”,但不太被理解的含义是“它依赖于您确保不浪费您大量购买的物品。”云数据湖提供了灵活性和较低的成本,但它们依赖于数据平台团队来确保它们以最优化的方式运行。在表格2-1中,我试图对这些架构在几个可预见的维度上进行评估,以便您可以将其作为确定适合您的合适方案的起点。让我们以另一种方式来看这些数据。图表2-17显示了在不同架构之间成本和复杂性之间的权衡关系。混合方式根据组织需求、方案成熟度和数据平台战略的不同,您可能会采用混合方法来管理您的数据湖。例如,虽然大部分组织使用云数据仓库作为中央数据存储库,但有一个创新中心正在使用数据湖架构进行一组精选场景的工作,并逐步将其扩展到整个公司。或者,虽然大部分组织采用数据湖仓架构,但一些团队仍然依赖需要数年才能迁移的传统基础设施。 您的场景的细微差别可能是如此独特或特定于您的组织,以至于超出了本书的范围。然而,本章讨论的原则将帮助您提出正确的问题并做出明智的数据湖架构选择。 大数据生态系统和云数据湖架构是一个快速创新的领域。我敢肯定,当我完成这一章时,某些方面已经发生了变化。总结在本章中,我们更深入地了解了云数据湖的三种关键架构,并与传统云数据仓库架构进行了比较。首先,我们介绍了现代数据仓库架构,在该架构中,您收集和处理数据,并将相对价值密度较低的原始数据转化为高价值的结构化数据,然后将高价值数据加载到云数据仓库以支持BI场景。接下来,我们介绍了数据湖仓架构,该架构支持在数据湖上直接进行BI场景(以及数据工程和数据科学场景),无需云数据仓库。然后,我们探讨了数据网格架构,它提供了一种分散化的管理和操作数据湖的方法,为组织内不断增长的需求和数据快速增加的情况提供可持续的扩展方式。最后,我们综合考虑了组织的成熟度、技能组合和规模等因素,帮助您制定适合您组织的云数据湖架构。在第三章中,我们将更多关注云数据湖中的“数据”部分:数据组织、管理、安全和治理的考虑因素。
0
0
0
浏览量2018
攻城狮无远

《云数据湖》第一章:大数据——超越噱头

第一章:大数据——超越噱头没有大数据,你就像盲人和聋子一样,置身于高速公路之中。 ——杰弗里·摩尔如果我们正在玩职场宾果游戏,有很大的机会你可以通过划掉以下这些术语来赢得胜利,这些术语你在过去三个月里可能在你的组织中听到过:数字化转型、数据战略、颠覆性洞察、数据湖、数据仓库、数据科学、机器学习和智能。现在众所周知,数据是组织取得成功的关键要素,而依靠数据和人工智能的组织明显胜过竞争对手。根据西格特赞助的IDC研究,到2025年,捕获、收集或复制的数据量预计将增长到175ZB。这些捕获、收集或复制的数据被称为全球数据领域。这些数据来自三类来源:核心传统或基于云的数据中心边缘硬化基础设施,如手机塔终端个人电脑、平板电脑、智能手机和物联网(IoT)设备该研究还预测,到2025年,全球数据领域中有49%将存储在公共云环境中。如果你曾经想过:“为什么需要存储这些数据?它有什么用?”答案非常简单。将所有这些数据想象成散落在全球各地的语言片段,每个片段都分享着一小部分信息,就像拼图的碎片。将它们有意义地拼接在一起,讲述的不仅仅是通知,还可能改变企业、人们甚至世界运转方式的故事。大多数成功的组织已经利用数据来了解业务增长的驱动因素和顾客体验,并采取适当的行动;观察“销售渠道”或顾客获取、采用、参与和保留已经成为产品投资的通用语言。这些类型的数据处理和分析被称为商业智能(BI),属于“离线洞察”。实质上,数据和洞察对于呈现增长趋势非常重要,以便业务领导者能够采取行动;然而,这个工作流程与运行业务本身所使用的核心业务逻辑是分开的。随着数据平台的成熟度提高,我们从所有客户那里得到的不可避免的信号是,他们开始接到更多关于在他们的数据湖上运行更多场景的请求,真正坚持“数据是新的石油”的说法。组织利用数据来了解业务增长的驱动因素和顾客体验。然后,他们可以使用数据设定目标并通过更好的支持和新功能改进顾客体验。他们还可以创建更好的营销策略来推动业务增长,并通过提高效率降低产品和组织建设成本。星巴克是一家全球范围内存在的咖啡店,它在尽可能多的地方使用数据来持续衡量和改进业务。正如在这个YouTube视频中所解释的,星巴克使用来自其移动应用程序的数据,并将其与订购系统进行关联,以更好地了解顾客的使用模式并发送有针对性的营销活动。它在咖啡机上使用传感器,每隔几秒钟就会发出健康数据,这些数据被分析用于提高预测性维护。它还利用这些联网咖啡机下载配方,而无需人为干预。在全球刚刚开始应对COVID-19大流行病的同时,组织们大量利用数据,不仅改变他们的业务,还衡量组织的健康和生产力,以帮助员工保持联系并减少倦怠感。总的来说,数据还被用于像Zamba项目这样的拯救世界的倡议中,在非洲偏远丛林中利用人工智能进行野生动物研究和保护,并利用物联网和数据科学创建循环经济,促进环境可持续性。什么是大数据之前提到的所有例子有一些共同之处:这些场景说明数据可以以多种方式进行探索和使用,并且在数据生成时,通常没有明确的消费模式。这与传统的在线事务处理(OLTP)和在线分析处理(OLAP)系统不同,传统系统的数据是专门设计和策划的,用于解决特定的业务问题。数据可以以各种形状和格式出现:可以是从物联网传感器发出的几个字节,社交媒体数据转储,来自业务线系统和关系数据库的文件,甚至是音频和视频内容。大数据的处理场景大不相同,无论是数据科学、类SQL查询还是其他定制处理方式。研究表明,大数据不仅具有高容量,而且可以以不同的速度到达:可以是一次大规模的数据转储,例如从关系数据库中批量摄入的数据,也可以是持续流式传输的数据,例如点击流或物联网数据。 这些都是大数据的一些特点。大数据处理指的是用于存储、管理和分析数据的一组工具和技术,而不对数据的来源、格式或大小做任何限制或假设。大数据处理的目标是分析大量的数据,其质量可能各不相同,并生成高价值的洞察。之前提到的数据来源,无论是物联网传感器还是社交媒体转储,其中都蕴含着对业务有价值的信号。例如,社交媒体信息中包含了顾客情感的指标:他们是否喜欢某个产品并在推特上发表了评论,或者他们是否遇到了问题并进行了抱怨。这些信号在大量其他数据中隐藏着,形成了较低的价值密度,你需要清洗大量的数据才能获取到少量的信号。在某些情况下,你可能根本没有任何信号。大海捞针,听起来很难找到吧?此外,单独一个信号可能并不能告诉你太多信息;然而,当你将两个较弱的信号结合起来时,你会得到一个更强的信号。例如,来自车辆的传感器数据可以告诉你刹车的使用频率或加速器的按压情况,交通数据可以提供交通模式,汽车销售数据可以提供有关谁买了什么车的信息。尽管这些数据来源各不相同,但保险公司可以将车辆传感器数据和交通模式相关联,建立起一个关于驾驶员安全性的档案,从而为安全驾驶档案的驾驶员提供较低的保险费率。如图1-1所示,大数据处理系统使得大量数据的相关性成为可能,这些数据的价值密度各不相同(价值密度可以被视为信噪比),从而生成具有明确高价值密度的洞察。这些洞察力量可以推动对产品、流程和组织文化的重要转变。大数据通常以六个V来描述。有趣的是,几年前我们只用了三个V来描述大数据:容量(Volume)、速度(Velocity)和多样性(Variety)。如今,我们又增加了三个V:价值(Value)、真实性(Veracity)和可变性(Variability)。这表明在短短几年内,我们发现了更多的维度。谁知道,也许在这本书出版时,可能会增加更多的V!现在让我们来看一下这些V:容量(Volume)这是大数据中的“大”部分,指的是正在处理的数据集的大小。当数据库或数据仓库提到超大规模(hyperscale)时,这可能意味着处理的数据量为数十或数百太字节(TB),在极少数情况下,甚至是拍字节(PB)。此外,您的数据集中可能有数千个列,这又增加了容量的另一个维度。在大数据处理的世界中,处理拍字节的数据更为常见,随着越来越多的场景在数据湖上运行,更大的数据湖很容易扩展到数百拍字节。需要注意的是,在大数据中,容量是一个连续的谱。您需要拥有一个对TB级数据运作良好且能够扩展到数百拍字节的系统。这样,您的组织可以从小规模开始,并随着业务和数据资产的增长而进行扩展。速度(Velocity)大数据生态系统中的数据具有不同的“速度”,即生成速度和移动速度以及变化速度。例如,想象一下社交媒体上的趋势。虽然TikTok上的一个视频可能会迅速走红,但几天后它就完全无关紧要了,为下一个趋势腾出了空间。同样,在健康护理数据方面,比如您的日常步数,虽然它在当时是衡量您活动的关键信息,但几天后它的信号价值就降低了。在这些示例中,您需要大规模地处理数百万甚至数十亿的事件,并在几乎实时生成洞察力,无论是实时推荐热门标签还是离您的每日目标有多远。另一方面,有些情况下数据的价值会持续很长时间。例如,销售预测和预算规划严重依赖过去几年的趋势,并利用过去几个月或几年持续存在的数据。支持这两种情况的大数据系统能够批量摄取大量数据并持续流式传输数据,并能够处理它们,让您在数据湖上运行各种场景并关联来自这些不同来源的数据,从而生成以前不可能实现的洞察力。例如,您可以使用同一系统根据长期模式和社交媒体的快速趋势来预测销售。多样性(Variety)正如我们在第一个V中所看到的,大数据处理系统可以适应各种场景。关键在于支持各种数据的处理。大数据处理系统能够处理数据而不对数据的大小、结构或来源施加任何限制。它们提供了处理结构化数据(数据库表、LOB系统)的能力,这些数据具有定义明确的表格结构和强大的保证,半结构化数据(以灵活定义的结构为特征的数据,如CSV和JSON),以及非结构化数据(图像、社交媒体数据、视频、文本文件等)。这使您可以从有价值的源头获取信号(比如保险或抵押文件),而无需对数据格式做任何假设。真实性(Veracity)真实性指的是大数据的质量和来源。大数据分析系统接受数据时没有对格式或来源做任何假设,这意味着并非所有数据都具有高度结构化的洞察力。例如,您的智能冰箱可以发送一些字节的信息,指示其设备的健康状态,其中一些信息可能会因为实现方式而丢失或不完整。大数据处理系统需要包括数据准备阶段,在进行复杂操作之前对数据进行检查、清理和整理。变异性(Variability)无论是大小、结构、来源还是质量,变异性是大数据系统的核心。任何用于大数据的处理系统都需要具备处理各种类型数据的能力。此外,处理系统可以根据需求定义数据的结构,这被称为按需应用模式。例如,如果您有包含数百个数据点的出租车数据的CSV文件,一个处理系统可以专注于源和目的地的值,而忽略其他部分,另一个处理系统可以专注于司机身份和定价,而忽略其他部分。这是最大的优势:每个系统本身都包含了谜题的一部分,将它们整合在一起可以揭示前所未有的洞察力。我曾经与一家金融服务公司合作,他们从各个县收集了关于房地产和土地的数据;这些数据以Microsoft Excel文件、CSV数据导出或高度结构化的数据库备份的形式呈现。他们处理并汇总这些数据,生成了关于土地价值、房屋价值和区域购房模式的优秀洞察力,从而使他们能够适当地确定抵押贷款利率。价值(Value)这可能已经在前面的观点中强调过了,但需要强调的最重要的V是大数据系统中的数据价值。大数据系统最好的一点是价值不仅仅一次性存在。数据被收集和存储,假设它对不同的受众有价值。数据的价值也随着时间的推移而改变,可能会因为趋势的改变而变得不相关,或者显示出过去具有先例的模式。让我们以销售数据为例。销售数据用于推动收入和税收计算,以及计算销售员的佣金。此外,对销售趋势进行的分析可以用于预测未来趋势和设定销售目标。在销售数据上应用机器学习技术,并将其与看似无关的数据(如社交媒体趋势或天气数据)进行相关分析,可以预测销售中的独特趋势。需要记住的一件重要事情是,数据的价值随着时间的推移有可能贬值,这取决于您尝试解决的问题。例如,包含全球气候模式的数据集在分析气候趋势如何随时间变化时具有很大的价值。然而,如果您试图预测雨伞的销售模式,那么五年前的天气模式就不那么相关了。图1-2说明了这些大数据的概念。弹性数据基础设施——挑战为了让组织能够实现数据的价值,存储、处理和分析数据的基础设施必须具备满足不断增长的数据量和多样化格式需求的能力。这种基础设施不仅必须能够存储任何格式、大小和形状的数据,还需要能够摄取、处理和利用这种多样化的数据,提取有价值的洞察力。此外,这种基础设施需要跟上数据的扩散和不断增加的多样性,并且能够在组织需求增长、对数据和洞察力的需求增加时弹性扩展。云计算基础知识如今,像云计算和弹性基础设施这样的术语已经如此普遍,以至于它们已经成为我们日常语言的一部分,就像“问Siri”或“你在谷歌上搜索了吗?”虽然我们在听到或使用这些术语时不会停顿一下,但它们究竟意味着什么,为什么它们是变革的最大潮流?在我们深入探讨云数据湖之前,让我们先稍微了解一下云计算的基础知识。云计算与组织传统上对待IT资源的方式有很大的转变。在传统的方法中,组织拥有IT部门,该部门购买设备或设备来运行软件。这些设备可以是提供给开发人员和信息工作者的笔记本电脑或台式机,也可以是由IT部门维护并向组织其他部门提供访问权限的数据中心。IT部门有预算来采购硬件,并与硬件供应商共同管理支持。他们还有操作程序和相关的人力资源来安装和更新运行在这些硬件上的操作系统和软件。这带来了一些问题:硬件故障威胁到了业务连续性,由于IT部门资源有限,安装和升级的管理限制了软件开发和使用,而且最重要的是,无法对硬件进行扩展,这阻碍了业务的增长。云计算术语简单来说,云计算可以理解为您的IT部门通过互联网提供计算资源。云计算资源本身由云服务提供商拥有、运营和维护。云并非一概而论,也有不同类型的云:公共云公共云提供商包括微软Azure、亚马逊网络服务(AWS)和谷歌云平台(GCP)等。公共云提供商拥有托管在世界各地的机房中的大量计算机,并且可以让不同组织利用相同的基础设施(称为多租户系统)的计算资源。公共云提供商提供隔离保证,以确保不同组织可以使用相同的基础设施,但一个组织不能访问另一个组织的资源。私有云诸如VMware之类的提供商提供私有云,其中计算资源托管在完全专用于一个组织的本地数据中心中。类比一下,可以将公共云提供商看作是一个商业综合体,可以在同一物理建筑中托管三明治店、面包店、牙医诊所、音乐课程和理发沙龙等各种不同的业务。另一方面,私有云就像一个完全只为一所学校使用的学校建筑。公共云提供商也提供其服务的私有云版本。您的组织可以使用多个云提供商来满足需求,这被称为多云方法。另一方面,一些组织选择采用所谓的混合云,在本地基础设施上拥有私有云,并利用公共云服务,根据需要在两个环境之间移动资源。图1-3说明了这些概念。我们已经谈到了计算资源,但它们究竟是什么呢?云上的计算资源可以分为三个不同的类别:基础设施即服务(IaaS)对于任何服务,都需要一个最基本的基础设施,其中包括提供计算(处理)、存储(数据)和网络(连接)功能的资源。IaaS 提供的是虚拟化的计算、存储和网络资源,您可以在公共云上创建自己的服务或解决方案,利用这些资源。平台即服务(PaaS)PaaS 资源本质上是供应商提供的工具,应用开发人员可以利用这些工具构建自己的解决方案。这些 PaaS 资源可以由公共云提供商或专门提供这些工具的供应商提供。一些 PaaS 资源的例子包括作为服务提供的运营数据库,如微软的 Azure Cosmos DB、亚马逊的 Redshift、Atlas 的 MongoDB 或 Snowflake 的数据仓库,在所有公共云上都提供此服务。软件即服务(SaaS)SaaS 资源提供预先准备好的软件服务,通过订阅方式提供。您可以在任何地方使用它们,无需在计算机上安装任何内容,虽然您可以利用开发人员来定制解决方案,但也可以立即开始使用现成的功能。一些 SaaS 服务的例子包括 Microsoft 365、Netflix、Salesforce 和 Adobe Creative Cloud。打个比方,假设您想要晚餐吃比萨。如果您选择利用 IaaS,您将购买面粉、酵母、奶酪和蔬菜,自己制作面团,添加配料,然后烘烤比萨。您需要是一个烹饪专家才能做到这一点。如果您选择利用 PaaS,您将购买一份预先制作好的比萨,然后将其放入烤箱中烤熟。您不需要是一个烹饪专家,但需要了解如何操作烤箱,并注意确保比萨不会烤焦。如果您使用 SaaS,您将打电话给当地的比萨店,让他们将热腾腾的比萨送到您家。您不需要有任何烹饪专业知识,而且可以直接享用到比萨。云计算的价值主张我经常从客户和组织那里得到的一个最常见的问题是,为什么要首先转向云计算。尽管回报率可能是多方面的,但价值可以分为三个关键类别:降低TCO(总拥有成本)TCO指的是您维护的技术解决方案的总拥有成本,包括数据中心成本、软件成本以及雇佣人员管理运营所需的薪资。几乎在所有情况下,除了少数例外,与在本地部署在您的自有数据中心的解决方案相比,基于云构建解决方案的TCO显著较低。这是因为您可以专注于雇佣软件团队为您的业务逻辑编写代码,而云提供商则为您处理所有其他硬件和软件需求。降低成本的一些因素包括以下内容:硬件成本云提供商以较低的成本拥有、构建和支持硬件资源,而不是您自己建立和运营数据中心、维护硬件以及在支持结束时更新硬件。此外,随着硬件的进步,云提供商能够更快地提供新硬件,而不是您自己建立数据中心。软件成本除了构建和维护硬件之外,IT组织的主要工作之一是支持和部署操作系统并进行更新。通常,这些更新涉及计划停机时间,可能对您的组织产生干扰。云提供商在不给您的IT部门增加负担的情况下负责处理这一周期。几乎在所有情况下,这些更新以抽象的方式进行,因此您不需要受到任何停机时间的影响。按需付费 大多数云服务采用基于订阅的计费模式,这意味着您按照实际使用量付费。如果您的资源仅在一天的某个时间段或一周的某些天使用,您只需支付该时间段的费用,这比始终拥有硬件要便宜得多,即使您不使用它。弹性扩展 您业务所需的资源具有高度的动态性,有时需要为计划和非计划的使用增加资源。当您维护和运行自己的硬件时,您受到现有硬件的限制,无法支持业务的进一步增长。云资源具有弹性扩展的能力,您可以通过几次点击利用额外的资源迅速应对高需求。跟上创新 云提供商不断创新,并根据从多个客户那里得到的经验为其服务添加新的服务和技术。利用先进的服务和技术,相比于拥有内部开发人员可能缺乏跨行业所需广度的情况下,有助于您更快地为业务场景创新。云数据湖架构要理解云数据湖如何帮助组织满足不断增长的数据需求,首先我们需要了解几十年前数据处理和洞察力是如何运作的。过去,企业通常将数据视为解决业务问题所需的补充。这种方法以业务问题为中心,包括以下步骤:确定需要解决的问题。定义一个能够帮助解决问题的数据结构。收集或生成符合结构的数据。将数据存储在OLTP数据库中,如Microsoft SQL Server。使用另一组转换(过滤、聚合等)将数据存储在OLAP数据库中;在这里也使用SQL服务器。从这些OLAP数据库构建仪表板和查询来解决业务问题。举例来说,当组织想要了解销售情况时,它会构建一个应用程序,供销售人员输入他们的潜在客户、客户和销售数据,而这个应用程序受到一个或多个操作数据库的支持。可能有一个数据库存储客户信息,另一个存储销售人员信息,还有一个存储销售信息,引用了客户和销售人员数据库。本地部署(称为“on prem”)有三个层次,如图1-4所示:*企业数据仓库 *这是数据存储的组件。它包含一个用于存储数据的数据库组件和一个用于描述数据库中存储的数据的元数据组件。数据集市数据集市是企业数据仓库的一部分,其中包含以业务或主题为重点的数据库,其中的数据已准备好为应用程序提供服务。仓库中的数据经过另一组转换,以存储在数据集市中。消费/商业智能(BI)这包括BI分析师使用的各种可视化和查询工具,用于查询数据集市(或仓库)中的数据以生成洞察力。本地数据仓库解决方案的局限性虽然这种架构在为业务提供洞察力方面效果良好,但存在一些关键的局限性:高度结构化的数据:这种架构期望数据在每个步骤中都是高度结构化的。正如前面的示例所示,这种假设不再现实;数据可以来自任何源,例如物联网传感器、社交媒体信息和视频/音频文件,可以是任何格式(JSON、CSV、PNG等)。在大多数情况下,无法强制执行严格的结构。数据存储的孤立:相同数据的多个副本存储在专为特定目的而设计的数据存储中。这是一个劣势,因为存储相同数据的副本需要付出高昂的成本,而来回复制数据的过程既昂贵又容易出错,导致在复制数据的过程中存在不一致的数据版本。针对高峰使用率的硬件规划:本地数据仓库要求组织安装和维护运行这些服务所需的硬件。当您预期需求激增时(例如财年结束预算或假日销售预测增加),您需要提前计划此高峰使用率并购买硬件,即使这意味着一些硬件在其余时间内处于低利用率状态。这会增加总拥有成本。请注意,这特指本地硬件的限制,而不是数据仓库和数据湖架构之间的区别。什么是云数据湖架构?正如我们在《什么是大数据?》一文中所看到的,大数据场景远远超出了传统企业数据仓库的范畴。云数据湖架构旨在解决这些确切的问题,因为它们被设计来满足数据和数据来源的爆炸性增长需求,而无需对数据的来源、格式、大小或质量做出任何假设。与传统数据仓库采用以问题为先的方法不同,云数据湖采用以数据为先的方法。在云数据湖架构中,所有数据都被视为有用的,无论是立即使用还是满足将来的需求。云数据架构的第一步是以原始、自然状态摄取数据,对数据的来源、大小或格式没有任何限制。这些数据存储在云数据湖中,这是一个高度可扩展且可以存储任何类型数据的存储系统。这些原始数据具有不同的质量和价值,并且需要更多的转换才能生成高价值的洞察。如图1-5所示,云数据湖上的处理系统对存储在数据湖中的数据进行处理,并允许数据开发人员按需定义模式,即在处理时描述数据。然后,这些处理系统对低价值的非结构化数据进行操作,生成通常是结构化且包含有意义洞察的高价值数据。然后,这些高价值、结构化的数据可以加载到企业数据仓库中供使用,或直接从数据湖中使用。如果所有这些概念似乎非常复杂,无需担心——我们将在第2章和第3章中详细介绍这些处理过程。云数据湖架构的好处在较高层面上,云数据湖架构通过以下方式解决了传统数据仓库架构的限制:数据无限制:数据湖架构由专为摄取、存储和处理各种类型数据而设计的工具组成,而且不对数据的来源、大小或结构施加任何限制。此外,这些系统可以处理实时连续产生的数据以及按计划批量摄取的大量数据。此外,数据湖存储成本非常低廉,因此可以默认存储所有数据,而不必担心费用问题。回想一下,以前使用胶卷相机拍照时可能会三思而后行,但现在使用手机相机可以毫不犹豫地点击拍摄。单一存储层,无隔离区:在云数据湖架构中,处理数据发生在相同的存储层,因此不再需要为特定目的使用专用数据存储。这不仅降低了成本,还避免了在不同存储系统之间来回移动数据时可能出现的错误。在同一数据存储层上运行多样化的计算:云数据湖架构天然地将计算和存储解耦,因此可以在同一存储层上运行各种数据处理计算工具。例如,可以利用相同的数据存储层进行类似数据仓库的商业智能查询、高级机器学习和数据科学计算,甚至是针对特定领域的定制计算,如高性能计算(如媒体处理或地震数据分析)。按需付费:云服务和工具始终以根据需求弹性扩展和收缩的方式设计,可以按需创建和删除处理系统。这意味着在节假日季节或预算结算期间需求激增时,可以选择启动这些系统,而无需将它们保留整年。这极大地降低了总体拥有成本(TCO)。独立扩展计算和存储:在云数据湖架构中,计算和存储是不同类型的资源,它们可以独立扩展,从而使您能够根据需要扩展资源。云上的存储系统非常便宜,可以让您存储大量数据而不会让您负债累累。相比之下,计算资源传统上比存储资源更昂贵;然而,可以根据需求启动或停止计算资源,从而提供经济性的扩展。以一种成本效益的方式处理各种类型的数据的这种灵活性有助于组织实现数据的价值,并将数据转化为有价值的变革性见解。定义您的云数据湖之旅我与数百个客户讨论过他们的大数据分析场景,并帮助他们完成了云数据湖之旅的部分工作。这些客户有不同的动机和问题需要解决:一些客户是云计算的新手,希望在数据湖方面迈出第一步;还有一些客户在云上实施了数据湖,支持一些基本场景,但不确定接下来该怎么做;还有一些客户是云原生的用户,希望从应用架构的角度开始使用数据湖;还有一些客户已经在云上实施了成熟的数据湖,并希望利用数据的力量提供与同行和竞争对手相比的差异化价值的下一个层次。如果我必须总结我从所有这些对话中学到的东西,基本上可以归结为两个关键要素:不论您的云成熟度水平如何,设计数据湖应考虑公司的未来。根据您目前的需求做出实施选择!您可能会觉得这听起来太显而易见和太普遍了。然而,在本书的其余部分中,您将会观察到我为设计和优化云数据湖提供的框架和指导,都是基于您不断将自己与这两个问题进行核对:什么业务问题推动了对数据湖的决策?当我解决了这个问题后,还能做什么来通过数据湖使我的业务具有差异化?让我给您举一个具体的例子。驱使客户实施云数据湖的常见情况是,他们的本地硬件支持的Hadoop集群即将到达寿命期限。这个Hadoop集群主要由数据平台和商业智能团队使用,用于构建仪表板和数据立方体,其中数据来自他们本地事务存储系统。公司需要决定是购买更多硬件并继续维护本地硬件,还是投资于这个大家一直谈论的云数据湖。云数据湖承诺弹性伸缩、更低的拥有成本、更多可利用的功能和服务,以及我们在前面部分看到的其他好处。当这些客户决定转向云时,他们面临一个时限,即硬件达到寿命时需要及时处理。因此,他们选择了一种将现有的本地实施方案迁移到云端的提升和迁移策略。这是一种完全合理的方法,特别是考虑到这些是为关键业务功能提供服务的生产系统。然而,这些客户很快意识到以下三个问题: 即使是进行提升和迁移实现也需要很多工作量。 如果他们意识到云的价值并希望添加更多场景,他们会受到设计选择的限制,例如安全模型、数据组织等,这些设计最初假定数据湖上运行一组BI场景。 在某些情况下,提升和迁移架构在成本和维护方面可能会更加昂贵,从而抵消了最初的目的。嗯,这很令人惊讶,不是吗?这些意外主要源于本地和云系统之间的架构差异。在本地的Hadoop集群中,计算和存储是共存且紧密耦合的,而在云上,理念是拥有一个对象存储/数据湖存储层,例如Amazon S3、Azure Data Lake Store(ADLS)和Google Cloud Storage(GCS),并且提供大量的计算选项,可以作为IaaS(提供虚拟机并运行自己的软件)或PaaS(例如Azure HDInsight、Amazon EMR等)提供,如图1-6所示。在云上,您的数据湖解决方案本质上是您用乐高积木搭建的结构,可以是IaaS、PaaS或SaaS的产品。我们已经看到了解耦合的计算和存储架构在独立扩展和降低成本方面的优势;然而,这要求您的云数据湖的架构和设计尊重这种解耦合的架构。例如,在云数据湖的实施中,计算系统通过网络系统与存储系统进行通信,而不是本地调用。如果您没有对此进行优化,会影响您的成本和性能。类似地,一旦您完成了主要的商业智能场景的数据湖实施,您现在可以通过启用更多场景、引入不同的数据集或对数据湖中的数据进行更多的数据科学探索性分析来从数据湖中获取更多价值。与此同时,您希望确保一个数据科学的探索性任务不会意外删除您的数据集,而这些数据集是为您的销售副总裁每天早上要查看的仪表板提供动力的。您需要确保您现有的数据组织和安全模型能够确保这种隔离和访问控制。 将这些令人惊奇的机会与您最初迁移到云的动机联系起来,即您的本地服务器即将到达寿命期限,您需要制定一个计划,帮助您按时完成,并为您在云上的成功做好准备。您迁移到云数据湖需要实现两个目标:关闭您的本地系统在云上为您的成功做好准备大多数客户最终只关注第一个目标,在重新架构应用程序之前陷入巨大的技术债务中。当您考虑云数据湖架构时,请确保以下内容成为您的目标:将数据湖迁移到云端。使数据湖现代化以适应云架构。这两个目标将携手助您识别出一个能够适应业务日益增长的规模和需求的稳健架构。 要实现这两个目标,您需要了解云架构是什么,实施的设计考虑因素是什么,以及如何优化数据湖的扩展和性能。我们将在第2-4章中详细讨论这些问题。我们还将重点提供一个框架,帮助您考虑云数据湖之旅的各个方面。总结在本章中,我们首先讨论了数据的价值主张以及可以改变组织的转型性见解。我们还建立了对云计算的基本理解,以及传统数据仓库和云数据湖架构之间的差异。最后,我们介绍了大数据、云计算和数据湖的概念。鉴于本地和云架构之间的差异,我们强调了一种思维方式的转变的重要性,这种转变进而在设计云数据湖时定义了一种架构转变。当我们深入探讨下一章中云数据湖架构及其实施考虑因素的细节时,我恳请您进行这种思维上的转变。这种思维变革是我在接下来的章节中深入讨论云数据湖架构及其实施考虑因素时强烈推荐您做出的一件事。
0
0
0
浏览量2031
攻城狮无远

《云数据湖》第三章:为您的数据湖考虑设计要素

第三章:为您的数据湖考虑设计要素不要害怕完美,因为你永远无法达到它。 --萨尔瓦多·达利在第1章和第2章中,我们从上帝视角了解了云数据湖是什么以及云上一些广泛使用的数据湖架构。前两章中的信息为您提供了足够的背景,以开始构建您的云数据湖设计;您至少需要能够拿起一支干擦笔并勾画一个表示云数据湖架构组件及其相互作用的块图。在本章中,我们将深入探讨云数据湖架构的实施细节。正如您所记得的,云数据湖架构由各种IaaS、PaaS和SaaS产品组成,这些产品组装成一个端到端的解决方案。将这些个别服务视为乐高积木,而您的解决方案就是您用乐高积木搭建的结构。您可能最终会建造一个堡垒、一条龙或一艘宇宙飞船——选择仅取决于您的创造力(和业务需求)。然而,有一些基本概念您需要理解,这正是我们在本章中要讨论的内容。我们将继续使用Klodars Corporation来举例说明一些决策选择。建立云数据湖基础设施大多数云数据湖架构可归为以下两类:从头开始在云上构建云数据湖。您没有先前的数据湖或数据仓库实现,从零开始建设。将数据湖从本地系统或其他云提供商迁移到云上。在这种情况下,您已经有了现有的实现,可以是数据仓库或数据湖,您将其迁移到云上。在这两种情况下,您在迁移到云上的旅程中的第一步基本上是相同的。您将选择云提供商,选择服务,并设置基础设施。云提供了各种各样的选择供您选择,每种选择都有其优势和机会,因此在转向云之前,首先要记住的是,在进行这种转变时,没有什么万能药或规定的12步过程。然而,通过与许多客户以及亲自参与云迁移,我将客户旅程总结为一个决策框架,包括以下关键步骤,如图3-1所示:确定您的目标。计划架构和可交付成果。实施云数据湖,可以从头开始构建,也可以将现有系统迁移到云上。运营和发布。确定你的目标正如我们在第一章中所看到的,数据湖对于获取关键洞见以推动业务发展至关重要。然而,在众多可能性中,明确数据湖为您的组织提供的具体目标非常重要。这些目标将帮助您确定需要优先处理的数据和处理方式,以满足业务需求。作为第一步,您需要确定数据湖的客户是谁,他们可以是组织内的部门(例如人力资源、财务、供应链)或组织外部的客户(例如消费您的仪表板的客户)。此外,还要查看您当前数据湖实施中的问题,如果有的话。例如,您当前可能面临数据中心和运营成本过高,耗尽了预算;或者您当前的硬件已经不再得到支持;或者您当前的架构无法支持数据科学和机器学习等高级场景,从而导致您的业务在竞争激烈的市场中失去优势。一旦与各种客户进行交流并了解他们最关注的问题,您可以确定数据能够帮助解决的一些头等问题的子集。您在数据湖实施中的目标将是解决这些问题,如图3-2所示。通过与客户沟通并解决他们的关键问题,您的数据湖实施将能够为组织提供有价值的解决方案,并为业务提供所需的洞见和转型机会。如图3-2所示,您的团队通过与各个消费者的交流,已经确定了10个问题。然而,当您将它们在高严重性与云数据湖在解决这些问题中的有用性之间进行绘制时,您会发现应该优先考虑Problem5、Problem6、Problem8和Problem9,因为它们在严重性和云数据湖的有用性方面都排名较高。Klodars Corporation是如何定义数据湖目标的呢?正如我们在第2章中所看到的,Klodars Corporation目前使用传统应用程序,利用操作数据库(如SQL Server)来管理其库存和销售情况,以及使用客户关系管理(CRM)软件(如Salesforce)来管理其客户资料和互动情况。当Klodars Corporation在迅速扩展至华盛顿州和周边州份,并且在线业务增长时,其软件开发负责人Alice向高管们推荐开发数据湖的想法,并且他们热衷于投资这种方法。 现在该项目已经启动,Alice开始规划数据湖的实施。她首先采取的第一步是对整个组织的问题进行清点,并列出了在表格3-1中概述的问题列表。根据这份清单,Alice将她的数据湖实施目标定义如下,并与相关利益相关者进行审查以最终确定目标:(必备)通过在第75百分位数处查询性能提升50%来支持现有销售和营销仪表板的更好扩展和性能。(必备)支持在数据湖上进行数据科学模型,通过在产品推荐方面向执行团队进行试点项目的参与来衡量。(可选)支持更多的数据科学模型在数据湖上,通过销售合作伙伴的下一组场景和营销的影响者推荐来衡量。规划架构和交付成果确定数据湖的目标后,下一步是定义架构和交付成果。正如我们在第2章中所见,有多种常用的架构模式,每种模式都有其设计考虑因素,如“设计考虑”一节所述。结合数据湖的目标和组织成熟度等其他因素,确定适合数据湖的正确架构。以下是确定架构时的一些常见考虑因素:解决方案的成本确定设置初始成本以及维护解决方案的长期成本,并将其与收益进行权衡。数据湖比数据仓库要便宜得多,但数据仓库更容易启动和运行。实施所需的时间在软件开发领域,开发人员和运营时间与金钱一样重要,因此在计算中考虑人力和运营方面,并估计实施解决方案所需的时间和精力。如果您有即将到期的现有硬件,您需要选择一种能在现有硬件支持终止之前实施的模式/架构。向后兼容性当您需要将现有数据基础架构迁移到云端时,合理地假设您的云迁移将分阶段进行。您需要确保在将解决方案的某些部分移至云端时,可以保证业务连续性,而不会对现有应用程序和使用者造成太多的中断。如果您有支持仪表板的现有操作数据库,那么确保现有应用程序的兼容性是需要考虑的因素。组织成熟度这是大多数营销材料中经常被忽视的因素。当您与云服务提供商或独立软件供应商(ISV)交谈时,请确保讨论组织的当前技能集和数据文化,并了解他们的解决方案如何适应当前状态,同时计划提升组织技能并转变其文化。例如,如果您尚未雇佣数据科学家,那么针对数据科学进行优化的架构并不适合您。一旦您确定了架构,您可以与客户进行合作,并根据您将支持的优先级列表来定义云数据湖架构的目标。然后,您可以创建一个项目计划,跟踪时间表、交付成果以及整体进展,以实现您的目标。Klodars Corporation对他们的架构和可交付成果的计划基于为云数据湖实施定义的目标,Alice和她的团队使用以下指导原则调查了不同的架构选择:对销售和营销团队使用的现有仪表板最小/没有干扰。仪表板需要根据数据的增长进行扩展,并解决客户面临的性能问题。数据科学场景需要作为新平台的一部分进行处理。包括向高管团队提供产品推荐,向销售团队提供分销商/零售商推荐,以及向营销团队提供影响者推荐。考虑到仪表板对业务的重要性以及预计的未来增长,新架构需要在接下来的六个月内推出。 他们评估了以下云端的架构模式(这与具体的提供者无关):云数据仓库在这种架构模式中,没有涉及云数据湖,主要组件将是云数据仓库。如果您想对云数据仓库有更多了解,请参考“云数据仓库”。尽管这需要实施的时间最短,并且对销售和营销团队的业务分析师来说是一个无缝体验,但数据科学能力有限。因此,这可以作为一个试点,但在场景变得更加复杂(例如引入更多多样化的数据集)时可能会遇到障碍。现代数据仓库正如我们在“现代数据仓库架构”中所看到的,这涉及利用云数据湖存储进行数据收集和处理,并使用云数据仓库进行商业智能场景。这需要比云数据仓库更长的时间来实施。然而,数据湖上丰富的数据科学能力将帮助团队专注于试点项目,同时通过数据仓库支持数据分析师。此外,数据湖提供了更便宜的存储方式,用于保存本地数据库的多个历史数据快照。数据湖仓库正如我们在“数据湖仓库架构”中所看到的,这涉及利用云数据湖存储进行端到端实现,而无需云数据仓库组件。对于团队来说,这非常有吸引力,因为它支持数据科学和数据分析师场景;然而,他们很快意识到需要对工具支持和端到端自动化进行提升。团队没有评估数据网格架构,因为他们希望专注于保持端到端实现的中央控制。他们将在项目的下一阶段评估数据网格架构。 团队决定采用现代数据仓库架构,因为它提供了与当前架构更顺畅的过渡,同时也支持数据科学。团队还计划在项目的下一阶段,在他们在云上运行后,对数据湖仓库和数据网格架构进行调查研究。正如图3-3所示,Klodars Corporation的现代数据仓库架构包括以下组件:云数据湖存储,作为数据的中央存储库。数据摄取系统,将来自现有来源(如操作数据存储)和新数据来源(如社交媒体)的数据上传到云数据湖中。数据处理引擎,对云数据湖存储中的数据进行复杂的数据转换处理,生成高价值的数据。数据科学和机器学习解决方案,由数据科学家用于临时的探索性分析。云数据仓库,将这些高价值数据提供给商业智能用例和数据分析师。项目的可交付成果包括以下阶段:阶段1:数据摄取:将数据加载到数据湖中。建立自动化流水线,将操作数据库和CRM系统的每日备份存储到数据湖中。保留过去90天的数据。阶段2:数据处理:包括两个可以并行运行的阶段:处理商业智能数据:运行处理流水线,将数据每日刷新到云数据仓库中。通过与销售和营销团队的数据分析师的反馈进行验证。高级场景:基于操作数据库和CRM系统的数据,开发产品推荐模型,利用数据科学家进行数据分析。阶段3:有限发布:在本地部署实施仍在运行时,将数据湖平台发布给销售、营销和高管团队的特定客户。这有助于早期发现问题并根据客户反馈进行迭代。阶段4:投入生产:将数据湖发布给Klodars Corporation的所有客户。此时,本地部署和云平台并行运行。阶段5:关闭本地仪表盘:一旦云上的数据湖实施成功,将关闭本地分析仪表盘。实施云数据湖在这个阶段,根据项目计划来实施架构。这个阶段涉及到选择云提供商和遵循基础设施的最佳实践。由于选择云提供商涉及到更多的因素,超出了技术层面,我们不会在这里详细讨论。在这个阶段的一些重要考虑因素如下:设置和管理云身份:在云提供商上创建您的身份管理系统是开始使用云的关键步骤。如果您在本地有一个身份管理系统,云提供商允许您将本地的身份与云进行联合。设置订阅:创建资源(基础设施即服务、平台即服务或软件即服务)需要一个订阅。订阅还有相关的访问控制;您可以将您的云托管身份分配给订阅的特定角色(所有者、贡献者等)。创建环境:强烈建议为开发(供开发人员测试其代码使用)、预生产(开发人员和部分客户可访问)和生产创建独立的环境。我还建议您为不同的环境使用不同的订阅,以使它们相互隔离。如果您在不同的地区拥有业务,而这些地区有不同的要求,您可以为每个地区创建独立的环境,例如北美或欧洲。选择服务:云提供商为云数据湖架构提供各种服务(基础设施即服务、平台即服务或软件即服务)。花些时间与云提供商交流,根据机会、业务需求和成本做出正确的选择。投资于自动化和可观察性:除了实施数据湖本身,确保您具备必要的自动化功能来按需创建和管理资源。考虑到您在云上按使用量付费(而不是一直拥有硬件),自动化将确保您能够按需创建和删除资源,以帮助管理成本。同样,确保您在云上具备日志记录和监控功能,以便监视系统的健康状况。要获取更多信息,您可以查阅以下顶级云提供商AWS、Microsoft Azure和Google Cloud的入门文档。在你的数据湖上组织数据一旦您建立并测试了端到端的基础架构,下一步就是进行数据摄取。在将数据摄取到数据湖之前,确保您有一个组织数据的策略非常重要。就像您设置厨房时,需要知道在哪个橱柜中存放瓷器、锅具和咖啡一样。您会确保锅具更容易拿到灶台附近,而咖啡、糖和奶精则放在一起存放。为了理解数据湖中的数据组织方式,让我们看一下数据在数据湖中的生命周期。数据的一天首先,数据以其原始的自然状态从各种来源被摄取到数据湖中。然后,对数据进行清洗和准备,例如应用模式、去重、删除空值以及在某些情况下修复空值为默认值。在数据准备阶段结束时,数据按照数据准备代码定义的表格结构进行组织。接下来,对数据进行聚合、连接和筛选,以提取高价值的数据,这个阶段被称为数据策划。除了按照这个生命周期进行处理的数据外,数据科学家等消费者也可以引入自己的数据集进行探索性分析。这个生命周期是一个重要的点,因为它标志着不同的模式。在过去几十年中,用于分析的数据加载的最常见模式是ETL:提取、转换和加载。数据从源头提取出来,经过转换以符合特定结构,然后加载到存储(Hadoop文件系统或数据仓库)中。如果在转换过程中丢失了数据的信号,重新从源头检索该信号不是微不足道的,而且在某些情况下是不可能的。此外,随着云基础设施和硅技术的创新,存储成本越来越便宜。数据价值的增加与存储成本的降低相结合,催生了一种新的模式,称为ELT:提取、加载和转换。在这种模式中,数据从源头提取出来,加载到数据湖中,然后再进行数据处理和转换。数据湖区域按照数据的生命周期,数据将被组织到数据湖中的不同区域。现在让我们详细了解这些区域。图3-4提供了每个区域的可视化,本节中我们将逐个介绍它们。数据湖中的数据根据处理阶段和数据中的价值密度可以划分为以下区域:原始数据(铜)区域该区域包含从源头获取的数据,处于其自然状态。该区域的数据具有最低的价值密度(即高信噪比),准备经过各种转换以生成有价值的洞察力。增强数据(银)区域该区域包含经过转换的原始数据版本,符合业务场景的结构要求。此时,价值密度为低到中等,但对数据遵循模式或结构有一定的保证。精选数据(金)区域该区域包含价值密度最高的数据。该区域的数据是通过对增强数据应用转换生成的。工作区数据区域该区域为数据湖的消费者提供自己的数据集。对于该数据区域没有特定的保证;可以将其视为在数据湖中预留的草稿区。现在让我们详细了解这些部分:原始数据(铜)区域这是数据湖的一个部分,外部数据首先被存储在这里。在我们讨论的ELT模式中,这是从源头提取并加载到数据湖存储中的数据。数据可以来自各种来源,例如本地系统、社交媒体、外部开放数据集等等。数据可以按计划方式摄入(批量上传)或实时方式摄入(事件)。该区域的数据几乎没有保证、结构、语义和价值。通常情况下,这种摄入通常由一组特定的团队控制,而不是对所有人开放。该区域的数据将按摄入源和数据的时间戳进行组织。在典型的数据湖场景中,大多数数据都与时间相关(例如某一天的Twitter数据源,某一天的服务器日志等)。因此,该区域通常按源进行组织,源内按时间组织;时间结构表示数据的新鲜程度。丰富的数据(银)区域原始数据在被摄入到数据湖时并不完全符合特定的结构或格式。正如我们在《关于数据的多样性》中所见,被摄入到数据湖的数据可以是结构化、半结构化或非结构化的。然后,数据经过清洗和处理,以适应表格结构;这个过程被称为数据准备、丰富或烹饪。在这一步骤中通常会发生以下三件关键的事情:模式定义模式实质上是数据所遵循的结构定义,即为数据的不同部分赋予意义,使其成为具有列定义的表格结构。数据清洗一旦定义了模式,可能会有一些数据部分不符合模式。例如,如果模式指示你的CSV文件的第五个字段是邮政编码,那么这一步骤会确保值符合邮政编码格式(XXXXX或XXXXX-XXXX),如果不符合,则要么删除它,要么填充合法的值(例如从地址中查找邮政编码)。优化一旦数据符合表格结构,还需要准备数据以针对最常见的使用模式进行优化。最常见的模式是数据只写一次,读取多次。例如,如果你有一个销售数据库,对该数据的最常见查询往往是针对特定地区的销售信息或随时间变化的趋势。在这一步骤中,数据被转换成一种格式并重新组织,以便对最常见的查询友好。通常选择像Parquet这样的列式格式(在《数据格式》中介绍过)来进行查询优化。这些数据更广泛地被不同的团队、数据科学家以及在某些情况下还有业务分析师用于更临时的分析。该区域的数据被组织成领域或各种消费者可以理解的单位,并在目录中发布(我们将在《数据治理简介》中详细讨论此内容)。策划数据(金)区域策划数据区域是数据湖中价值最高的数据和支撑关键业务仪表板的关键数据集。将该区域中的数据视为数据价值的摘要或总结,以及关键的洞察力。在该区域中,通过执行聚合、过滤、关联来自不同数据集的数据以及其他复杂计算等方式处理数据,从而为解决业务问题提供关键洞察力。该区域的数据使用策划区域的数据作为其来源,有时还会使用其他数据源。鉴于其对业务的广泛影响,该区域的数据在数据质量方面需要符合最高标准。策划数据主要由业务分析师使用,并为关键业务决策者提供支持的仪表板提供动力。此外,这些数据会发布到目录中,并按业务单元和相关领域进行组织。工作区或沙盒区(可选)正如我们所讨论的,原始数据、丰富数据和策划数据区域中的数据大部分由一组精选的数据工程师或数据平台团队进行管理。然而,消费者可能希望带来一些数据,无论是用于探索性分析还是测试。这些数据可以组织到专门为用户配置的单元中(将其视为临时工作区)。此处的数据不受任何特定标准或质量的约束,基本上可以自由使用。图3-5展示了Klodars公司的数据组织方式。组织机制虽然这不是一个强制要求,但按照使用模式来组织数据是一个良好的实践。如我们在“数据湖区域”中讨论的那样,数据在数据湖中经历了特定的生命周期,您可以将数据按照区域进行分组以实现组织。这种组织机制在数据湖中数据和使用量不断增长的情况下非常有用。组织机制有许多优点,包括以下几点:控制对数据的访问:例如,原始区域中的数据访问通常被限制在数据平台团队,并由您来管理。当新员工加入营销部门时,他们将自动获得对营销业务单元区域中的数据的访问权限。管理数据保留:例如,当您为用户提供工作区域区域以导入他们自己的数据时,由于数据本身不受数据平台团队的管理,该数据有可能无法受控地增长。数据平台团队可以为特定区域设置数据保留策略,以管理这种无法控制的增长。主要云提供商的数据湖存储服务提供了不同的方式来组织数据区域。Amazon S3和GCS提供了可以利用的存储桶作为组织数据的单位。Microsoft Azure Data Lake Storage提供了一个本地文件系统,其中存储帐户、容器和文件夹作为数据组织的单位。数据治理导论无论您在数据之旅中的哪个阶段,无论是初步尝试还是已经具备成熟的数据湖实施,数据量及其对业务的价值只会增加。正如蜘蛛侠中Ben大叔所说:“伴随巨大的力量而来的是巨大的责任。”让我们来看看管理数据可能面临的一些挑战:您收集的数据可能包含个人或业务关键信息,例如个人姓名和地址,或者商业机密,如果落入错误的手中,可能对个人或业务造成潜在伤害。像雅虎、斯达伍德万豪酒店、阿里巴巴等巨大企业多年来都不得不处理数据泄露问题。数据平衡和完整性中的错误可能导致分析结果偏离,从而为用户构建非理想的体验。2016年,微软推出了一款会话式人工智能聊天机器人,由于所学习的数据集,该机器人很快学会了发布种族主义和性别歧视的消息。随着数据湖中数据的增长和使用量的增加,这些数据集的管理和发现变得极为重要。如果没有这些功能,您的数据湖可能会变成一个数据沼泽。为了更好地理解这一点,想象一下那个杂乱无章的壁橱(或房间、车库或阁楼),在某个时候,您甚至不知道里面有什么,直到搬家时才去接触它。涉及云数据湖运营时,有越来越多的数据隐私法规,例如《通用数据保护条例》(GDPR)、《加利福尼亚消费者隐私法》(CCPA)等,您需要注意遵守这些法规。参与数据治理的角色数据治理是一个统称,指的是确保组织使用的数据安全、可访问、可用和合规的技术、工具和流程。在组织的数据治理中,有四个主要角色。数据治理工具旨在满足其中一个或多个角色的需求。需要注意的是,这些角色不一定是人类用户,也可以是应用程序或服务。此外,同一团队或组织可以扮演一个或多个角色:数据官员这个团队主要负责管理数据的定义和要求,确保数据的可信度,并定期进行审计和评审以确保要求得到满足。这涉及到定义和管理数据共享要求、数据保留政策以及数据资产需要满足的合规法规等控制措施。他们设定了数据治理的标准。数据工程师这些角色负责实施数据湖架构。他们提供和设置各种服务,管理角色的身份,并建立正确的技术和流程,确保数据和相关洞见具有所需的可信度。换句话说,他们实施和管理基础设施和技术,确保实施符合数据官员定义的要求。他们利用各种工具集来了解数据经历的不同处理阶段,这个过程被称为跟踪数据的谱系,并确保数据的质量和一致性。他们还在审查和审计中提供相关证据和支持材料,起着关键的作用。数据生产者这些是数据湖用户的一个群体。顾名思义,数据生产者将数据引入您的数据资产中。这些数据可以是原始数据(从外部和内部来源摄取)、丰富数据(经过准备/处理的表格形式数据)、策划数据(具有高价值洞见的摘要/概要),或由数据科学家生成和使用的临时数据集。数据生产者非常关注确保数据的质量符合一定的标准,具体取决于数据的类型,并希望了解如何在访问管理方面将数据限制或开放给组织其他部分。他们需要遵守数据官员和数据工程师设定的工具和流程,并不试图规避限制措施。数据消费者这些是数据湖的另一个用户群体。与其他任何产品一样,只有当有客户使用时,产品才有价值。数据也不例外。无论他们使用仪表板、可查询的表格还是临时数据集,数据消费者是指使用数据或洞见的任何人。数据生产者控制着谁可以使用数据,而数据消费者实际上使用这些数据,要么按原样使用,要么进行进一步处理。例如,在数据策划过程中,一组用户或应用程序最终使用丰富的数据并生成策划的数据。图3-6提供了与数据资产(在本例中是数据湖)交互的各种角色所关注的一些主要问题的非常简化的概述。 为这些不同的角色提供了许多工具和自动化功能,以促进更好的数据治理并遵循最佳实践。话虽如此,您也可以通过手动操作进行数据治理。数据治理可以分为可以通过工具和自动化完成的任务以及通过手动执行的流程来实现。数据分类数据治理始终从数据官员开始,他们负责定义可以收集、存储和处理的数据的要求和控制措施。数据类型指的是组织使用的数据或资产的类型。您的数据湖中的数据需要标记为其中一个或多个数据类型。数据类型本身也被称为信息类型(infotype),它具有与之相关联的单一含义,例如包含名字或邮政编码的数据。这些信息类型被组织成数据类别。例如,姓名和地址属于个人身份信息(PII)数据类别,信用卡号属于财务信息数据类别。图3-7以一个例子说明了这种层次结构。策略是适用于数据类别的规则和限制。例如,您的策略可能是消费者的PII数据必须在消费者同意并清楚了解您的组织如何使用这些信息后收集。如果您的企业处理包含个人身份信息(PII)或财务信息等敏感数据,您将需要遵守由您的组织甚至有时由政府强制执行的政策和法规。您需要向监管机构(制定这些规定的人)证明您已经制定了相应的政策;这通常通过审计来完成。建议您制定这些政策,并以计划的方式记录您的处理过程,以避免在审查和审计期间出现紧急情况。为确保您的数据符合政策,您的数据湖中的数据需要进行分类,即与类别和类型相关联,以便您可以应用相关的政策。像Amazon Macie这样的云服务利用机器学习来对数据湖中的数据进行自动分类,并处理结构化、非结构化和半结构化数据。元数据管理、数据目录和数据共享元数据是指描述存储在数据湖中的数据的格式和字段的技术数据,以及对数据集具有更多业务上下文的其他数据。在本书中,我们将重点关注技术元数据。例如,对于一个员工表,元数据包括描述信息。第一列是名字,它是一个字符串数组,接下来是另一个字符串数组表示姓氏。下一个字段是年龄,它是一个介于15到100之间的整数。数据目录是存储这些元数据的系统,并可以为组织的其他部门发布。类比一下,在一家有很多书的图书馆中,如果没有目录,你很难找到你想要的书,而目录可以让你通过标题或作者搜索书籍。类似地,数据消费者可以利用数据目录查找他们有权访问的表,可以使用表名或特定关键字段进行搜索。例如,你可以搜索所有关于员工的表。这里需要记住的关键是一个数据目录可以存储来自各种数据源的数据,包括数据湖、数据仓库和任何其他存储系统。图3-8说明了这个概念。一旦重要的数据集和高价值的洞见对组织可用,可能会有其他消费者,无论是组织内部的消费者,或者在某些情况下,甚至是组织外部的消费者。换句话说,数据就像一种产品,可以与更广泛的受众共享和实现货币化。数据共享是数据治理的一种新兴能力,其中数据湖或数据仓库中的数据集可以与组织内部或外部的受众共享,消费者向数据生产者支付数据访问费用。数据目录使得实现这些场景变得更加容易。数据访问管理当我们谈到通过目录和数据共享实现数据可发现性时,我们必须确保适当的角色组具有对适当的数据的访问权限,更重要的是,这些角色没有访问其他数据的权限。数据访问管理是数据治理的一个重要部分,其中有一系列功能让角色在不同层面管理数据,从对存储数据的访问到通过其他应用程序(如数据共享或仓库)对数据的访问。我想以一种分层的方式解释数据访问管理的概念:在最内层核心,有对数据本身的访问,这是一个数据湖存储级别的安全模型,帮助您锁定对存储本身的访问。接下来的层次涉及对运行在数据湖存储之上的计算引擎的访问,可以通过ETL流程、数据仓库或仪表板进行访问。下一层涉及到云系统级别的边界,它控制着云资源或数据在网络边界上的可见性和可访问性,例如区域访问限制,决定哪些数据需要保留在某个区域内,而哪些数据可以在不同区域之间传输。最后,您可以使用全面的数据安全工具,例如Apache Ranger,在多个数据存储中应用高级规则和策略(例如被标记为PII的数据只能由人力资源部门访问)。图3-9说明了这种方法。数据质量和可观察性鉴于数据对于运营业务的重要性,数据的质量已经变得与代码的质量一样重要,甚至更重要。无论是导致关键新闻如COVID-19数据被阻断的故障,还是像2017年奥斯卡颁奖典礼上尴尬地宣布《爱乐之城》获奖的错误公告,数据错误都会在下游产生连锁效应。组织越来越多地依赖于衡量和监控数据质量作为最佳实践,这也是数据湖领域一个不断增长的投资领域。虽然这超出了本书的范围,但我建议您查阅其他关于数据质量和数据可观察性的资源,以深入了解相关概念,并了解可以用于数据质量和可观察性解决方案的可用工具集。在本节中,我将概述数据可观察性的基本概念和方法。在《数据质量基础》一书中,Barr Moses、Lior Gavish和Molly Vorwerck(O'Reilly)给出了对数据可观察性的五个支柱的简明定义,即确保数据湖架构中数据质量的度量属性。这是思考数据可观察性的一个很好的起点。特别是在云数据湖架构中,有一个不同的组件集将数据加载到数据湖存储中,并处理数据以生成高价值的洞察力,还有另一个组件集绘制仪表板。一个组件并不完全意识到其他组件的存在。因此,在数据湖中采用以数据为中心的方法建立这种共同理解,通过数据可观察性至关重要。以下是从该书中改写的数据可观察性的五个支柱:新鲜度 新鲜度是数据的时效性指标。数据最后一次刷新是什么时候?举个例子,在前几章中我们讨论了一种常见模式,即将操作性数据存储中的数据每天上传到数据湖中。新鲜度属性表示此次更新的新鲜程度,因此如果昨晚的数据上传失败,那么您就清楚地知道报告是基于两天前的数据。分布 分布是数据的可接受范围或值的指标。这让您可以为数据定义可接受的范围。通常,当仪表板中的图表看起来异常时,您会想知道数据是不是在突出显示一个真正的问题,或者数据只是简单地错误或损坏了。确保对数据有可接受的范围有助于您知道当数据超出或低于可接受范围时,您遇到的是数据问题而不是趋势上的实际问题。例如,当最近的销售数据尚未到达时,您可能会看到销售额为0美元,而实际上几乎不可能没有销售。同样,如果您看到销售额突然增加了500%,与正常范围相比,您会知道您需要调查一下这是一个真正值得庆祝的原因,还是可能存在一些重复处理导致销售数据计算了两次。容量 容量是数据可接受大小的指标。类似于分布,这指示数据大小的可接受级别,而超出此范围的任何数据可能表明数据水平有问题。例如,当您的数据表通常有10,000行时,经过一天的处理后,您看到只有10行或5百万行,那么您就会得到进一步调查的线索。架构 如前所述,架构指的是数据的结构和语义定义的描述。如果上游组件更改了架构,那么一个或多个字段可能会消失,从而破坏下游场景。跟踪架构的变化有助于我们理解其对下游组件的影响,并将变化隔离到影响范围内。血统 简单来说,血统可以描述为数据的生产者和消费者之间的依赖关系图。对于给定的数据集,数据血统描述了数据是如何生成的以及哪些组件使用它。当出现错误时,数据血统提供了可以追溯到需要调查的其他组件的线索。建议组织投资于自动化来衡量这些支柱,以确保数据具有可接受的质量,并且数据湖可以提供服务级别协议(SLAs),以确保数据符合标准的程度。数据治理在Klodars公司的应用情况Alice和她的团队了解数据治理对于他们的数据平台架构的重要性,并着手进行以下改变:他们利用基于开源Apache Atlas的数据目录,对其丰富和策划区域中的数据进行整理和发布元数据。 - 他们对销售和客户表中的数据进行分类,包括PII(个人身份信息)、财务信息等,并确保数据目录中有关于这些数据分类的信息。考虑到他们的业务场景不需要实际的PII信息,他们编写了一个PII清除程序,以确保PII数据被屏蔽(存储的是该值的唯一哈希值,而不是明文值)。结果是,数据分析师仍然可以查看关于唯一用户的信息,而无需查看其个人信息。从安全和访问控制的角度来看,他们进行了以下操作:他们实施了数据湖存储安全,以便只有平台团队可以访问原始数据,而业务分析师和数据科学家只能以只读方式访问丰富的数据和策划数据集。数据科学家和业务分析师对为他们分配的工作空间具有读写权限,但除非他们明确选择共享,否则不能查看其他用户的工作空间。他们确保产品团队和高管团队可以访问仪表板,而数据科学家可以访问所有的数据科学计算框架。数据摄取流水线和数据准备流水线严格受到数据工程团队的控制。他们实施了一个数据治理解决方案,该解决方案包括数据目录以及跨数据湖和数据仓库数据的策略和访问管理。图3-10展示了Klodars公司实施的数据治理情况。数据治理总结将所有这些概念综合起来,我将总结数据治理的方法,作为一系列步骤,可帮助您在数据湖的客户中建立对数据的信任:了解数据治理政策,包括数据官员的专业知识以及数据生产者和数据消费者的需求。这些需求指导数据工程师实施数据治理。理解并对数据湖中的数据进行分类,确保您知道哪些数据集适用于哪些政策。构建数据目录,管理元数据,帮助理解和发现可用的数据集。这使得数据生产者和消费者可以更轻松地发布和查找可用的数据集。这还帮助数据工程师实施数据访问和治理政策,并使数据官员能够审核和审查数据集以确保合规性。您还可以利用数据共享功能来控制和管理数据的共享方式。在不同层面上管理数据访问,包括数据层、计算引擎层和网络层,并设置定制的自动化数据策略,以确保控制和限制数据访问,以符合访问政策要求。投资于适当的数据可观测性水平,确保您有可靠的监控工具,帮助识别和调试数据质量问题。通过采取这些步骤,您可以建立与数据湖的客户对数据的信任,并确保数据的质量、安全和合规性。管理数据湖成本云数据湖架构的最大价值主张之一是降低成本。以下是实现较低成本的主要驱动因素:无需维护数据中心和设备维护成本——由云服务提供商负责处理。云上的按使用付费模式,您只需为实际消耗的资源付费,而不必维持一直运行的硬件。解耦的计算和存储,使您能够独立扩展计算和存储,从而确保较大的存储需求不会导致相应的存储成本增加。这样,您可以在数据湖上引入更多数据并启用更多场景,而不会造成成本激增。虽然数据湖上的单位成本较低,但也有一些因素会增加数据湖成本,您应该了解这些因素,以便在实施过程中平衡业务目标:云服务提供较低的成本,因为您只需为实际消耗付费。然而,这意味着您需要有按需运行的云资源,并在不使用时关闭它们。如果您不管理云资源的按需预配和关闭,可能会导致它们一直运行,这种消费模型可能不适用于您的情况。正如我们之前所见,云数据湖架构具有计算和存储的分离设计,它优化了成本,因为您可以根据需要独立扩展计算和存储。然而,这种设计还需要注意数据在服务之间传输时的事务成本,例如计算与存储服务之间的交互成本。在云架构中,云资源在同一地区内按服务之间的交易没有网络成本。类似地,将数据从其他来源(例如您的本地系统)传入云中也没有成本。然而,当您在不同地区之间传输数据或将数据从云传出(从云到您的本地系统等)时,将会产生网络成本。由于数据湖存储成本较低,您有机会引入各种类型的数据,即使您目前没有即时使用它的计划。然而,这可能导致数据无法受控地增长,使数据湖变成了数据沼泽,从而增加了成本。云服务提供了丰富的数据管理、数据持久性和性能功能。当选择这些云服务功能时,如果选择不合理,可能会增加成本。在本节中,我们将通过了解云交互的工作方式以及它们对成本的影响,从更广泛的角度来看这些因素。我们还将探讨如何通过仔细的设计考虑来优化成本。解密云上数据湖的成本云上数据湖的关键组件主要包括以下几个方面:数据存储数据湖存储或数据仓库,用于存储和组织数据。这里的计费模型主要包括两个关键要素:存储的数据量和事务的成本。计算引擎用于处理和转换数据的服务,即计算引擎。这可以是大数据框架如Spark引擎的IaaS,也可以是SaaS。这里的成本组成部分主要与计算资源的利用相关,比如每个计算单元的价格(由计算引擎定义),根据使用的CPU和内存量以及每个核心每小时的利用率。软件成本您需要支付订阅费用(通常是每月)来使用软件。网络成本数据在网络上的传输成本,特别是跨区域传输或从云端传出的数据(出口成本)。价格通常按传输的数据量(每千兆字节)计算。图3-11以数据湖架构为背景,说明了这些成本的关系。这些成本构成要素会以不同的方式呈现,影响到您的数据湖的总成本。例如,尽管您知道自己支付的是存储费用,但确切的费用取决于您如何设计存储系统。以下是一些影响可变存储成本的因素:存储层级不同层级的存储具有不同的成本。例如,热/标准层级比存档层级的静态数据成本更高,但对于事务来说成本较低。因此,如果数据需要频繁的事务处理,最好将其存储在标准层级中;对于冷存储(仅用于数据保留目的而没有事务处理,比如需要满足保留策略的数据),可以选择存档层级。数据保护和持久性特性数据保护特性(如版本控制和备份)以及冗余存储特性(如跨区域复制)为数据提供更高的持久性,但同时也会增加额外副本的费用。鉴于数据湖中的所有数据并非都是相同重要性,最好将这些特性用于高价值数据。事务成本需要特别注意两种具体的事务成本:与跨区域出口或从云端传输到本地系统相关的任何网络成本,以及在不同服务之间传输数据的事务成本。对于存储事务,事务成本是根据事务数量计算的,因此使用许多小文件(例如1 KB)传输相同数据量(例如1 GB)的成本将高于传输较大文件(几百兆字节)。了解云上数据湖系统成本的这些关键因素对于根据您的需求优化成本是必要的。数据湖成本策略制定良好的数据湖成本策略始于对业务需求、架构、数据和最重要的客户的理解。图3-12显示了您需要了解的关于数据湖的关键因素。数据湖环境和相关成本在你的数据湖架构中,就像在你的编码环境中一样,存在着不同的环境:开发环境供数据开发人员使用,预生产环境用于端到端测试,生产环境用于支持你的客户。你需要了解这些环境的使用情况和你承诺的服务级别协议(SLA),以便为它们配置适当的配置水平。例如,在开发环境中,由于运行的工作负载是为了验证功能,可能不需要非常强大的计算核心。你的压力测试环境可能需要很多计算核心,因为你正在推动系统的极限,但你的压力测试可能是每周运行一次,并不需要一直保持运行。你的生产环境需要满足客户的SLA,不能在可用性或性能方面妥协。同样,了解你的作业的性质决定了哪些环境需要一直运行,哪些可以按需生成。例如,数据科学家使用的笔记本集群可以按需启动,而用于支持关键业务仪表板的集群需要一直运行。对于按需集群,使用自动化可以帮助按需启动和关闭资源。自动化对于按需生成合成数据以满足你的用例也非常有用,而不是存储数据。基于数据的成本策略你的数据湖中并非所有的数据都是等价的,了解数据的价值、可重现性和消费模式对于优化数据成本非常重要:云数据湖存储解决方案提供预留选项,如果你能承诺一定大小的数据(例如,至少100 TB),你将获得更低的数据价格。如果有意义,可以考虑这个选项。云数据湖存储系统提供不同层次的数据存储。需要频繁交易的数据称为热数据,而需要存储但不需要频繁交易的数据称为冷数据。对于冷数据,使用成本更低的归档层进行存储,但交易成本较高。归档层还有一个最小保留期,需要注意。对于需要频繁交易的数据,使用标准层进行存储。云数据湖存储系统还提供高数据耐用性功能,例如版本控制:存储多个数据副本,以防止数据损坏,可以将数据复制到多个区域以应对灾难,可以将数据存储在硬化备份系统中以提供数据保护。这些功能非常适用于关键数据集,但对于非关键数据集来说并非必需,因为你可以容忍数据损坏或删除。云数据存储系统具有数据保留策略,你可以实施这些策略,以便数据在一定期限后自动删除。通常的做法是从各个来源每天、每周或者其他你喜欢的时间间隔中拍摄数据快照,然后将其加载到数据湖中。这可能会导致成本飙升,并使你的数据湖变成一个数据沼泽。根据数据的生命周期设置保留策略可以确保你的数据湖保持整洁。交易和对成本的影响交易成本,无论是网络出口成本还是数据存储交易成本,在大多数情况下都会让数据湖的使用者感到意外,并且经常被忽视。问题在于,这些交易成本也是最难建模的。您需要关注两个因素来了解和优化交易成本:交易数量数据传输量了解和优化交易成本的最佳方式是运行一个规模化的概念验证(PoC),以代表您的生产工作负载。同时,确保避免一些反模式,比如小文件,最好将它们设置为每个文件至少几百兆字节。除了节省成本外,这还可以提高数据湖解决方案的可伸缩性和性能。我们将在第四章中对此进行更详细的讨论。虽然这对于数据湖中的所有数据可能并非都可行,但您可以针对丰富和策划区域的数据来解决这个问题。例如,物联网数据往往只有几个字节或几千字节,并存储在原始数据湖区域中。然而,在数据准备过程中,有意识地将这些数据聚合到一个更大的文件中以优化交易。总结在本章中,我们深入探讨了云数据湖的实施细节。首先,我们介绍了如何开始规划云数据湖的实施。然后,我们讨论了数据湖的核心——数据:根据数据的自然生命周期,组织和管理数据的策略。接下来,我们概述了数据治理,以帮助管理数据湖中的数据的可发现性、访问和共享限制。最后,我们讨论了影响数据湖成本的因素,并制定了优化成本的策略。我在本章的目标是帮助您建立对设计数据湖架构的基本概念的理解。有了这种理解,您将能够通过选择适合您需求的正确云服务并使用正确的配置来设计数据湖架构。如果您不知道正确的配置是什么,请参考本章,并将您的要求传达给云服务提供商或ISV。在接下来的两章中,我将分别讨论构建可伸缩性和性能的数据湖的概念和原则。
0
0
0
浏览量623
攻城狮无远

《云数据湖》第六章:数据格式的深度探究

第六章:数据格式的深度探究设计不仅仅关乎外观和感觉,设计是关于它的运作方式。 ———史蒂夫·乔布斯传统上,数据仓库是建立在专有的数据格式上的,通过优化查询模式来提升性能。随着云数据湖的应用场景越来越多,尤其是湖仓一体化架构的兴起,越来越多的客户和解决方案提供商正在投资于能够直接在云数据湖上运行类似仓库的查询的功能。这使我们更接近实现一种架构的承诺,即在特定用途下最小化在数据存储之间来回复制数据的需求。这种无障碍数据存储的承诺导致了越来越多的开放数据格式的出现,这些格式使得可以直接在云数据湖存储上运行类似仓库的查询。在本章中,我们将详细介绍三种这样的格式:Apache Iceberg、Delta Lake和Apache Hudi。本章可能是本书中最技术性的一章,我们将对这些格式进行详细的探讨,包括它们是如何满足设计目标的。我希望这一章能为您提供足够的知识,以了解这些格式的设计原因,这样当您评估其中一种格式时,可以提出正确的问题,找到适合您的云数据湖架构的正确数据格式。为什么我们需要这些开放数据格式?如果我必须用一句话总结对开放数据格式的需求,我会说开放数据格式本质上使得云数据湖存储能够存储表格数据。这引出了两个问题:为什么我们需要存储表格数据,以及为什么在云数据湖存储中存储表格数据会成为一个问题?让我们详细探讨这些问题。我们为什么需要存储表格数据呢?在《数据格式》一章中,我提到了存储在云数据湖中的数据的关键假设,如下所示:存储在数据湖中的活跃交易数据在很大程度上以表格格式存在(按行和列组织)。写入一次的数据会被多次读取。读取模式主要依赖于对数据的条件选择,其中具有某些列相似值的数据会被过滤返回或进行聚合分组。让我们看看为什么存储在数据湖中的数据通常是表格形式。虽然大数据分析系统中的数据可以来自任何来源,具有任何大小和格式,正如《什么是大数据?》中所示的六个V(体量、速度、多样性、价值、准确性、可视化),但这些数据本身被认为价值较低,噪音较多。大数据架构的主要价值主张是从这些价值较低的数据中生成高价值的洞察。生成这些高价值洞察的过程涉及各种操作,主要归类为几个高级类别,详见表6-1。表格数据结构适用于这些常见的数据湖操作,因为它提供了将相似数据组合在一起的灵活性。当相似数据被分组时,获取所需数据子集并进行计算所需的交易次数更少。这就是为什么大多数数据格式都优化了存储表格数据。在大数据分析系统中,进入系统的数据不一定是表格形式;然而,正如之前所说,这些数据被认为是低价值的原始数据。这些数据经过一系列转换,作为第一步转换为表格数据,然后再进行进一步处理。您可以回顾《数据的一天》以了解更多关于数据湖区域的信息。除原始数据区域以外的其他区域中的数据通常具有表格性质。在云数据湖存储中存储表格数据为什么会成为问题呢?当你想到表格数据时,你可能直观地想到一个可以将数据按行和列进行组织的电子表格。大多数数据库和数据仓库的存储方式都是按照这种方式进行组织的。然而,正如我们在《云数据湖存储》中所看到的,数据湖架构使用的存储是通用的对象存储,旨在存储各种类型的数据而不施加任何限制。因此,用于存储支持网站、博客以及在线相册中的照片等内容的系统,也用于存储用于大数据分析应用的数据。这样做非常具有吸引力的主要原因是低成本和能够存储任何大小和格式的数据,而不会施加任何限制;这种组合使得组织可以将所有他们拥有的数据带入数据湖存储,而不会耗尽资金。与此同时,数据湖存储本身在存储和处理表格数据方面具有一些明显的限制,如下所示:现有数据的更新数据湖存储系统主要是追加写入的存储系统。这意味着如果需要替换现有的任何数据,这个过程并不是非常直接。模式强制和验证模式指的是数据本身的描述。例如,我们知道地址具有以下字段:街道地址、城市名称、缩写州名和邮政编码。对象存储系统无法保证存储的地址具有所有这些字段。正如我们在第5章中详细讨论的那样,确保数据湖的性能需要考虑许多因素。然而,所有这些因素都依赖于从业人员为其设计,而不是开箱即用的保证,因为对象存储系统本身不能保证性能。由于其灵活性和低成本,云数据湖架构得到了广泛采用。因此,越来越多的业务关键查询和仪表板依赖于存储在通用对象存储中的数据。为了确保存储在通用对象存储系统中的数据可以针对支持业务关键场景的核心数据湖计算进行优化,客户和云数据从业者孵化了各种开放数据格式,这些格式主要对数据的表格性质提供了保证。处理数据的计算引擎也能理解这些开放数据格式,从而确保了优化的性能。尽管孵化出了越来越多的数据格式,但我们将深入研究其中的三种格式:Delta Lake、Apache Iceberg和Apache Hudi。Delta LakeDelta Lake是由Databricks(由Apache Spark的创始人创建的公司)孵化和维护的开放数据格式。正如我们在《Apache Spark》中所看到的,Apache Spark在云数据湖架构中实现了统一的编程模型,涵盖了各种场景,如批处理、实时流处理和机器学习场景。拼图的最后一块是消除需要数据仓库来支持BI场景的孤立。Delta Lake是Databricks推广的数据湖仓库模式的基础,除了批处理、实时处理和机器学习场景外,组织还可以直接在云数据湖存储上运行BI场景,而无需单独的云数据仓库。为什么创建了Delta Lake呢?Delta Lake成立作为数据湖仓库模式的基础构建模块,提供以下价值主张。消除业务分析师、数据科学家和数据工程师之间的数据孤立。正如在《Apache Spark》中所介绍的,Apache Spark的创建基于提供灵活的编程模型,支持各种应用程序,如批处理、实时流处理和机器学习。Apache Spark在客户采用率和关注度方面取得了广泛的成功,并且持续增长。通过Apache Spark,客户可以使用一种适用于数据工程师进行核心数据处理以及数据科学家进行机器学习场景的单一编程模型。然而,仍然需要将数据复制到数据仓库中,以便业务分析师能够利用类似SQL的语言进行查询,因为数据仓库提供了优化的查询性能。我们在《为什么需要这些开放数据格式?》中讨论的云对象存储的限制成为了这一过程的阻碍。Delta Lake是由Databricks创建的开放数据格式,使业务分析师可以直接利用云数据湖进行查询,为组织实现了数据湖仓库架构。为批处理和实时流数据提供统一的数据和计算系统。在洞察力方面,组织往往关注两个不同的方面:了解当前发生的情况以及从历史数据中了解模式。例如,当市场团队在社交媒体上发布一篇文章时,他们希望了解这篇文章当前的趋势。同样地,当他们在进行下一次营销活动时,他们会利用历史数据来了解过去的活动趋势,以帮助指导他们的策略。实时流处理是指对进入大数据湖的数据进行即时洞察的分析,即当前的情况。计算重点放在最近的数据上,以提高处理速度。然而,如果你想基于历史数据进行洞察,那么你会使用批处理管道对存储在数据湖中的数据进行处理。支持这两种路径的架构模式被称为lambda架构,它包括用于实时分析的热路径和用于分析历史数据的冷路径。虽然Spark提供了统一的编程语言来处理实时和批处理,但传统上实时和历史数据的分析是通过不同的数据管道进行的。Delta Lake减少了对这两条不同路径的需求。图6-1展示了lambda架构的图示表示。支持对现有数据进行批量更新或更改正如我们之前学到的,数据以低价值的原始数据的形式进入数据湖,并经过多次转换生成高价值的结构化、策划数据。随着原始数据的更改,这也会影响策划数据。新进数据通常会在高价值的策划数据中更改多行和多列,这并不罕见。举个例子,假设有一个包含四行四列的数据集,如图6-2所示。随着新数据的到来,行A被修改,行C被删除,行CC被新增到表中。对象数据存储层无法以简单的方式处理对现有数据的增量更新。因此,通常情况下,数据从头开始生成整个表。正如我们已经知道的,无论是从成本还是工程能量的角度来看,这都不是理想的解决方案。此外,如果在重新计算数据集时,数据使用者正在读取该数据集,他们将看到不准确或部分结果,因此这种重新计算需要在没有读取者的情况下进行,并且必须进行适当的协调。Delta Lake提供了一种管理这些增量更新的方式,无需对整个数据集进行更新,并且使数据的使用者能够在执行更新时继续读取数据集。处理由于模式更改和不正确数据引起的错误在表格数据格式中,模式指的是行和列值需要遵循的规范或描述。因为云数据湖存储系统对数据的大小或形状没有任何限制,所以输入的数据可能会有一些缺失部分,从而不符合计算引擎所期望的模式。例如,如果有一个包含地址的数据集,并且该数据集中有一些记录没有街道地址或邮政编码,从该数据集提取地址的计算引擎将会失败。同样地,随着时间的推移,数据源可能会添加新的字段或更改现有字段,计算引擎可能会因为同一数据集中存在旧数据和新数据,而导致混淆。这种情况的示例如图6-3所示。Delta Lake通过提供模式强制和模式验证的功能,可以优雅地处理这些场景,因此您可以确保对这些数据进行检查,并主动通过修复缺失值或拒绝不符合模式的记录来纠正这些问题。这听起来非常棒。Delta Lake如何实现这些场景呢?Delta Lake致力于在数据湖存储中提供数据的ACID保证:原子性、一致性、完整性和持久性,为实现先前描述的场景奠定了基础。如果您想了解更多关于ACID事务的信息,请参阅《数据湖仓库的参考架构》。让我们来看看Delta Lake的内部结构以及它如何实现这些场景。Delta Lake是如何工作的?Delta Lake是一种用于在数据湖存储系统中存储表格数据的开放式存储格式,提供ACID保证。Delta Lake表由以下组件组成:Data objects:实际数据以Parquet文件的形式存储在表格中。您可以在《探索Apache Parquet》中了解Apache Parquet背后的概念。Log:事务日志或账本,用于跟踪表格中数据的更改。这些更改被称为操作,以JSON格式存储。Delta日志跟踪数据本身的更改,即插入、删除或更新操作,以及元数据或模式的更改,例如表格中的列的添加或删除。Log checkpoints:日志的压缩版本,包含到某个特定时间点的非冗余操作。考虑到随着时间的推移,对数据进行的操作数量可能很大,日志可能会变得非常庞大,因此日志检查点可作为性能优化的一种方式。Delta Lake文档页面提供了有关如何使用Delta表格的详细说明。创建Delta表时,还会为该表创建一个日志。表格中的所有更改都记录在日志中,而这个日志对于保持表格中数据的完整性至关重要,从而提供了我们讨论的保证。如图6-4所示,向Delta表进行写操作涉及两个组件:通过修改Parquet文件对数据对象进行更新。更新Delta日志,并将该修改与Delta日志中的唯一标识符相关联。除非两个操作都完成,否则写操作将无法成功。因此,如果对表格进行了两次同时进行的写操作,它们会自动按顺序通过此日志进行串行化。第二次写操作需要等待第一次写操作成功并更新了日志后,才能完成自己的写操作并更新日志。有了这个带有事务账本的日志,调用者还可以进行时间穿梭,访问过去版本的数据。除了提供ACID保证和支持时间穿梭等场景外,Delta表还提供了模式强制执行功能,您可以确保数据符合您指定的模式(例如,邮政编码必须是五位整数),以及模式演化功能,您可以通过设置默认值来确保较旧的数据与添加新列和演化的模式兼容。这使得在数据湖上实现类似SQL的场景成为可能。何时你会使用Delta LakeDelta Lake为存储在通用对象存储中的数据提供了更强的保证。需要记住的一点是,对于以Delta Lake格式存储的数据,您需要利用理解该格式的计算引擎来充分利用其功能。我建议您在预期要运行类似SQL查询或为机器学习模型提供数据集的数据上使用Delta Lake,以便能够保留版本信息。如果您使用Apache Spark,您可以在最小的修改下充分利用现有的管道,将现有数据转换为Delta Lake格式。Apache IcebergApache Iceberg是由Netflix作为孵化器开发的,它在数据湖存储上支撑着关键业务应用,而这些应用在《为什么在云数据湖存储中存储表格数据是个问题?》中描述的不足之处上运行。为什么会创建Apache Iceberg?Netflix是一家流行的视频流媒体公司,从一开始就建立了一个高度数据驱动的公司。数据支持其关键业务场景,例如根据用户的观看模式提供推荐内容,了解Netflix需要创建或分发的内容类型以吸引用户群体,并监控其服务的健康状况,仅举几例。尤其在像视频流媒体这样竞争激烈、市场上存在多个竞争对手的领域,数据驱动的洞察和业务是Netflix的关键区别之一。根据Netflix技术博客的说法,Netflix使用的数据集存储在不同的数据存储中:Amazon S3作为支持其云数据湖的通用对象存储;MySQL作为操作性数据库;还有Redshift和Snowflake等数据仓库,仅举几例。Netflix的数据平台确保这些多样的数据存储作为一个单一的数据仓库对其消费者具有互操作性。这在图6-5中有所描绘。具体来说,Netflix在其托管在Amazon S3的云数据湖中的数据上利用了Apache Hive表。Apache Hive表利用Apache Hadoop生态系统提供了一种表格格式,可用于运行类似SQL的查询。Netflix在通用对象存储解决方案中遇到了以下限制:对现有数据集的更新正如我们之前在《Delta Lake是为什么而创建的?》中所看到的,对象存储系统无法很好地处理对现有数据的更改。强一致性是指读取始终返回最后写入的数据的行为。AWS在2020年宣布了强一致性写入。然而,在Apache Iceberg成立时,Amazon S3提供的是最终一致性写入,这无法为Netflix用户提供可预测的读取数据。为了克服这个问题,写入操作需要以一种不与读取操作冲突的方式进行协调和编排。Apache Hive的性能Apache Hive将数据存储在对象存储文件系统中的文件和文件夹中。这意味着每当需要查询数据时,都需要列举文件和文件夹以找到感兴趣的数据。随着数据规模增长到PB级别,需要在该规模上列举文件变得非常昂贵,并为查询创建了性能瓶颈。Apache Iceberg于2018年作为开源项目孵化,旨在克服在云数据湖上使用表格时遇到的这些限制。Apache Iceberg是如何工作的?有趣的是,Apache Iceberg建立在现有数据格式之上,因此您可以在现有数据上使用它。物理数据以Apache Parquet和Apache ORC等开放数据格式存储。描述Apache Iceberg的最简单方式是将其视为物理数据存储(Apache Parquet或Apache ORC)与如何组合和结构化形成逻辑表之间的翻译层。Apache Iceberg将贡献到表格的文件存储在一个持久的树状结构中。表格的状态存储在多个描述元数据的文件中,包括:一个目录文件,其中包含指向元数据最新版本的指针,它是最新元数据位置的主要来源。一个快照元数据文件,用于存储有关表格的元数据,例如模式、分区结构等。与该快照相关联的清单列表,其中每个清单文件都有一个条目。一组清单文件,其中包含实际存储数据的文件路径列表,以及有关数据本身的指标,如数据集中列的最小值和最大值。该清单还包含有关数据集的元数据,例如列的上下界,类似于行组头信息。这在图6-6中有所描绘。让我们来看一个示例,介绍Apache Iceberg如何进行写入操作,如图6-7所示。初始时,Apache Iceberg表格具有行A、B、C和D,并存储在两个数据文件中。存在指向这些数据文件的清单文件,以及包含指向这两个清单文件的指针的清单列表。让我向您介绍Apache Iceberg在管理对数据集进行更改时的步骤:首先,当对行A进行修改时,该数据将被写入一个新的数据文件,并创建一个新的清单文件来指向该新行。然后创建一个新的快照,并将其保存在清单列表文件中。接下来,Apache Iceberg更新目录,使其指向这个新的清单列表文件。只有当所有这些操作都成功完成时,写入才会成功,并且仅当目录文件更新元数据指针到最新版本时,写入操作才会完成;这样可以控制多个写入操作。当删除行C并插入行CC时,会重复类似的过程,目录现在指向数据的最新版本。当有一个读取查询只针对行A和B进行过滤时,清单文件可以帮助指示可以跳过包含行CC和D的数据文件,从而避免不必要的文件读取,提高查询性能。何时我们使用Apache Iceberg?与Delta Lake类似,Apache Iceberg用于策划数据和增强数据区域,其中数据采用表格格式,并用于查询。它在需要强大保证的数据中具有优先级。由于更新频繁,通常用于表格中的策划数据区域。Apache Iceberg非常适合在不需要特定格式的基础数据的情况下,需要对数据进行结构保证的场景。Apache Iceberg在架构中提供以下功能,您可以利用这些功能:支持模式演化:当模式更新时,即添加新列或删除现有列时,这些更新会被保留在快照中,您可以利用它们来了解模式随时间的演变。数据分区优化:如同我们在《数据格式》中所看到的,将相似的数据放置在一起可以提高查询性能,因为您优化了最小事务数以获取所需的数据。当基础数据发生变化时,您可以继续更改数据文件的组织方式并更好地对数据进行分区,而应用程序仍然可以调用清单,而无需了解这些优化,从而灵活地优化数据布局而无需担心影响应用程序。时间旅行或回滚:在机器学习场景中,模型是使用特定版本的数据构建的,随着数据的变化,模型的行为也会改变。在Apache Iceberg中,您可以通过关联特定的快照或版本,将应用程序与数据的某个版本关联起来,从而保存特定版本的数据快照。这个概念被称为快照隔离。此外,如果您的更新有问题或错误,您可以更轻松地管理快照并回滚到以前的版本。尽管Netflix孵化了Apache Iceberg,但它已被多个客户和数据提供商广泛采用作为数据湖的开放数据格式。Apache Iceberg已被Apple、Airbnb、LinkedIn和Expedia Travel等组织采用。此外,数据平台提供商如Dremio、Snowflake和AWS在其云服务中提供了对Apache Iceberg的原生支持。Apache Iceberg与Delta Lake非常相似,都提供了数据湖中数据的表格结构;然而,它们实现这一目标的方式是通过元数据层。Apache Iceberg还支持非Parquet文件格式,因此如果您需要以不同格式的数据具有表格表示,Apache Iceberg非常适合。通过扫描计划等功能,您可以快速缩小查询感兴趣的大型数据集,从而大大优化在大型数据集上操作的性能。Apache HudiApache Hudi是由Uber孵化的,Uber最初是最早的打车应用之一。多年来,Uber发展成为一家移动服务提供商,提供额外的服务,如食品配送(Uber Eats和Postmates)、包裹投递、快递员、货运运输、与Lime合作提供电动自行车和电动滑板车租赁服务,以及与当地运营商合作提供渡轮运输服务。Uber快速扩张的基础在于该组织的数据驱动文化。数据和先进的机器学习能力支持Uber的关键业务运营,如预测司机的预计到达时间、为Uber顾客提供有意义的晚餐订购建议,以及确保乘客和乘客的安全,等等。这些功能的及时性,即实时推荐或操作,对于确保出色的顾客体验至关重要。当然,这对于Uber的客户满意度和品牌非常重要。面对与Netflix类似的挑战,以及其他涉及实时洞察力重要性的挑战,Uber孵化了Apache Hudi,以提供对数据湖中数据的强大保证和及时洞察力。Apache Hudi旨在支持这些操作和大规模的数据保证,在2020年的150 PB数据湖上支持每日约5000亿条记录的更新,随着Uber作为业务的不断扩大,这个规模还在增长。为什么Apache Hudi会被创建?Apache Hudi的动机与Apache Iceberg和Delta Lake基本相似,即旨在克服用于数据湖存储的通用对象存储的固有限制。具体而言,Uber希望解决以下情况:高效写入的UpsertsUpsert是一种概念,当数据不存在时,将其作为插入操作进行写入,如果行已存在,则将其作为更新操作进行写入。在不支持upsert的情况下,会出现多行而不是一行,需要编写额外的计算来获取这些行并筛选出最新的数据。这种解决方案成本更高,处理时间更长。对象存储系统主要是追加写入,不具备内在支持upsert的概念。理解增量修改正如我们在之前的章节中所看到的,数据处理管道运行作业以处理大量数据集,生成高度精选的数据,这些数据包括输入数据的聚合、过滤和连接版本。通常情况下,当输入数据发生变化时,这些处理引擎会重新计算整个数据集上的精选数据。为了加快洞察力的获取时间,不需要重新处理整个批处理,可以只针对发生变化的数据集运行重新计算,从而仅处理增量变化。为了做到这一点,需要了解自上次作业以来发生了什么变化,而数据湖存储本身并不自然地支持这种操作。支持实时洞察力正如我们在“为什么创立Delta Lake?”中所看到的,虽然有编程模型可以在实时和批处理流之间实现统一的计算,但实际的架构和实现方式是不同的。例如,在需要做出决策的情况下,如为特定行程寻找最佳司机,将实时的司机位置数据与地图和交通优化的批处理数据结合起来,以预定最佳司机。类似地,在Uber Eats中提供推荐时,将实时的客户点击流数据与可能存储在图数据库中的推荐数据相结合,以提供正确的推荐。再次强调,数据湖存储本身并不是为这些情景而设计的。Apache Hudi的主要设计目标是通过支持upsert和增量处理来提高效率,从而实现数据的及时更新,而无需从头重新计算整个数据集。Apache Hudi是如何工作的?Apache Hudi,像其他格式一样,是一种用于存储表格数据的开放数据格式。在支持实时流式处理场景和批处理场景的lambda架构中,数据有两种写入模式:连续摄入大量数据-想象一下大量的Uber车辆实时传输信息。批量大规模摄入数据-想象一下每日定期执行的销售或营销数据的导入。为了支持这些模式,Apache Hudi提供了两种类型的表格:写入时复制(Copy on Write)有一个真实数据源,读写表格的操作都与此数据源进行交互。每次写入操作都会立即以更新的形式写入Apache Hudi表格,并且这些更新会近实时地反映给读取者。数据以针对读取进行优化的列式格式(如Apache Parquet)进行存储。读时合并(Merge on Read)每次写入操作都会以优化写入的数据格式(如行式数据格式,如Apache Avro)写入缓冲区。然后,这些数据会更新到供读取者使用的表格中,其中数据以列式格式(如Apache Parquet)进行存储。Apache Hudi由三个主要组件组成,这些组件用于存储表格的数据:数据文件(Data files) 包含实际数据的文件。对于写入时复制的表格,数据以列式格式存储。对于读时合并的表格,数据以增量写入的形式存储在行式格式中,并与完整数据集以列式格式存储在一起。元数据文件(Metadata files) 元数据文件是表格上存储的所有事务的完整集合,按照时间顺序进行排序。Apache Hudi表格上有四种类型的事务:提交(Commits) 将一批记录作为原子写操作写入Apache Hudi表格中的数据集。Delta提交(Delta commits) 将一批记录作为原子写操作写入增量日志中,并稍后将其提交到数据集。此操作仅适用于读时合并类型的表格。压缩(Compaction) 通过重新组织文件结构来优化存储的后台进程,将增量文件合并到数据集的列式格式中。清理(Cleans) 删除不再需要的旧版本数据的后台进程。索引(Index) 一种数据结构,用于高效查找属于事务的数据文件。Apache Hudi表格支持三种类型的读取操作:快照查询(Snapshot queries) 查询Apache Hudi表格中存储的特定快照数据。快照指的是给定时间点的数据版本。该查询返回与查询条件匹配的所有数据。增量查询(Delta queries) 查询在给定时间段内发生变化的数据。如果只关注数据的变化情况,可以使用增量查询。优化读取查询(Read optimized queries) 这种查询类型适用于读时合并的表格,返回以优化读取为目标存储的数据。该查询不包括尚未合并的增量文件中的数据。这种查询经过优化以提供更快的性能,但代价是数据可能不是最新的。让我们将这些概念结合起来,通过一个具体的例子来了解Apache Hudi的工作原理。Copy-on-write表图 6-8 给出了对复制写表格上的事务的图形表示。在复制写表格中,每次写操作,无论是插入新行、更新现有行还是删除现有行,都被视为一个提交操作。数据集的唯一真实源头以列格式保留,并且每次写操作都是一个原子操作,更新数据集。这些真实的快照被保留下来,可以看到数据在特定时间点的状态。在此支持的查询类型有快照查询和增量查询。Merge-on-read表图6-9展示了合并读取表格上的事务的图示表示。尽管这在很大程度上与复制写表格相似(如图6-8所示),但这里的关键区别是真实源头是分布式的。在合并读取表格中,存在一个以列式数据格式优化读取的数据集,而写入操作则以写入优化格式存储在增量日志中,然后将其压缩成列式数据格式的主数据集。这些表格支持三种类型的查询:快照查询返回给定时间点的数据集版本,增量查询仅返回发生更改的数据,读取优化查询返回列式数据格式的数据集,提供更快的性能,但不包括尚未压缩的数据。何时使用Apache Hudi?Apache Hudi通过其支持不同类型的查询的不同类型的表格,为数据提供了强大的保证,包括原子写入和数据版本控制,从而提供更大的灵活性。Apache Hudi最初由Uber孵化,但随后在其他公司中得到了广泛的采用,如亚马逊、沃尔玛、Disney+ Hotstar、GE航空、罗宾汉和TikTok。近期成立的Onehouse公司提供了一个基于Apache Hudi构建的托管平台。Apache Hudi旨在支持需要以实时和批量方式处理高频写入的表格。它还提供了根据性能和数据实时性要求使用不同类型查询的灵活性。总结在本章中,我们深入探讨了数据格式,并了解了这些数据格式如何提供强大的数据保证,改善查询性能,并通过利用数据湖存储降低成本。我介绍了Delta Lake及其用途。我还描述了由运行大规模数据湖的客户孵化的数据格式:Apache Iceberg和Apache Hudi。这些开放的数据格式可以实现真正无障碍的数据湖,可以支持各种计算,满足数据工程师、机器学习工程师、数据科学家和BI分析师的需求。尽管这些数据格式取得了重大进展,但构建基于这些数据格式的数据湖仍需要陡峭的学习曲线和强大的数据平台团队。像Dremio、AWS和Onehouse等数据解决方案提供商正在构建提供开箱即用的湖仓解决方案的托管数据平台解决方案。Tabular是一个相对较新的组织,他们基于Apache Iceberg提供了一个数据平台。根据组织的动机和工程资源,您可以在云数据湖上实现湖仓。在下一章中,我们将集中讨论端到端的决策策略,将我们在前六章中涵盖的所有概念整合起来。
0
0
0
浏览量1119
攻城狮无远

《云数据湖》第四章:可扩展的数据湖

第四章:可扩展的数据湖如果你改变看待事物的方式,你所看待的事物也会改变。 - 韦恩·戴尔阅读前三章后,你应该已经具备了在云上建立并运行数据湖架构所需的一切,以合理的成本配置适合你的组织。从理论上讲,你已经成功地在生产环境中运行了第一批使用案例和场景。你的数据湖非常成功,现在对更多场景的需求更高,你正在忙于满足新客户的需求。你的业务蓬勃发展,数据资源正在迅速增长。正如商界所说,从零到一与从一到一百或从一百到一千是完全不同的挑战。为了确保你的设计也具备可扩展性,并随着数据和使用案例的增长而保持良好性能,认识到影响数据湖规模和性能的各种因素非常重要。与普遍观点相反,规模和性能并不总是与成本权衡的关系,而是紧密相连的。在本章中,我们将更详细地探讨这些考虑因素,以及优化数据湖以实现规模的策略,同时继续优化成本。我们将再次使用虚构组织Klodars Corporation来说明我们的策略。在第五章中,我们将进一步关注性能。提前一睹可扩展性的瞥见可扩展性和性能是你可能在产品推介和市场材料中经常见到的术语。它们究竟意味着什么,为什么它们很重要?为了更好地理解这个问题,让我们首先来看一下可扩展性的定义。在第5章中,我们将深入探讨性能方面的内容。什么是可扩展性?我所遇到的最好的可扩展性定义来自Werner Vogels的博客。Vogels是亚马逊的首席技术官,该公司拥有全球规模最大的超大规模系统之一。根据他的博客,可扩展性的定义如下:如果我们增加系统中的资源,结果是性能按比例增加,那么该服务被认为是可扩展的。如果增加资源以实现冗余不会导致性能损失,那么一个始终开启的服务被认为是可扩展的。这个规模的概念非常重要,因为随着您的需求和使用量的增长,拥有一个可以保证客户在性能上不受损的体验的架构是非常重要的。为了更好地说明这一点,我们将把规模的原理应用于我们所有人都可以理解的事物:制作三明治。我们日常生活中的规模概念让我们以可扩展性的实际例子来说明。假设你花费总共五分钟来准备一个花生酱和果酱三明治作为午餐,包括以下步骤,如图4-1所示:烤两片面包。在一片面包上涂抹花生酱。在另一片面包上涂抹果酱。把两片面包组装在一起。把三明治装袋。当你只需要制作一两个三明治时,这个过程可能很简单和快速。但是,当顾客数量增加时,你可能需要调整你的方法来提高效率。假设现在你的三明治店变得非常繁忙,每天需要制作数百个三明治。在这种情况下,你需要考虑如何扩展你的操作以满足顾客的需求。以下是一些可能的解决方案:使用多个烤面包机来同时烤多片面包,而不是逐一烤。使用涂抹机或其他设备来自动涂抹花生酱和果酱,以提高效率。设置一个生产线式的工作流程,让员工分工合作,每个人负责其中一个步骤,以加快整个制作过程。优化包装流程,使用快速而可靠的包装机器,以确保高效的包装和交付。通过采取这些扩展方法,你可以提高三明治制作的效率,并在高需求时保持良好的性能和质量。这就是如何应用可扩展性的原则来解决日常问题的例子。在数据湖架构中,类似的思维方式也适用。你需要考虑如何扩展数据处理和存储的能力,以满足不断增长的数据需求,并保持良好的性能和质量。我们将在接下来的章节中进一步探讨这些概念。
0
0
0
浏览量1866
攻城狮无远

云数据湖应用与精讲

没有大数据,你就像盲人和聋子一样,置身于高速公路之中。 ——杰弗里·摩尔
0
0
0
浏览量2156
攻城狮无远

第九章:通过混合和边缘扩展数据平台

到目前为止,在本书中,我们已经讨论了如何利用公共云的能力规划、设计和实施数据平台。然而,有许多情况下,单一的公共云是不够的,因为根据数据用例的特性,数据可能在其他位置产生、处理或存储,这可能是在本地、在多个超大规模云提供商处,或者在连接的智能设备(如智能手机或传感器)中。在这些情况下,需要解决一个新的挑战:如何提供对平台的全面视图,以便用户能够有效地混合和连接分布在不同地方的数据?在本章中,您将了解组织在处理这种分布式架构时可以采取的方法、技术和架构模式。此外,还有其他情况需要在部分连接或断开连接的环境中使数据发挥作用。在本章中,您将学习如何利用一种称为边缘计算的新方法来处理这种情况,该方法可以将存储和计算资源的一部分移到云外,更靠近生成或使用数据的主体。为什么选择多云?作为数据领导者,您的组织希望您不断寻找提高业务结果并最小化技术成本的方法。在数据平台方面,您被期望通过采用市场上最佳的解决方案,或至少是最适合业务需求的解决方案,来管理整个数据生命周期。单一云更简单且成本效益高将整个软件架构集中在单一云提供商上有很多吸引人的原因:简单 在使用单一云提供商时,由此产生的技术堆栈更简单、更流畅。通常情况下,云内的服务是本地集成的。例如,在Google Cloud上,DWH工具(BigQuery)可以本地读取托管关系数据库(Cloud SQL)中的数据,无需数据移动。在AWS上的Redshift和Aurora之间以及Azure SQL Data Warehouse和Azure SQL Database之间(通过Azure Synapse Link)也有类似的零ETL集成。学习曲线 当您只有一个云时,员工在组织内部移动变得更容易。新员工入职变得更容易,员工使用组织其他部分构建的工具也更容易。成本 使用单一云提供商使从安全性到合同签订等方面都变得更简单。仅IT和法律服务上的成本节约就足以使单一云成为最佳选择。由于云提供商基于使用量提供折扣,通过将所有技术支出集中在一个提供商上,可以获得更大的折扣。因此,我们建议小型和中型企业选择单一云提供商,并使用该超大规模云上提供的完全托管服务设计其架构。多云是不可避免的我们发现许多组织可能希望使用单一云,但最终采用了混合或多云环境。为什么呢?收购 即使您的组织最初使用单一云,您可能收购了一家在另一云上运行其整个技术堆栈的公司。现在,您是一个多云商店。这是最常见的情况,因为重新平台化的成本可能非常高,而且不值得对业务产生拖累。最佳云 在不同超大规模云提供商上可用的能力存在差异。如果您真的喜欢BigQuery、DynamoDB或Azure OpenAI,并认为它们是最优秀的,并且已经根据这些能力设计了应用程序,那么您将希望该应用程序在Google Cloud、AWS或Azure上运行,即使您的其他技术基础设施位于其他地方。最好/最熟悉的工具可用性的差异、只需信用卡即可开始阴影IT项目的能力以及不愿重写已经运作的部分,意味着许多组织逐渐演变成一种事实上的多云系统。支持客户 如果您正在构建在客户环境中运行的软件,您将需要支持所有主要云,因为您将有来自所有三个云的客户。这就是为什么,例如,SAP或Teradata在AWS、Azure和Google Cloud上都可用的原因。面对这些不可避免的情况,重要的是要认识到没有理由成为障碍。不再需要与单一供应商绑定并围绕单一技术构建整个数据堆栈。云技术比传统的本地技术更加开放,企业现在可以创建依赖于来自多个供应商的多个互连环境的复杂架构,或完全使用开源软件(例如,有些公司正在一个云提供商上实现其主站点,而在另一个上实现灾难恢复站点,以减少对单一超大规模云提供商的依赖风险)。当然,这种自由度的层次会带来不同的成本(在技术和人员方面),并需要额外的治理和管理。但正如您将在后面看到的那样,这种方法由于它可以提供的优势而变得越来越受欢迎。多云可能是战略性的与大型企业的IT高管交流时,我们经常听到他们正在进行包括多云战略在内的数字化转型之旅。已经有一些大型组织采用了多云方法的例子:例如,为其iCloud服务利用所有三个主要的公共超大规模云提供商的苹果;或者Twitter,在最近被收购之前,将Google Cloud Platform用于其数据平台,但使用AWS提供其新闻推送;或者汇丰银行,将工作负载分配给Google Cloud和AWS,并将一些传统服务迁移到Azure。正确使用时,多云使您能够通过在不同环境上部署最佳解决方案来为业务增加价值。实际上,这是一个全新的相互连接的服务生态系统的发展,成为公司所有解决方案的着陆区域。采用多云环境的主要驱动因素是什么?最相关的是:避免被锁定 这是组织最担心的问题之一,因为他们不想受制于单一提供商的"暴政"。这不是技术问题(因为无论是云还是多云软件供应商,都无法逃避锁定),而更多是业务战略问题。退出策略 在失败的情况下离开提供商的能力(甚至可能是合同违约)。利用 您的管理层可能希望通过维持两个或更多云提供商来保留与超大规模云提供商的谈判筹码。商业 可能是您的组织使用Microsoft的企业软件,在Amazon上销售,并在Google上进行广告宣传。在一个公共云上留下足迹可能具有更大的商业迫切性。法规要求 也许某个提供商在某些地区或在该地区提供的服务集中没有适当的服务(例如,灾难恢复)。可持续性 公司希望选择最佳的可持续云,因为这对于满足未来环境、社会和企业治理战略的趋势至关重要。创新 采用没有成本、商业方面或功能方面障碍的解决方案。知识 提供一个无障碍的环境,让员工成功,并且人们可以利用他们在职业生涯中已经获得的技能,或者可以获得新的技能。可移植性 超大规模云的专有解决方案在运行位置方面往往受到限制,而在多个云上运行的开源解决方案通常也可以在本地和边缘上使用。现在,您对为什么应该考虑为您的业务采用多云战略有了更好的理解,让我们看一些您可以使用的架构模式,以使其成为现实。多云架构模式多云架构可以使用不同的模式连接数据,使用户能够与进行分析所需的所有解决方案进行交互。在本节中,您将了解在使用这些范例时可能遇到的最常见模式。一面玻璃面临的最大挑战之一是开发一种解决方案,使得能够跨越由不同供应商管理的各种位置中的多个数据孤岛进行数据分析。为了实现这一目标,必须利用从根本上具有云不可知性的开放解决方案,并具备与多个和不同的数据源连接的能力,以在需要时混合数据。在这里主要有两种不同的方法可以利用:基于 BI 工具的方法,如图9-1所示。基于处理引擎的方法,如图9-2所示在第一种方法中,您将任务委托给BI工具(例如Looker或Power BI),以连接多个稀疏的数据源,检索相关信息,远程执行查询,并最终聚合结果。在第二种方法中,您相反地赋予处理引擎(例如PrestoSQL、BigQuery Omni)与不同环境中的各种数据源连接的能力。在这种情况下,可能有两种不同的方法:利用跨越多个超大规模云提供商的分布式环境(例如BigQuery Omni),为最终用户提供与解决方案交互的一面玻璃。利用连接器(例如Java数据库连接[JDBC])查询和混合跨多个系统的数据。一次编写,到处运行获得云选择性的常见方法是使用可以在不同超大规模平台上原样运行的软件。有几种可能的方法来实现这种模式:托管开源软件 您可以使用Apache Airflow(一种开源工具)进行工作流编排,但通过使用Amazon Managed Workflows for Apache Airflow(MWAA)、Google Cloud上的Cloud Composer或Azure上的Azure Data Factory来避免管理的开销。这样,开发人员编写的代码在不同云之间是可移植的,但您仍然可以获得托管服务的好处,除了初始设置上的小差异。这种在不同超大规模云上使用不同托管服务的开源的模式在其他情况下也适用,包括Presto、Spark、PyTorch和TensorFlow等。多云产品 Snowflake、Confluent Kafka和Databricks等工具作为主要超大规模云提供商上的完全托管服务可用。因此,可以将在AWS上运行的Snowflake工作负载(包括Snowflake SQL、Snowpark等)几乎原样运行在Azure或GCP上。请注意,不同超大规模云上的软件版本之间通常存在一些滞后。多个运行程序 Google Cloud将用于编写Cloud Dataflow管道的软件API开源为Apache Beam。由于Apache Flink和Apache Spark现在为Apache Beam提供了运行时实现,因此您可以在托管Flink服务(例如Amazon Kinesis Data Analytics)上运行几乎没有更改的Apache Beam管道。OSS抽象层 与通过Azure OpenAI或Google Cloud Vertex AI提供的提示API调用LLM不同,您可以选择通过提供一致的接口访问LangChain来访问模型。这允许在不同LLM提供商之间保持软件工作负载的可移植性(尽管您将不得不验证所使用的提示是否可以互换使用)。IaaS上的OSS 开源软件,如Dask、Modin、RAPIDS等,可以在从超大规模云提供商租用的虚拟机或集群上运行。除非您具有管理IaaS上软件的成本效益的规模,否则请尽量避免这种情况。从本地扩展到云的突发模式这是一种旨在支持拥有大型数据湖的组织的模式,他们希望将其影响扩展到云中,但尚未准备好进行完整的迁移。混合工作负载可以帮助解决他们的紧急问题,并作为额外的奖励,它们实际上还可以为未来的迁移铺平道路,显示采用和使用云技术的简便性。开始采用云方法的最简单方式是突发本地Hadoop工作负载。对于那些在硬件和与Hadoop相关的堆栈上都有重大投资且容量受限的组织来说,突发是一种很好的选择。突发对于那些可以针对上传到blob存储服务(例如AWS S3、Google Cloud Storage、Azure Blob Storage)的数据运行多个作业的情况以及可以增量更新用于处理的数据的情况非常有效。这种解决方案的主要优势之一是相同的Spark或Hive作业可以在本地运行,并且可以在PaaS集群上运行(例如Amazon EMR、Google Cloud Dataproc、Azure HDInsight)。它与那些重视开源解决方案并希望避免供应商锁定的组织相一致。在这里重要的是,将数据导入数据湖的所有上游流程保持不变。所有这些显著降低了重新执行和重新测试现有流程的风险,并缩短了第一次部署的时间。它实际上是如何工作的呢?这种方法使用Hadoop的分布式复制工具,也称为DistCp,将数据从您的本地HDFS移动到目标云blob存储中。一旦数据传输完成,就会创建一个PaaS集群,并可以在此集群上运行Spark作业。作业完成后,集群被销毁,作业结果被保存,如果您不计划运行其他作业,则blob存储桶也可以被销毁。突发工作负载的编排可以利用诸如Airflow之类的开源解决方案进行,这些解决方案既可以在本地工作也可以在云中工作(例如,通过Google Cloud Composer等以PaaS模式提供的解决方案),如图9-3。该模式涵盖的其他用例包括:组织测试版本升级的能力。在云中启动具有特定Spark版本的PaaS集群,并验证作业在本地数据的子集上是否正常运行,通常风险较小,成本较低。实验和测试新技术(例如,直接在Spark作业中集成第三方服务)。突发是我们在许多组织中经常看到的一种常见模式;让我们看看如何扩展它。从本地到云的直通模式这个模式可以看作是对前一个模式的补充。在前一个场景中,我们展示了如何使用Hadoop本地工具DistCp将本地数据湖的一部分移动到云blob存储桶中。一旦数据到位,组织可以利用其他处理引擎的工具(例如AWS Redshift、Google BigQuery、Azure Synapse),如图9-4所述,来处理数据,除了使用Hadoop本地工具。通过利用云处理引擎,可以实现多种工作负载:处理ORC、Parquet或Avro文件,包括利用联邦查询的Hive分区数据。将本地发起的数据与云中的数据进行连接:一个很好的例子是将组织的本地事务数据与从营销工具(如Adobe或Google Analytics)加载的数据进行连接。利用人工智能/机器学习工具基于本地数据构建模型。在本地数据上批量运行预测。采用这种方法时,需要注意一些关键点:与完全迁移相比,无需更改任何向本地数据湖提供数据或使用本地数据湖的系统。可以通过云处理引擎进行数据分析,除了使用Hadoop工具。一旦数据传输到云存储桶,通过所选的处理引擎访问联邦数据无需额外的延迟或处理。传输到云桶的相同数据可以由在本地运行的相同Spark作业处理。集成平台即服务(iPaaS)解决方案,如Informatica、Talend、TIBCO或MuleSoft,可用于简化数据源集成并保持同步。通过流式数据集成组织之间存在数据库和应用程序、本地和云之间的障碍。处理过程通常是批处理的,因此不支持业务要求的快速运营决策。服务通常是自管理的、传统的,并且不是为云构建的,运行和维护成本高昂。这导致了昂贵、缓慢和分散的系统架构。变更流是将数据更改(通常是数据库)实时从源移动到目标的过程。由变更数据捕获(CDC)技术驱动,变更流已经成为关键的数据架构构建块。全球公司要求CDC提供对不同数据源的复制功能,并为实时分析和业务运营提供实时流数据源。那么什么是CDC呢?CDC是一种数据集成方法,使组织能够使用更少的系统资源更快地集成和分析数据。它是一种仅从数据源中提取最新更改(更新、插入或删除)的方法,通常是通过读取源保留用于自身内部事务完整性的更改日志。CDC是限制将新数据加载到操作性数据存储和数据仓库时对源的影响的一种高效机制,它通过支持数据变更的增量加载或实时流式传输到数据目标,消除了批量加载更新和不便的批处理窗口的需求。CDC可以在许多从数据变更中获取价值的用例中使用;最常见的用例按常见程度排序如下:分析通过集成CDC将数据加载到数据仓库,组织可以获得源数据的实时物化视图。组织可以使用这个持续更新的数据构建实时仪表板。这可以用于监控系统并获取有关业务状态的最新见解,如图9-5所述。在考虑数据新鲜度的业务影响和收集处理成本之间始终存在权衡。数据库复制和同步场景通过集成CDC将数据加载到SQL数据库,组织可以获得这些数据库中源数据的实时物化视图。组织可以在目标数据库中使用这个持续更新的数据,用于从源到目标的低停机时间数据库迁移,或用于多/混合云配置,其中源和目标位于不同的托管环境中,如图9-6所示。事件驱动架构基于现代微服务的架构依赖于从组织各部门不断更新的数据的中心数据集,以实现事件驱动。通过持续写入目的地,比如云blob存储,组织可以构建基于从这些目的地消费事件数据的事件驱动架构,如图9-7所述。既然您对允许构建多云数据能力的可能模式有了更好的了解,让我们看看如何采用一种整体的多云策略。采用多云战略采用多云范式是战略的一部分,随后应将其转化为IT架构。框架为了将多云战略转化为多云IT架构,企业架构师利用一些常见的框架,如TOGAF(The Open Group Architecture Framework),来识别业务需求,定义支持流程所需的数据,以及定义应用程序如何处理这些数据(Architecture Development Model [ADM]过程),如图9-8所示。一旦完成,就可以确定为整合架构提供愿景的技术需求,而多云范式在这里为组织提供了高度的灵活性和自由度。拥有广泛的服务选择无疑是一种机会,但重要的是要谨慎管理,以避免失去关注。识别能够根据您的战略和组织规则(特别是在合规性方面)帮助实现业务目标的服务至关重要。这在今天尤为重要,因为组织越来越多地(重新)内部化IT功能,以拥有和控制其软件和解决方案。以金融服务行业为例。我们可以看到大量的投资用于将传统环境(主要基于主机解决方案)转变为现代环境,特别是在数据方面。实时分析支持着欺诈检测系统,先进的机器学习算法使得客户了解流程得以实现,反洗钱则需要处理海量数据的能力。如果仅依赖于单一提供商,获得这样一个特定解决方案/工作负载可能会很困难;因此,组织正在考虑采用多云方法,以获取最适合满足其需求的解决方案。时间尺度确定采用多云的时间尺度至关重要。具体来说: 一些组织可能永远不会完全迁移到公共云,可能是因为它们属于金融或医疗行业,需要遵守关于数据存储方式的严格行业法规。对于它们而言,“数据驻留”非常重要。一些企业可能希望保护其遗留的本地工作负载,如SAP、Oracle或Informatica,但同时也希望利用公共云创新,比如完全托管的数据仓库。 而其他大型企业则致力于用多年的时间进行与原生公共云的现代化,他们将不得不多年来采用多云架构作为最终状态。 最后,有些组织可能还没有准备好迁移,但由于临时大批处理作业的挑战而难以满足业务SLA:他们希望利用公共云的可扩展容量,避免扩展本地基础设施的成本。考虑到前述概念,您如何最终获得多云架构?让我们深入了解一下。定义目标多云架构您可以采用与之前处理单一(公共或私有)云提供商时相同的方法,但需要(1)针对每个涉及的供应商重复该方法,(2)在整体目标架构的上下文中执行。 以下步骤将帮助您定义和采用目标云架构:定义战略。 在采用多云策略之前,您需要清楚了解您的目标。这些目标可能与业务、战略或技术相关。一旦了解了您的目标,您需要确定推动您采用多云策略的驱动因素。这些驱动因素可能是成本、功能缺失、开放性或法规合规性。例如,如果您采用多云策略是为了利用,您可能希望将最容易迁移的工作负载(例如Hadoop或Kafka)放在辅助云上。一旦明确了您的目标和驱动因素,您就可以继续进行多云之旅的下一步。组建团队。 总体而言,谈论云时所需的技能可能会非常庞大。身份、安全、网络、编码等只是云工程师应该能够掌握的一些主题。当涉及到多云架构时,这套技能因为即使从高层次上来看这些主题是可以互换的,但仍然需要深入和具体的知识,相关于工程师需要操作的平台。通常情况下,您通常需要建立一个云卓越中心,如“第1步:战略和规划”中所述,以便所有所需的技能都聚集在“一个帽子”下,成为整个项目的参考。评估。 为了确定候选目标解决方案,有必要更好地了解当前的操作情况。这一部分非常关键,因为它可以完全改变后续步骤的方法。您是要执行搬迁和迁移以保持组织现有解决方案,还是最好采用基于重新平台化/重新开发的方法,以从云提供商中获得最大的好处?通常,在这一步中有一些工具可以简化发现,并收集可以用于定义迁移计划的多个数据片段。向完全无服务器架构过渡并使用云原生工具通常是最佳解决方案,但在您手头资源短缺的情况下,这可能在短期内是不可行的。设计架构。 一旦业务目标明确,您有足够的知识将所有事务变为现实,就开始将要求转化为技术架构。定义组件如何相互通信,它们如何交换信息,以及它们如何共同执行定义的任务。架构师的目标是确定最佳解决方案,为最终用户提供更大的灵活性,确保最高级别的安全性和性能,同时注意整体成本。准备迁移计划。 一旦您收集到关于何处开始和何处想要前进的所有信息,您必须确定如何到达那里的方式:方式、时间表、所需的工作量和里程碑。了解不同活动/环境之间的关系以及可能的依赖关系,如果有的话,也很重要。构建着陆区。 一切都从着陆区的定义开始(正如您在第4章中所读到的那样),那里是工作负载将被部署的基础环境:身份和访问管理、网络连接、安全性、监控和成本管理只是需要在这个阶段考虑的一些元素。这个阶段对于准备接收根据前一步中定义的模型传输数据的目标环境至关重要。迁移和转换。 基于前面步骤中定义的所有内容,现在是时候将解决方案迁移到新的架构中了。在这个最后一步中,我们将应用新服务,或者完全重建应用程序,利用本地服务。既然您对如何处理多云架构有了更好的了解,让我们深入了解另一个混合范例:边缘计算。为什么要采用边缘计算?边缘计算简而言之是一种架构模式,促使数据处理在数据产生的地方进行,即使它位于架构的边缘。边缘计算是云计算范式的补充。尽管云计算使组织能够以集中的方式访问可无限扩展的计算和存储资源,边缘计算旨在解决不同的挑战。边缘计算帮助组织处理需要在原地处理数据(或延迟非常低)或在断开网络连接的情况下需要运行活动的工作负载。云计算更倾向于将业务带到全球范围,而边缘计算更专注于将功能带到决策点附近(例如,工厂、销售点等)。带宽、延迟和断断续续的连接来自工厂部署的机器的数据已经使制造公司能够分析其仪器的使用方式,并预测维护需求,限制潜在的损坏。电信公司已能够更好地了解其网络的拥塞情况并采取适当措施来缓解可能的问题。问题在于你不能在云中进行这样的分析。为什么呢?想象一下你是一家制造公司的首席信息官。首席执行官要求你找到一种方法,以加快目前通过手动进行的生产总装线上产品质量检查的速度。你的团队已经开发了一种基于机器学习的图像识别解决方案,通过比较总装线上的物品与完美产品,快速识别缺陷。你决定进行概念验证以展示解决方案的强大之处——它以自动方式识别98%的缺陷,现在你准备投入生产。你该如何做呢?最好的方式是如何进行?概念验证过程非常简单:收集代表完美物品的图片。收集代表有损坏/缺陷物品的图片。为每张图片分配一个特定的标签(良好物品,有缺陷物品)。开发和训练一个图像识别模型,以识别两组对象。部署模型并通过API提供。针对每个总装线上的物品执行以下步骤: a. 使用摄像机拍摄物品的照片。 b. 将照片作为API调用所需的输入有效负载上传到云环境。 c. 根据模型输出决定是否继续(物品良好)或停止流程(有缺陷物品)。这种方法适用于测试目的,但有一些缺点使其难以在生产场景中部署:带宽:系统需要拍摄高分辨率的照片才能发挥效果;需要上传到云的每张照片都是相当大的。考虑到每天需要检查每个总装线上的每个物品的数量,需要传输的数据量非常大,需要大量的带宽。延迟:为了使其快速并可扩展,您需要在拍摄照片后的几毫秒内获得结果(物品良好与有缺陷物品)。即使使用高速连接,要使其足够快以跟上总装线也是困难的。脱机:工厂通常位于偏远地方,很难保持稳定的网络连接。因此,即使脱机,这些解决方案也至关重要。所有这些缺点都与单一故障点相关:与云环境的连接需求。但如果您能够使更多的智能接近物理设备/传感器并赋予它们在不可察觉的延迟或甚至脱机的情况下运行和智能决策的能力,会怎么样呢?边缘计算的目标正是实现这一点:将存储和计算资源的一部分从云中带到生成/使用数据的主体附近。用例有几种情况下,集中式云环境无法工作,边缘部署可能会有益。这不是详尽无遗的清单,但应该给您提供可能性的想法:自动光学检查:利用基于深度学习模型的图像分析来识别与期望状态不符的某些内容,例如在总装线上验证项目质量的自动检查或加速车辆组件检查的解决方案,以发现其磨损程度。提高安全性:利用摄像机和其他传感器监视特定位置(如工厂、工作场所或危险场所)的人员安全的用例。这可能包括使用热感摄像机在危险地方或接近危险机器的地方识别人员,或使用传感器检查某人是否摔倒并需要帮助。农业:使用传感器监视植物的健康、生长和吸收的营养水平。实时分析收集的数据,以验证是否缺失或超量。医疗保健:分析实时患者数据以提取更多见解,例如磁共振图像、超声图像、葡萄糖监测仪、健康工具和其他传感器。内容交付网络(CDN):为了改善浏览体验,通过互联网提供其数据的内容提供商(例如音乐、视频流、网页等)通常在边缘缓存信息,以减少检索时的延迟。实时算法可以极大提高缓存选择的效果。沉浸式互动:实时、迅速的反馈对于改善VR头戴设备(例如增强现实、游戏等)提供的沉浸体验至关重要。智能城市:旨在使城市更智能化并避免能源和资源浪费的应用,例如通过监控和控制单个灯具或一组灯具的传感器来实现的自动照明系统。交通管理系统:利用摄像机和传感器,可以实时调整交通信号,或管理交通车道的开启和关闭,以避免拥堵。当自动驾驶汽车变得更加普遍时,这一案例的重要性将更加突出。现在您对可能采用该模式的用例有了了解,让我们现在关注您可能获得的好处。好处边缘计算的作用是扩展集中式基础设施,并将更多计算能力带到架构边界附近。这使连接(或断开连接)的设备能够执行需要极低延迟和本地计算能力的任务(例如,机器学习推断)。这种模式解决了一些基础设施挑战,如带宽限制和网络拥塞。其他好处包括以下几点:可靠性 大多数物联网(IoT)架构包含在不完全连接的环境中的元素,例如您的办公室或家庭。有一些相当常见的情况,实际上是无法在规定的低延迟的情况下保持与世界的持续可靠连接。想象一下农村工业站点,电信公司没有投资于高速有线连接,或者位于海中的风力涡轮机,利用老式的连接方式(2G/3G),或者自动驾驶汽车需要微秒级的延迟来做出决策(如果您的汽车必须刹车以避免事故,您不希望等待来自云的响应)。所有这些用例为了有效运作,需要能够在本地存储和处理数据的设备,并能够处理暂时的连接中断而不对其功能产生影响。法律/合规性 在某些行业(例如金融服务和保险)中,某些国家有关存储、处理和暴露数据的规定非常严格(想想GDPR法规)。在本地转换和使用数据,并可能将修改后的数据发送回云端(例如,去标识化、加密等),将增加组织采用现代架构并仍保持合规性的能力。安全性 数据外泄、分布式拒绝服务(DDoS)攻击防范和数据保护是边缘计算可以显著降低风险的一些场景,因为设备可以完全脱机工作,甚至可以被强制通过强化的网关与外部世界连接,该网关可以实施额外的数据保护层,如临时加密。现在让我们将注意力转向处理边缘计算范式时可能面临的挑战。挑战除了这种新范式可能为组织带来的好处之外,还存在一些您可能需要解决的缺点,例如:计算和存储能力的限制 部署在边缘的设备通常配备执行定义良好的操作的有限硬件(例如,收集温度数据的传感器等)。因此,这些设备往往是超专业化的,不适合于通用性任务。即使设备能够执行一些通用任务,比如在本地运行ML模型,安装的设备版本可能没有必要的功能。设备管理/远程控制 正如我们之前所概述的,由于连接性或严格的访问政策,云与这些设备之间的连接可能会很棘手。您可能需要物理访问每个设备以检查状态,并最终应用可能需要的更新/补丁。如果某些位置条件恶劣或设备部署在难以访问的位置,则这可能不是一项简单的任务。备份和恢复 由于这些设备大多数时间都处于脱机状态,您可能需要实施额外的本地物理基础设施(例如,智能网关加上网络区域存储)来进行备份和恢复,这将提高总体成本。如果这些挑战中的任何一个适用于您的用例,您将需要评估它们是否构成阻碍,或者是否存在有效的解决方案来缓解这些问题。边缘计算架构模式与云计算一样,在定义边缘计算架构时,有一个清晰的策略是很重要的:可能存在所有设备由中央进行管理的情况(可能是由云应用程序管理),而也可能存在节点完全断开连接或只与本地网络部分连接的情况(以相互通信或与本地网关通信)。总体而言,有两种类型的边缘计算架构:一种是设备智能的情况,另一种是在边缘添加了智能网关的情况。无论是智能设备还是智能网关,ML激活的工作原理都类似。让我们看看这两种模式。智能设备智能设备是实施边缘计算架构的一种直截了当(尽管昂贵)的方式。在我们的示例场景中,用于生产必须经过质量检查的物品的机器将都需要具有执行能够识别图片中缺陷的ML算法的硬件。进行逻辑处理的设备可以简单地称为“节点”,它们的硬件根据它们需要解决的用例而变化很大:它们可以配备通用CPU或专用硬件来执行特定任务。具有可以直接执行复杂逻辑的非平凡硬件的智能设备(例如,Raspberry Pi、Coral传感器等),如图9-9所示,提供了很高的灵活性,但需要大量的管理工作(例如,软件更新、安全补丁等)和更高的硬件成本。智能网关通过有线或无线连接到智能网关的“哑”设备/传感器,智能网关可以代表它们执行逻辑,如图9-10所示,是处理同一位置(例如,工厂内)大量传感器的首选方法,因为它可以减少管理工作(一个智能设备而不是n个)及相关成本。然而,这引入了架构中的一些安全挑战,因为它可能成为单点故障(SPOF)。ML激活我们之前讨论过一个PoC场景,其中机器与云环境完全连接,不断地发送和接收数据,以决定如何处理流水线上的物品(接受或拒绝)。您已经看到,要使这种架构能够在生产环境中部署,唯一的方法就是扩展它,使设备能够在边缘直接执行该逻辑。在边缘执行的设备代码可能因情况而异,但可以概括为一个ML模型,该模型通过处理一些输入(在这种情况下是图片)来获取所需的输出(我们示例中物品的最终决定)。在前面的章节中,当讨论现代数据云架构时,您已经了解到云的主要优势之一是其能够大规模收集和处理数据,使开发人员能够轻松实现最先进的ML算法。让我们看看开发ML算法的过程的不同步骤(如图9-11所示),为此,让我们利用先前的示例:数据收集 一切都始于将用作流程输入的数据。在我们的场景中,我们需要开发一个能够从图片中识别物品的信息和特征的模型,因此重要的是能够访问大量正常物品和有缺陷物品的图片。(请注意,您可以创建尽您所愿的状态,但为简单起见,我们将仅考虑两个状态:正常物品和有通用缺陷的物品。)数据分析 需要对在先前步骤收集的数据进行精炼(例如,清理、转换、丰富)以供利用。在这个具体的示例中,我们需要验证所有拍摄的照片的质量(例如,焦点、对齐、噪声等),然后,对于每个图像,我们需要应用一个标签,指示图片中报告了什么,以生成两个分离的集合:正常物品和有缺陷的物品。ML模型开发、训练和测试 是时候开发算法了;有许多工具和技术可供使用(例如,scikit-learn、TensorFlow、PyTorch),甚至还有使生活更轻松的自动化解决方案(例如,在特征工程、模型选择和参数调整方面提供支持)。在我们的示例中,我们可以使用迁移学习技术来开发一个深度学习模型来识别图像。ML模型部署 该模型已准备好用于预测。它可以作为云中的API服务部署,也可以直接部署在边缘节点上。反馈数据收集 为了提高模型的质量,可以收集来自边缘的数据,然后重新开始整个过程,进行交互式过程,旨在使预测变得更好。在我们的案例中,边缘节点将通过批处理过程发送回(例如,分析过的图像以及预测结果)。很明显,这些ML建模步骤对边缘架构提出了一定的要求:数据收集需要具备存储收集的图像的能力,以便在足够长的时间内可以交换磁盘并将数据上传到云端,同时可以将磁盘移到具有更好连接性的位置。数据分析可以在云中进行。ML模型开发也可以在云中进行。然而,测试需要在云上模拟边缘环境的能力。部署要求在开发ML模型时牢记边缘硬件的特征。将ML模型部署到边缘可能需要升级硬件组件,包括ML推理芯片,如Google Coral Edge TPU或NVIDIA Jetson Nano。反馈数据收集需要实施一个应用程序来跟踪人工操作员覆盖ML算法建议的决策的情况。现在您对边缘计算范式的理论有了更多了解,让我们看看如何采用它。采用边缘计算让我们看看采用这种范例可能如何为一个模范组织提供更大的可见性和更高的效率。最初的背景MagiCan是一家(虚构的)专门生产用于为全球最具标志性品牌提供服务的饮料罐的制造公司。该公司在世界各地拥有多个生产厂,每个工厂都有多台机器全天候工作。该公司的战略一直是在城市外地区建厂,而在一些地方,本地电信公司为该公司提供了基于老式无线技术(例如3G载波)的互联网连接。该公司的核心价值之一是“高质量产品是必须的,不惜一切代价”,并且该组织一直投入大量资金以确保端到端生产周期的质量(例如,维护机械和对生产物品进行质量检查)。MagiCan发现存在一些问题需要解决:多个站点的机器故障引起了生产的多次暂停,生产物品存在缺陷导致罚款,并且难以获得对其生产厂状态的一致性视图。因此,董事会决定启动一个新项目,以改善其收集和处理工厂数据的方式,以解决所有这些问题。项目该组织已经在多个工作负载(例如DWH、网站、SAP等)中利用云计算解决方案,并决定扩展其当前的体系结构,投资于直接连接到其机械设备的设备:目标不仅是直接从工厂收集数据以实时查看机器的工作方式,还允许用户对一些参数/组件(例如执行器、装配线等)进行操作,以修复问题、纠正不准确之处并改善生产过程的整体质量。该组织在IoT项目专业化的第三方合作伙伴的帮助下,定义了一个三步旅程:通过开发所需的体系结构来改善整体系统的可观察性,以从工厂收集数据并构建近实时监控系统。开发用于调整执行器运行的自动化。通过开发预测模型来优化机械设备的维护。让我们深入探讨这三个步骤的旅程。改善整体系统的可观察性考虑到MagiCan在全球各地有几个位置分布,并且其中大多数位置与云世界没有可靠的连接,无法基于流式处理模式开发实时体系结构。该公司决定将问题分为两个不同的部分:本地体系结构(在工厂级别),在该级别,所有机器都实时连接,所有信息立即被中央但本地应用程序可见。利用云系统的集中体系结构,从工厂收集的信息每天多次以批处理模式收集。目标是开发一种标准方法,以监控每个工厂的机器,并在工厂离线时仍使数据可用。中央云智能脑必须收集来自不同工厂的所有数据,然后使数据科学家能够研究这些数据,以便他们可以提取更多见解并利用云的强大功能,如图 9-12所示。在每个工厂中,部署了带有传感器的设备,用于收集来自不同执行器的数据(例如速度、旋转、温度等)。这些设备与能够实时收集所有数据并通过自定义开发的可视化工具提供给用户的智能网关进行本地连接,该工具为用户提供了对工厂状态的一种连续快照。每隔x分钟,数据会被压缩并发送到云进行进一步分析,然后与来自其他工厂的数据一起进行汇总。如果网络存在问题,则批处理将被排队并在下一批次中发送数据。发展自动化一旦来自所有工厂的数据在云中可用,就是时候开始玩耍了!可用的信息量使团队能够对以下几个方面获得可见性,例如:工人如何与机械设备进行交互执行器配置与罐头缺陷之间的相关性由于机械问题导致的平均闲置时间在使用特定原材料时的理想机器配置 考虑到设备和云之间通过智能网关的连接是双向的,可以发送反馈并使机器接收更新的信息以调整它们的操作方式。公司开发了一个近实时的过程,用于处理来自工厂的所有数据,了解与理想目标操作模式的差异,并发送更新的配置以立即调整执行器的行为。通过这个在中心管理的过程,可以根据机器所处的当前场景提供基于定制配置的能力。优化维护最后一步是解决一个相当难以处理的问题:机器的维护。在能够全面了解机器运行状况之前,维护是以两种不同的方式进行的:计划维护通过定期干预 根据机器型号、历史记录以及其运行方式,按照明确定义的日历安排,定期检查机器。非计划维护通过临时干预 由于某些事件(如部件损坏或由操作员错误引起的故障),故意检查机器。考虑到维护操作可能会使机器暂停数小时甚至数天,有必要尽量减少非计划停机时间。利用来自工厂的数据(分析是在最初投入使用后的几个月后进行的,因为需要足够的历史数据量),开发了一个预测ML模型算法来计算机器故障的概率:当百分比超过一定阈值时,会向工厂经理发送警报,后者必须决定如何处理(即安排非计划维护操作或等待下一次计划维护)。最终结果和下一步骤整个端到端项目耗时超过一年,但最重要部分(即在各工厂实现统一视图的开发)的第一个版本相对较快(不到六个月)就完成了。借助这种分布式架构,公司能够提高在工厂和公司层面的可观察性,利用了一个共同的、标准化的、统一的过程。同时,它能够提高整个生产链的效率。MagiCan现在正在专注于一个全新的项目,旨在降低质检成本,尽可能自动化整个流程。与本节开头介绍的类似,该组织希望扩展已经建立的架构,实施一个在生产线上直接识别缺陷的流程:为实现这一目标,它将利用新的摄像设备,这些设备具有基于机器学习模型处理图像的能力。其目标是将当前执行质检的大部分人员重新分配到其他活动中。总结本章向您提供了处理混合和多云架构的高层次理解,当单一云提供商无法满足所有业务分析需求时。它向您介绍了在处理这种架构时需要考虑的各种因素,以及一些常见的实施模式。最后,它向您介绍了边缘计算以及如何使用它。主要要点如下:在单一云提供商上 consol,是非常有吸引力的,因为它简化了整体架构,易于学习/采用,以及总体成本较低。中小型企业应选择单一云提供商,并使用该超大规模提供商提供的全托管服务设计其架构。由于收购或希望使用在其中一个云上可用的服务,多云架构可能变得不可避免。当组织担心锁定、需要实施退出策略、有一些严格的监管要求,或想要提高工作负载的可移植性、创新、知识和可持续性时,必须考虑多云战略。采用多云战略是一个需要明确定义所需战略并扩展团队技能的过程。在处理多云架构时,有各种各样的架构模式可供利用,如开发单一玻璃窗口、在本地工作负载上爆炸Hadoop、使用Hadoop直通以在云中进行数据处理,以及使用变更流进行数据集成。边缘计算是一种旨在将存储和计算资源的一部分从云中带到生成/使用数据的主体附近的范例。带宽限制、网络拥塞、可靠性、法律合规性和安全性仅仅是边缘计算范例可以带来的一些好处。边缘计算的主要挑战包括计算和存储能力的限制、管理/远程控制以及备份和还原。利用边缘计算范例的主要用例包括自动光学检测、改进的安全性、农业、医疗保健、CDN、沉浸式交互、智能城市和交通管理系统。在选择边缘架构时,智能设备比使用智能网关更简单但更昂贵。无论哪种情况,ML 激活都涉及添加诸如设备存储和 ML 推断芯片等功能。在下一章中,您将了解在人工智能和机器学习中对架构和框架做出的高层次决策。
0
0
0
浏览量2016
攻城狮无远

《流式Data Mesh》第七章:构建流式Data Mesh

第七章:构建流式Data Mesh在第3到第6章中,我们介绍了流式数据网格的支柱。现在我们将运用这些知识来设计一个流式数据网格。正如本书前面提到的,"数据网格"这个术语来自于微服务架构中的"服务网格"一词。我们借鉴这种相似性,使用与描述微服务架构部分相同的术语来描述流式数据网格的组成部分。我们将描述架构的每个部分,因此对微服务架构的了解不是先决条件。我们还将考虑多种流式数据网格解决方案,并列出它们的优点和权衡。最终得到的将是一个简单清晰的框架,可用于实现您自己的流式数据网格。基础设施正如在第1章中所述,我们将使用Kafka来实现流式数据网格。使用Kafka是可选的,可以替换为Apache Pulsar或Redpanda;无论您选择哪个,我们建议使用完全托管和无服务器的流式处理平台,以摆脱自我管理基础设施的任务。同样,我们将使用ksqlDB作为流处理引擎。它也可以作为完全托管或自我管理的服务提供。以下是一些完全托管的选项:DeltaStreamPopsinkDecodableMaterializedRisingWaveTimeplusKafka和ksqlDB都是使用SQL作为构建流式数据管道的主要方式的流处理引擎。可能还有其他选择,但在撰写本书时,这两个选项最为突出。 我们在第6章讨论了支持域所需的自服务和相关工作流程。这些相同的自服务和工作流程无论您使用完全托管服务还是自我管理基础设施都可以使用。本章后面,我们将提供关于如何将这些自服务实现为工作流程的想法。两个架构方案在设计流式数据网格时,我们有两个选项:在域内使用专用的流式基础设施,或者使用共享的多租户基础设施,其中每个域都是一个租户(参见图7-1和7-2)。这两个图表都包括了流式平台(Kafka、Pulsar、Redpanda)。不可见的是流处理平台(ksqlDB、SaaS流处理器)和Kafka Connect集群,因为它们的存在是隐含的。这两个图表的唯一区别是流式平台的位置。简要总结每个组件的目的,流式平台是用于流式传输数据(或事件)并将其存储在主题中的消息系统。流处理平台是在流式平台的主题之间对数据进行转换(丰富和清洗)的系统。最后,连接集群保存连接器,用于将系统(如数据库)与流式平台集成,以将其数据输入或输出到主题中。在这两个选项中,流式数据产品的生产者和消费者(或客户端)在本地进行生产和消费。这意味着客户端可以访问靠近或最好是位于同一全球区域的基础设施。这是在流式数据网格中保持良好体验的一个规则。如果基础设施不接近,这些客户端将不得不通过长距离与其基础设施通信,从而在应用程序中产生延迟和其他不良影响。如果需要处理较长的距离,可以使用复制机制将数据本地传递给客户端。复制是在流式平台(如Kafka)之间移动数据的过程。对于使用Kafka进行消费或生产的客户端来说,这个过程是隐藏的。因此,客户端不会暴露给长距离可能给它们带来的意外影响。现在让我们更详细地讨论这两个选项。专用基础设施此选项将基础设施(流式平台、流处理平台和Kafka Connect)部署到各个域中,以便只有该域本身可以使用它们;因此,它们是专用于域的基础设施。这个选项具有一些优势:安全性实施更容易,因为基础设施中没有其他需要分离的域(租户)。这减少了中央工程师需要开发的自服务数量,并使其更易于管理。这种复杂性的减少可能导致安全风险的降低。基础设施在与域相同的区域进行配置。这使得所有数据派生都保持本地化,从而提高了基础设施组件的性能。它还减少了云中跨区域数据移动的成本。跨区域数据移动仅限于将流式数据产品复制到其他域。使用情况指标更容易与域相关联。日志已经分隔开,可以与特定消费者关联,以便轻松计费。这包括审计日志,便于监控域中的安全访问。可以将可扩展性专门应用于域,而不会影响其他域。扩展基础设施的上下文可能会造成中断。将中断限制在特定域内,将为所有未受影响的域提供更好的体验。如果域向消费者保证了正常运行时间的服务级别协议(SLA),可以针对满足这些保证的域/数据产品实施灾难恢复计划。唯一的缺点是将基础设施提供给所有域的成本。使用完全管理此基础设施的SaaS提供商可以降低这一成本。有两种类型的域:生产流式数据产品的域和消费流式数据产品的域。一个域也可以是两者兼而有之。由于基础设施专用于域,我们将描述如何在这些域中部署基础设施。生产域架构生产域是指仅生成数据产品而不消费其他域的数据产品的域。在图7-3中,配置了一个Kafka集群、一个Connect集群和一个ksqlDB集群。对于专用基础设施,这是域所需的最低配置。Connect集群从操作(源)数据库中读取数据并将其提供给Kafka。在将其发布为流式数据产品之前,数据派生物经过ksqlDB中的清洗、转换和混淆处理,并发送到最终的Kafka主题中。如果您的数据源仅来自微服务,就没有必要使用Connect集群。同样,如果您的数据不需要任何转换或清洗,可以省略ksqlDB集群。大多数情况下,一个域将需要同时使用Connect集群和流处理集群来构建流式数据产品。高吞吐量生产域对于提供高吞吐量流式数据产品的域,我们建议将Kafka集群拆分为写入和读取集群(参见图7-4)。这样可以让每个集群独立扩展。写入集群针对高吞吐量写入进行了优化。读取集群专注于向其消费者提供具有特定SLA保证的流式数据产品。写入集群和读取集群之间的复制可以由Kafka连接器或集群链接处理,集群链接是Confluent提供的一种专有技术,使数据复制变得简单。它减轻了中央团队管理更多组件的负担。同样,如果读取集群旨在为多个消费者提供服务,从而增加了读取吞吐量,这种模式也适用。在这种情况下,写入集群的规模可以比读取集群低。拥有两个Kafka集群可以在灾难情况下提供故障转移的方式。这也增加了可靠性。每个集群都可以故障转移到另一个集群。这需要两个集群具有相同的容量。故障转移的协调是中央团队需要自动处理的事项。故障转移不是域需要了解的技能。灾难恢复的详细信息超出了本书的范围。消费域架构图7-5说明了生产领域的数据如何复制到消费领域中的Kafka集群。需要明确的是,生产领域和消费领域都有一个Kafka集群。流式数据从生产领域的Kafka复制到消费领域的Kafka。如何进行这种复制将在“数据平面”中讨论。重要的是要知道,在消费领域中执行复制的应用程序存在。如果生产领域实施了按使用量计费的方法,那么消费工作负载将不会成为成本的一部分,因为它位于消费领域的基础架构中。如果您希望在消费领域内保持Kappa架构,请使用此架构。数据保持为流式,并且允许使用ksqlDB进行进一步处理,而无需首先落入数据库中。该架构还使消费领域能够生成流式数据产品。它具有与生产领域相同的基础架构。在第2章中,我们提到流式数据网格的一个优点是对实时用例和批处理都提供了流式支持。我们还警告说,如果一个仅限批处理的领域突然需要实时用例而没有支持的基础设施,将会产生昂贵的技术债务。在消费领域中拥有一个Kafka集群可以避免这种债务。该架构还提供与实时在线分析处理(实时OLAP或RTOLAP)数据库的集成。RTOLAP数据库可以快速检索、聚合和分析数据,而无需运行繁重的批处理。图7-6展示了与RTOLAP数据库的本机集成,并省略了图7-5中显示的Connect集群。图7-6展示了市场上今天可用的一些RTOLAP数据库示例:Rockset、Apache Pinot、Apache Druid、Materialized、Tinybird和ClickHouse。这些RTOLAP数据库可以以大规模为实时仪表板和应用程序提供数据。通过流数据网格,您可以从产生领域中的源头保持Kappa架构,到消费领域中的实时应用程序。没有流平台的消费领域为了降低成本,只在消费领域部署一个Connect集群是可选的(参见图7-7)。此选项从产生领域的Kafka集群中消费数据,并将其持久化存储在消费领域的数据库中。这个选项阻止了该领域自身提供流数据产品的能力。它还强制该领域运行批处理过程,以将数据转换到目标数据库中。这个转换将被嵌入到数据流水线中,但不会由中央团队支持。这是因为目标数据库在中央团队提供的基础设施范围之外。 在数据流水线中嵌入的任何批处理过程将使该领域失去实时能力。要更改这一点,需要提供一个Kafka集群和一个ksqlDB集群。需要付出很大的努力来重新配置所有的基础设施,使其像图7-5一样工作。这是我们在第2章中提到的技术债务。这个架构还给中央工程团队增加了工作量。他们必须支持两种消费流数据产品的方法:Kafka集群之间的复制(图7-5)和基于连接器的消费(图7-7)。每种方法都有自己的工作流程和自助服务来构建基础设施。这个选项开始变得更像是一种违反流数据网格精神的反模式。推荐的架构方案图7-8展示了我们推荐的流数据网格架构,因为流数据产品在消费领域本地出现。可以将这种体验类比为计算机文件系统中的快捷方式。用户可以本地读取和处理快捷方式的内容,而这些内容实际上来自远程计算机。在我们的情况下,消费领域Kafka集群中包含流数据产品的主题看起来像是从产生领域发出的快捷方式。消费者在本地读取数据,而复制流数据产品的复杂性被隐藏起来。这种架构也被推荐,因为它使得为各个领域提供基础设施变得简单,对中央团队的负担也较轻。所有领域都使用相同的基础设施进行配置:Kafka、ksqlDB和Connect集群。这使得任何领域都能够产生和消费流数据产品。类似地,图7-9是如何将RTOLAP数据库包含到实时数据分析中的示例。RTOLAP数据库可以直接从Kafka消费流数据产品。ksqlDB还提供将流数据产品转换为RTOLAP数据库易于消费的格式的能力。RTOLAP数据库希望对数据进行预处理,以减少其工作量并专注于更快的查询执行。通过采用单租户方法,专用基础设施使得管理流数据网格变得更加容易。这种方式使得安全性和基础设施的配置都变得简单。领域也不会受到其他租户消耗资源导致的“嘈杂邻居”问题的影响。可扩展性更简单,使得领域的基础设施具有弹性。然而,所有这些都可能导致流数据网格的成本非常昂贵。另一种选择是使用多租户基础设施来构建流数据网格。多租户基础设施在多租户基础设施中,Kafka被多个领域共享。访问控制被设置为将敏感数据与其他领域分离和保护。这可以降低成本,但会增加流数据网格的复杂性。在图7-10中,Connect集群仍然在领域内部进行配置。ksqlDB仅部署在产生领域,这一点我们稍后会在本节中讨论。每个领域还通过访问控制被分配了多租户Kafka集群的一部分。由于两个Kafka集群将为多个产生和消费领域提供服务,我们再次建议将写入集群与读取集群分离,以便能够分别进行扩展和简化管理。这将仅将流数据产品的转换工作负载隔离到写入集群中。在表7-1中,您可以看到实施此分离的一些优势列表。生产域架构在图7-10中,产生领域看起来类似于本章前面讨论的专用基础设施,只是Kafka是一个多租户集群并位于中央空间。写入集群将用于流数据产品的开发。领域中的Connect集群将将源数据衍生发送到写入集群中的主题。ksqlDB将访问这些主题,将衍生转换为构建流数据产品。最终的流数据产品从写入Kafka集群复制到读取Kafka集群。作为发布的一部分,流数据产品会复制到读取集群中。独立的读取和写入集群有助于将资源用于流数据产品的开发,与为其提供服务的资源分离。再次强调,读取和写入流平台的分离使得扩展变得容易,而不会中断任何一方。消费域架构在图7-10中,消费领域没有ksqlDB,这意味着无法进行实时数据转换。如果消费领域需要进一步转换数据,有以下几种替代方案:消费领域可以使用完全不同于流数据网格的基础设施,但这需要自行管理。消费领域的Connect集群很可能会将数据写入数据存储,这会立即强制执行批处理语义。将流数据产品持久化到数据存储中会将其转换为批处理数据产品。使用Apache Spark或Apache Flink直接从流平台读取流数据产品。这将保持流式处理,但需要在这些技术上具备深入的技能。此外,基础设施将超出中央团队的责任范围。这将使消费领域回到单体式数据仓库或数据湖的方式。让原始产生领域创建满足消费领域需求的新的流数据产品。这将不需要消费领域进行进一步的转换。尽管其中一些替代方案遵循批处理语义,但数据产品仍然以流式方式进入消费领域,这仍然使得该解决方案符合流数据网格的要求。流数据网格确保数据产品从其来源流式传输到其消费者。消费者在消费数据产品后如何最终使用这些流数据产品(批处理还是流式处理)不在流数据网格的要求范围内。需要注意的是,如果没有像ksqlDB或SaaS流处理器这样的流处理平台,领域将无法进行实时数据转换。数据产品的转换将需要在数据库或源系统本身中进行。这开始倾向于批处理语义。即使Kafka Connect在数据产品转换后读取数据库,生成的流数据产品也不会真正实时,而是数据产品的快照。消费者需要了解这一点,以便在实施其用例时考虑到这种行为。区域多半情况下,您的业务将跨越多个全球区域。如果是这样的话,将数据保持在接收方所在的区域是非常重要的。正如我们之前提到的,要求客户端跨区域进行数据的生成或消费将会对应用程序产生负面影响。解决方案是在每个区域内复制读写流平台的模型,并在区域之间异步复制数据。图7-11展示了多个全球区域:美洲、欧洲、亚太地区。虚线表示区域之间的数据异步复制,实线表示客户端在各自区域内本地生成和消费数据。复制过程是一个流式处理过程,因此不会丢失实时性。在这个模型中,为了避免在流平台内出现名称冲突,为所有资源提供前缀(或命名空间)非常重要。这包括主题名称和模式名称。在图7-12中,假设通过流数据网格流动的流数据产品包含来自不同全球区域的员工信息。这些领域之间的分离是因为它们都有不同的工资和福利系统,并遵循不同的区域法规。遵循流数据网格中的数据治理规则,应达成共识,所有这些区域都将遵守一个单一的员工模式。这将防止每个区域中发生多次冗余的转换操作。在图7-12中,浅色阴影的管道代表包含流数据产品的可写主题,该产品是本地写入的。其他管道表示只读主题,其中包含源自远程可写主题的复制数据。这为每个区域提供了一个本地主题,用于读取流数据产品,以及一个只有本地区域可以写入的本地可写主题。为了创建业务中所有员工的单一全局视图,您可以同时消费这三个主题(参见示例7-1)。每个区域都可以创建一个包含所有员工合并为单个流的全局视图。consumer.subscribe(["*.employees.dp"]) 这种模式对于专用基础设施解决方案也是适用的。 专用基础设施和多租户基础设施并不是相互排斥的。我们可以两者混合使用。这需要中央团队更多地管理和组织基础设施和元数据。例如,可以为美洲地区的域提供专用基础设施,而将亚太地区和欧洲地区限制为多租户基础设施。在云环境中,这些地区的基础设施配置往往更昂贵,而在这些地区实施多租户解决方案可能是一种更具成本效益的选择。我们提出的这两种架构解决方案都是针对域和它们之间的数据共享。它们使域能够生成和消费流数据产品。流数据网格还有一个中央组件,用于协调和管理它及其域,中央团队是其中的一部分。它还具有作为流数据网格核心的架构。流数据网格中央架构流数据网格的中央架构是指域之外的一切。它由中央团队操作,并管理通过我们在第6章中介绍的命令行界面触发的所有自服务。它还管理OpenLineage、模式注册表、流数据目录等。在本节中,我们将列出中央架构中的所有系统,并查看域和流数据网格之间的相互通信。微服务具有数据平面、控制平面和边车组件。将这些组件组合在一起形成了服务网格,从中派生出了“网格”一词。数据网格的概念就是从这种架构中引申出来的。我们可以将这些组件与流数据网格中的组件进行映射:边车组件是部署在域内的代理,与控制平面进行通信。域之间的数据复制是数据平面。中央流数据网格是控制平面(除了域之外的一切)。在本节中,我们将详细介绍这些映射关系。术语“数据平面”将代表流数据产品在域之间的复制,并由域代理控制。我们还将开始称呼中央流数据网格为“控制平面”。域代理(aka Sidecar)在微服务中,边车是与微服务一起部署的组件,它与控制平面进行通信。控制平面与边车进行通信,以控制微服务的行为、配置其安全性、捕获使用度量和配置数据的传输。在流数据网格中,也可以使用相同的概念。该代理的唯一目的是将域与流数据网格之间的所有交互聚合到一个组件中,以便在配置域时可以轻松部署。我们在第6章中提到的面向域的CLI与控制平面进行通信。然后,控制平面代表CLI向域代理发送命令作为命令工作流的一部分。以下是一些命令示例(非详尽列表):下载并安装连接器和UDF(用户自定义函数)到相应的系统。控制平面中的自助服务将编排一系列任务,包括(1)在中央仓库中搜索连接器或UDF(即构件),(2)命令域代理将构件下载到域本地,然后(3)指示域在相应的系统中安装或升级现有构件。配置生产和消费域之间的复制。此命令将要求从生产和消费域中的多个域代理进行编排,包括(1)向消费域授予对流数据产品的访问权限,(2)检查每个域中两个流平台的容量,并且(3)在消费域中从生产域进行复制。发布流数据产品,包括(1)构建AsyncAPI YAML文档,(2)在模式注册表中注册其模式,(3)将其部署添加到OpenLineage图中,然后(4)在流数据目录中部署AsyncAPI。这些工作流本身也不是详尽无遗的。我们将在本章后面提供更详细的示例,说明这些工作流的实现方式。在图7-13中,我们将域代理的位置表示为每个域中的代理/边车。该代理使数据可以在流数据网格中的其他域之间移动,这被称为流数据网格中的数据平面。控制平面控制代理并代表管理平面编排工作流。数据平面在图7-13中,如前所述,数据平面表示域之间复制流数据产品,并由域代理提供支持。域代理根据控制平面的指示来配置数据的复制。域代理配置数据复制机制。在本章中,我们将使用的机制是集群链接(cluster linking),这是一项由Confluent提供的功能,使Kafka代理能够相互复制数据。配置必须包括安全性和监控。域将不知道流数据产品复制到消费域的具体机制或该机制如何获得安全性。域中所有系统的安全日志(如审计日志)和使用度量日志也属于数据平面的一部分。度量日志将被发送到可观测性服务(如Datadog、AppDynamics或Prometheus和Grafana)。安全日志将被发送到安全信息和事件管理(SIEM)服务(如Splunk、Sumo Logic或SolarWinds)。许多这些日志服务可以用于度量日志和安全日志。域代理会自动配置这些日志系统,并且在域中大部分是隐藏的。控制平面控制平面与域代理进行通信,以执行许多与域相关的任务。这些任务是由CLI或中央团队从管理平面发起的。在本书的大部分内容中,我们一直将流数据网格的这一部分称为“流数据网格”甚至是“中央域”。它包括中央团队、自助服务的实现以及支持域的系统,如OpenLineage和Schema Registry。正如前面所述,在本书的剩余部分中,我们将称之为控制平面。在图7-14中,控制平面包含三个子平面:元数据和注册平面包含控制平面需要使用的许多开源工具。它们包括OpenLineage、Apicurio、Schema Registry和JFrog(一个构件库)。这些工具中包含了消费者在流数据网格中发布的流数据产品中所需的大部分元数据,使消费者能够信任这些产品。管理平面为用户提供监控流数据网格和发起命令的访问权限。自助服务平面自助服务平面包含面向域的服务。管理平面和元数据注册平面管理平面(Management plane)在图7-14中提供了与域角色进行交互所需的接口,通过控制平面与其自身的域进行交互。管理平面还被中央团队用于管理控制平面和域。它包含了可视化工具,允许团队监控使用度量和安全日志。他们能够从日志和度量中接收警报,以帮助诊断问题。中央团队能够禁用系统、断开连接、隔离域,并应用速率限制规则,如限制数据吞吐量和存储。元数据和注册平面,实际上可以是管理平面的一部分,允许用户管理模式、监视数据产品部署、可视化谱系图,并在JFrog Artifactory中更新连接器/UDF。我们在图中将这些平面分开,再次帮助组织系统。一个单一的可视化工具将有助于提供一个“全景式”的视图,聚合所有所需的元数据和用户界面,以帮助管理整个流数据网格。您应该能够在管理和元数据/注册平面中更换不同的产品,仍然能够获得对流数据网格的相同视角。自助服务平面自助服务平面没有可视化工具。它是实施自助服务的微服务面向域的部分。它也是由中央团队控制流数据网格的微服务。这些服务直接与域代理进行通信,并调用工作流。基本上,自助服务平面由中央团队开发的代码组成,用于整合流数据网格中的所有域和系统。工作流编排在自助服务平面编写的大部分代码都被实现为工作流编排的有向无环图(DAG)。这些DAG提供了工作流的可视化,可以提供给合规审计员和中央团队的管理员。这些可视化是证明数据治理和法规遵守的证据。例如,在构建生产域和消费域之间的复制过程时,应包含逻辑以显示未违反GDPR。可以是一个采样敏感信息并拒绝复制的机器学习算法,也可以是一个手动任务,使安全团队成员执行相同操作。无论哪种方式,看到任务是工作流的一部分将有助于让参与流数据网格的所有人放心。 在图7-14中,工作流编排的实现是Apache Airflow。市场上还有其他工作流编排器,如Dagster和Luigi,可以完成相同的工作。在这个例子中,我们将使用Airflow。实施链接的DAG图7-15是一个DAG,展示了将生产域与消费域链接以复制流数据产品的工作流程。在这个例子中,DAG中的一个节点检查GDPR违规情况。图7-15显示,GDPR法规检查结果没有违规,但对于一些节点,未能授予对主题的访问权限。我们可以在DAG中添加更多逻辑,以检查可能影响域之间创建集群链接的更多信息。在图7-16中,我们在实际授予对主题的访问权限之前添加了两个节点。首先,我们添加了一个检查,以查看目标Kafka集群是否具有足够的容量来容纳流数据产品。其次,我们检查源Kafka集群的容量,以确定是否具有资源来添加另一个消费者到其流数据产品中。更准确地表示工作流程将帮助审计员知道我们正在采取措施防止违规行为。准确性还可以在命令不起作用时更容易进行调试。中央团队可以直接查看工作流程后面的具体任务,以调查和解决问题。 在Airflow中,DAG使用Python编写。示例7-2是一段Python代码片段,显示了如何组装DAG,但缺少与任务的实现。每个方法都是一个任务,并向下一个任务提供输入。这为Airflow提供了代码内省的信息,以生成用户的图形视图。1 @dag( 2 tags=['link'], 3 description='link data product to consuming domain' 4 ) 5 def link(): 6 7 @task 8 def grant_access_to_topic(data_product, source, destination): 9 return {} 10 11 @task 12 def check_capacity_source(destination): 13 return {} 14 15 @task 16 def check_capacity_destination(destination): 17 return {} 18 19 @task 20 def check_for_gdpr_violation(dataproduct): 21 return { "ok": True } 22 23 @task 24 def create_link(data_product, destination): 25 return {} 26 27 @task 28 def test_link(context): 29 return {} 30 31 @task 32 def notify(results): 33 return {} 34 35 data_product = "foo" 36 destination_domain = "bar" 37 38 result = check_for_gdpr_violation(data_product) 39 link = create_link( 40 grant_access_to_topic( 41 result, 42 check_capacity_source(destination_domain), 43 check_capacity_destination(destination_domain) 44 ), 45 destination_domain 46 ) 47 notify(test_link(link)) 48 49 50 link = link() 这个DAG应根据业务的数据治理需求进行定制。业务可能需要在工作流程中添加额外的任务,比如捕获要发送到Prometheus的度量日志,或在可以自动完成的情况下添加更多的容量。更重要的是,需要进行OpenLineage调用,将消费域追加到谱系图的末尾。 在Airflow中,DAG也可以通过事件(称为Sensors)触发启动,或者从微服务中调用,传递DAG所需的所有参数。尽管流式数据网格专注于实时用例,但DAG本身不需要实时运行,可以具有批处理语义,特别是如果工作流需要人工干预,如通知和审批。这些人工干预很可能会在第二天进行。实施用于发布数据产品的DAG另一个重要的可视化DAG是用于发布流式数据产品的DAG。这个示例再次帮助可视化正确执行此操作所需的步骤。在图7-17中,这个工作流逻辑可能变得更加复杂。考虑将其拆分为多个DAG以简化可视化。在这个DAG中,重要的任务是设置日志记录和构建AsyncAPI YAML文档。它足够详细,可以帮助轻松查找任何任务中的问题。基础设施即代码(IaC)另外,您可以在我们的DAG中加入调用Terraform或Ansible等工具的代码,这些工具可以构建基础设施。这些工具用于简化供应和弹性扩展(向上或向下扩展)的工作。同样,像Kubernetes这样的框架可以帮助简化与控制平面相关的一些任务。一些示例包括部署流式平台、流处理平台和Kafka Connect。它们可以添加额外的代理来增加流式平台的容量,或者向Kafka Connect集群添加更多的Connect工作者来处理更大的负载。构建这些工具的混合体可能有助于供应基础设施。总结本章重点介绍了如何构建数据平面、控制平面(包括其他子平面)、自助服务平面和管理平面。我们还提供了一些应用程序和工具,以便轻松实现这一目标。我们并没有固守解决方案中的任何产品,它们只是用来识别构建良好控制平面的组成部分的方式。您最终使用的工具希望能够满足您所替代的功能。本章在很大程度上依赖于在Kafka集群(或您选择的流式平台)之间复制数据的能力。本章中的图表暗示了使用Confluent提供的集群链接来在集群之间复制/镜像Kafka主题。您也可以使用MirrorMaker 2(MM2)来实现这一点,它是一个在Kafka集群之间进行数据复制的开源工具。其他流式平台可能会有自己的解决方案来复制流式数据,这可能包括分离存储层或使用分层存储。这些细节超出了本书的范围。中央团队需要在编码和基础设施方面具备更深入的技能。之前在单体数据湖或数据仓库中工作的数据工程师非常适合转向中央团队。他们应该具备Python、Java/Scala、Kubernetes、Terraform和构建微服务等技能。在第8章中,我们将提供建立一个数据团队的指导,以帮助您创建一个良好的流数据网格。
0
0
0
浏览量767
攻城狮无远

第一章:现代化您的数据平台:入门概览

数据是一项宝贵的资产,可以帮助您的公司做出更明智的决策,发现新的机会,并改进业务运营。谷歌在2013年启动了一项战略项目,旨在通过提高管理质量来提高员工留任率。即使像管理技能这样宽泛的概念也可以以数据驱动的方式进行研究。通过分析1万份绩效评价,识别高绩效经理的共同行为,并创建培训计划,谷歌成功将管理青睐度从83%提高到88%。亚马逊也进行了一项战略性的数据项目。这家电商巨头实施了一个基于客户行为的推荐系统,该系统在2017年推动了35%的购买。旧金山的篮球队勇士队是另一个例子;他们实施了一个分析计划,帮助他们一举成为联赛的佼佼者。这些例子——员工留任、产品推荐、提高胜率——都是通过现代数据分析实现的业务目标。要成为一家数据驱动型公司,您需要构建一个用于数据分析、处理和洞察的生态系统。这是因为有许多不同类型的应用程序(网站、仪表板、移动应用程序、机器学习模型、分布式设备等)创建和使用数据。公司内部还有许多不同的部门(财务、销售、营销、运营、物流等)需要数据驱动的洞察。由于整个公司都是您的客户群体,构建数据平台不仅仅是一个IT项目。本章介绍了数据平台及其要求,以及为什么传统数据架构不足以满足需求。它还讨论了数据分析和人工智能的技术趋势,以及如何利用公共云构建未来的数据平台。本章是对本书其余部分更详细涵盖的核心主题的概括。数据生命周期数据平台的目的是支持组织从原始数据到见解信息的过程。了解数据生命周期的步骤(收集、存储、处理、可视化、激活)是有帮助的,因为它们几乎可以直接映射到数据架构,从而创建一个统一的分析平台。通往智慧的旅程数据帮助公司开发更智能的产品,触达更多客户,并提高投资回报(ROI)。数据还可以用于衡量客户满意度、盈利能力和成本。但是单独的数据是不够的。数据是一种原材料,需要经过一系列阶段才能用于生成见解和知识。这一系列阶段就是我们所说的数据生命周期。尽管文献中有许多定义,但从一个一般的角度来看,我们可以确定现代数据平台架构中的五个主要阶段:收集 数据必须被获取并注入到目标系统中(例如,手动数据录入、批量加载、流式摄取等)。存储 数据需要以可持续的方式存储,并且能够在未来轻松访问(例如,文件存储系统、数据库)。处理/转换 数据必须被操纵以使其对后续步骤有用(例如,清理、整理、转换)。分析/可视化 数据需要被研究,通过手动加工(例如,查询、切片和切块)或自动处理(例如,使用ML应用程序编程接口进行丰富)来得出业务见解。激活 在形式和位置上展示数据洞察,使决策能够被做出(例如,作为特定手动操作的触发器的通知,当满足特定条件时自动执行的作业,向设备发送反馈的ML模型)。这些阶段中的每一个都向下一个阶段提供输入,类似于水通过一组管道流动。水管类比为了更好地理解数据生命周期,可以将其想象成一个简化的水管系统。水源始于一个引水渠,然后通过一系列管道进行传输和转化,最终到达一组房屋。数据生命周期类似,数据在被收集、存储、处理/转换和分析之前会经过一系列步骤,最终用于做出决策(见图1-1)。你可以看到管道世界和数据世界之间存在一些相似之处。给水工程师就像数据工程师,他们设计和构建能够使数据可用的系统。分析水样本的人类似于数据分析师和数据科学家,他们分析数据以发现见解。当然,这只是一个简化。公司中还有许多其他角色使用数据,如高管、开发人员、业务用户和安全管理员。但这个类比可以帮助你记住主要的概念。在图1-2中展示的经典数据生命周期中,数据工程师收集并将数据存储在一个分析存储中。然后使用各种工具处理存储的数据。如果工具涉及编程,处理通常由数据工程师完成。如果工具是声明性的,处理通常由数据分析师完成。处理后的数据然后由业务用户和数据科学家进行分析。业务用户利用这些见解做出决策,例如启动营销活动或发起退款。数据科学家使用数据训练ML模型,这些模型可以用于自动化任务或进行预测。现实世界可能与前述对现代数据平台架构和角色如何运作的理想化描述有所不同。阶段可能被合并(例如,存储和处理)或重新排序(例如,在ETL [抽取-转换-加载]中处理在存储之前,而不是在ELT [抽取-加载-转换]中存储在处理之前)。然而,这些变化存在一些权衡。例如,将存储和处理合并为一个阶段会导致耦合,从而导致资源浪费(如果数据量增长,您将需要扩展存储和计算)和可扩展性问题(如果您的基础设施无法处理额外的负载,您将陷入困境)。既然我们已经定义了数据生命周期并总结了数据从原始数据收集到激活的旅程的各个阶段,让我们逐个深入了解数据生命周期的五个阶段。采集设计过程中的第一步是摄取。摄取是将数据从源传输到目标系统的过程,源可以是任何地方(本地、设备上、另一个云中等),以便将其存储以供进一步分析。这是考虑大数据的3V的第一个机会:Volume(容量) 数据的大小是多少?通常在处理大数据时,这意味着以TB(千兆字节)或PB(拍字节)为单位的数据。Velocity(速度) 数据输入的速度是多少?通常以MB/s(兆字节/秒)或TB/day(千兆字节/天)为单位。这通常被称为吞吐量。Variety(多样性) 数据的格式是什么?表格、平面文件、图像、声音、文本等。识别要收集的数据的数据类型(结构化、半结构化、非结构化)、格式和生成频率(连续或在特定间隔)等。根据数据的速度以及数据平台处理所需的体积和多样性的能力,选择批处理摄取、流摄取或两者的混合。由于组织的不同部分可能对不同的数据源感兴趣,因此设计此阶段时要尽量灵活。有几种商业和开源解决方案可供使用,每种都专门针对先前提到的特定数据类型/方法。您的数据平台将需要全面支持所有需要摄取到平台的数据所需的容量、速度和多样性的整个范围。您可以使用简单的工具定期在文件传输协议(FTP)服务器之间传输文件,也可以使用复杂的系统,甚至是地理分布的系统,实时从物联网(IoT)设备收集数据。存储在这一步骤中,存储您在前一步中收集的原始数据。您不对数据进行任何更改,只是存储它。这很重要,因为您可能希望以不同的方式重新处理数据,而为此您需要有原始数据。数据有很多不同的形式和大小。您存储数据的方式将取决于您的技术和商业需求。一些常见的选择包括对象存储系统、关系数据库管理系统(RDBMS)、数据仓库(DWH)和数据湖。您的选择在某种程度上将受到底层硬件、软件和工件是否能够满足您所期望的用例所施加的可扩展性、成本、可用性、耐久性和开放性要求的影响。可扩展性可扩展性是以一种有能力的方式增长并管理增加的需求。实现可扩展性有两种主要方法:纵向扩展 这涉及向同一节点添加额外的扩展单元,以增加存储系统的容量。横向扩展 这涉及添加一个或多个额外的节点,而不是向单个节点添加新的扩展单元。这种类型的分布式存储更加复杂,但它可以实现更好的性能和效率。极其重要的是,底层系统能够处理现代解决方案所需的体积和速度,这些解决方案必须在数据爆炸的环境中工作,其性质正在从批处理过渡到实时:我们生活在一个大多数人持续生成并要求通过智能设备访问信息的世界中;组织需要能够为他们的用户(内部和外部)提供能够对各种请求进行实时响应的解决方案。性能与成本识别您需要管理的不同类型的数据,并根据数据的业务重要性、访问频率以及数据使用者对延迟的期望创建一个层次结构。将最重要且访问频率最高的数据(热数据)存储在高性能存储系统中,例如数据仓库的本地存储。将不太重要的数据(冷数据)存储在成本较低的存储系统中,例如云存储(它本身有多个层次)。如果您需要更高的性能,比如用于交互式用例,您可以使用缓存技术将热数据的有意义部分加载到一个易失性存储层中。高可用性高可用性意味着在请求时具有运行和提供对数据的访问的能力。通常通过硬件冗余来实现,以应对可能的物理故障/中断。在云中,通过将数据存储在至少三个可用性区域来实现这一目标。这些区域可能并非物理上分开(即它们可能在同一个“园区”),但往往会有不同的电源等。硬件冗余通常被称为系统正常运行时间,现代系统通常具有四个9或更多的正常运行时间。耐久性耐久性是在长期内存储数据而不会遭受数据退化、损坏或彻底丢失的能力。通常通过在物理上分开的位置存储数据的多个副本来实现。在云中,通过将数据存储在至少两个区域(例如在伦敦和法兰克福)来实现这一目标。在面对自然灾害时,这在处理数据恢复操作时变得极为重要:如果底层存储系统具有很高的耐久性(现代系统通常具有11个9),那么除非发生灾难性事件使得即使是物理上分开的数据中心也宕机,否则所有数据都可以恢复而不会出现问题。开放性尽量使用非专有且不会造成封锁效应的格式。理想情况下,应该能够使用多种处理引擎查询数据,而无需生成数据的副本或将其从一个系统移动到另一个系统。也就是说,可以使用使用专有或本地存储格式的系统,只要它们提供方便的导出功能。与大多数技术决策一样,开放性是一种权衡,专有技术的回报率可能足够高,以至于您愿意承担封锁的代价。毕竟,转向云的原因之一是为了降低运营成本,这些成本优势在完全托管/无服务器系统中往往比在托管的开源系统中更高。例如,如果您的数据用例需要事务,Databricks(使用基于Parquet的准开放存储格式Delta Lake)的运营成本可能比Amazon EMR或Google Dataproc低(后两者将数据分别存储在S3或Google Cloud Storage [GCS]中的标准Parquet中)- Databricks在Delta Lake中提供的ACID(原子性、一致性、隔离性、耐久性)事务在EMR或Dataproc上实施和维护将是昂贵的。如果以后需要迁出Databricks,可以将数据导出为标准Parquet格式。开放性本身并不是拒绝更适合的技术的理由。处理/转换这是魔术发生的地方:原始数据被转化为可供进一步分析的有用信息。这是数据工程师构建数据管道的阶段,以使数据以有意义的方式对更广泛的非技术用户可访问。该阶段包括为分析和使用准备数据的活动。数据集成涉及将来自多个来源的数据合并为单一视图。可能需要进行数据清理,以从数据中删除重复项和错误。更一般地说,进行数据整理、处理和转换以将数据组织成标准格式。有几个框架可以使用,每个框架都具有依赖于前一步中选择的存储方法的功能。一般来说,允许您使用纯SQL命令查询和转换数据的引擎(例如AWS Athena、Google BigQuery、Azure DWH和Snowflake)是效率最高、成本最低且易于使用的。然而,与基于现代编程语言(通常是Java、Scala或Python)的引擎相比,它们提供的功能有限(例如,在Amazon EMR、Google Cloud Dataproc/Dataflow、Azure HDInsight和Databricks上运行的Apache Spark、Apache Flink或Apache Beam)。基于代码的数据处理引擎不仅可以实现更复杂的批处理和实时转换以及机器学习,还可以利用其他重要功能,如适当的单元测试和集成测试。在选择适当的引擎时,另一个考虑因素是SQL技能在组织中通常比编程技能更为普遍。您想要在组织内建立更多的数据文化,就越应该倾向于使用SQL进行数据处理。如果处理步骤(例如数据清理或转换)需要领域知识,这一点尤为重要。在这个阶段,还可以使用数据虚拟化解决方案,该解决方案抽象了多个数据源以及管理它们的相关逻辑,使信息直接对最终用户进行分析。在本书中,我们将不再讨论虚拟化,因为它往往是在构建一个完全灵活的平台的路上的一种权宜之计。有关数据虚拟化的更多信息,建议参阅Sandeep Uttamchandani(O'Reilly)的《自助数据路线图》一书的第10章。分析/可视化一旦进入这个阶段,数据最终开始具有独立的价值——您可以将其视为信息。用户可以利用多种工具深入挖掘数据的内容,提取有用的见解,识别当前趋势,并预测新的结果。在这个阶段,可视化工具和技术(例如图表、图形、地图、热力图等)发挥着重要作用,因为它们提供了一种简单的方式来发现和评估趋势、异常值、模式和行为。数据的可视化和分析可以由多种类型的用户执行。一方面,有人对理解业务数据感兴趣,希望利用图形工具执行常见的分析,如切片和切块汇总和假设分析。另一方面,可能有更高级的用户(“高级用户”),他们希望利用类似SQL的查询语言的强大功能进行更精细和定制的分析。此外,可能还有数据科学家,他们可以利用机器学习技术从数据中提取有意义的见解,发现模式和相关性,提高对客户的理解和定位,从而提高企业的收入、增长和市场地位。激活这是最终用户能够根据数据分析和机器学习预测做出决策的步骤,从而实现数据决策过程。从提取或预测的见解中,现在是采取一些行动的时候。可以执行的行动分为三类:自动操作 自动化系统可以使用推荐系统的结果向客户提供定制推荐,从而通过增加销售额帮助业务的顶线。SaaS集成 通过与第三方服务集成可以执行操作。例如,一家公司可能会实施一项营销活动,试图降低客户流失率。他们可以分析数据并实施倾向模型,以识别可能对新商业报价做出积极响应的客户。然后,客户电子邮件地址列表可以自动发送到营销工具,以启动活动。警报 您可以创建实时监视数据的应用程序,并在满足某些条件时发送个性化消息。例如,定价团队可能在访问某个商品列表页面的流量超过某个阈值时接收到主动通知,从而使他们可以检查商品是否定价正确。这三种场景的技术堆栈是不同的。对于自动操作,ML模型的“训练”通常定期进行,通常通过安排端到端的ML管道来完成(这将在第11章中介绍)。预测本身是通过调用部署为Web服务的ML模型实现的,使用类似AWS SageMaker、Google Cloud Vertex AI或Azure Machine Learning的工具。 SaaS集成通常在特定功能的工作流工具的上下文中进行,该工具允许人类控制检索的信息、转换的方式以及激活的方式。此外,使用大型语言模型(LLM)及其生成能力(我们将在第10章更深入地探讨这些概念)可以通过与核心系统紧密集成来帮助自动化重复性任务。警报是通过编排工具(例如Apache Airflow)、事件系统(例如Google Eventarc)或无服务器函数(例如AWS Lambda)来实现的。在本节中,我们已经了解了现代数据平台需要支持的活动。接下来,让我们查看在实现分析和AI平台方面的传统方法,以更好地理解技术的演变以及为什么云方法可能产生重大差异。传统方法的局限性传统上,组织的数据生态系统包括用于提供不同数据服务的独立解决方案。不幸的是,这些专用的数据存储解决方案有时可能规模庞大,可能导致组织内部形成信息孤岛。由此产生的信息孤岛系统是独立的解决方案,它们无法以高效的方式协同工作。信息孤岛中的数据是沉默的数据,从中难以获取洞察。为了拓宽和统一企业智能,跨业务部门安全共享数据至关重要。如果大部分解决方案都是定制构建的,那么处理可伸缩性、业务连续性和灾难恢复(DR)就变得困难。如果组织的每个部分都选择在不同的环境中构建其解决方案,复杂性将变得令人不堪重负。在这种情况下,很难确保隐私或审计数据的更改。一种解决方案是开发一个统一的数据平台,更确切地说,是一个云数据平台(请注意,统一并不一定意味着集中,稍后将讨论)。数据平台的目的是允许在整个组织的所有数据上以一致、可扩展和可靠的方式进行分析和机器学习。在这样做时,您应该尽可能地利用托管服务,以便组织可以专注于业务需求而不是操作基础架构。基础设施的运营和维护应完全委托给底层的云平台。在本书中,我们将介绍在开发一个可扩展和可靠的平台来整合跨业务部门的数据时,您需要做出的核心决策。反模式:通过ETL打破信息孤岛组织很难对其数据实现统一视图,因为它们往往使用多种解决方案来管理数据。组织通常通过使用数据移动工具来解决这个问题。ETL(抽取、转换、加载)应用程序允许在不同系统之间转换和传输数据,以创建一个单一的真相源。然而,依赖ETL存在问题,现代平台中有更好的解决方案可用。通常,会定期使用ETL工具从事务性数据库中提取最新的交易并将其存储在分析存储中,以供仪表板访问。然后进行标准化。为了进行分析而对每个数据库表创建ETL工具,这样就可以在不必每次都访问源系统的情况下执行分析(见图1-3)。跨组织捕获所有数据的中央分析存储,根据使用的技术而定,被称为DWH或数据湖。这两种方法之间的高级区别基于系统内部存储数据的方式:如果分析存储支持SQL并包含经过管控的、经过质量控制的数据,则被称为DWH。相反,如果它支持Apache生态系统中的工具(如Apache Spark)并包含原始数据,则被称为数据湖。有关称为介于两者之间的分析存储(例如经管控的原始数据或未管控的质量受控数据)的术语因组织而异,有些组织称其为数据湖,而其他组织则称其为DWH。正如您将在本书后面看到的,这种混乱的词汇不是问题,因为数据湖(第5章)和DWH(第6章)方法正在汇聚成为被称为数据湖仓(第7章)的东西。依赖数据移动工具来尝试构建数据的一致视图存在一些缺点:数据质量:ETL工具通常由数据的使用者编写,这些使用者往往对数据的了解程度不及数据的所有者。这意味着很多时候提取的数据不是正确的数据。延迟:ETL工具引入了延迟。例如,如果用于提取最近交易的ETL工具每小时运行一次,并且运行需要15分钟,那么分析存储中的数据可能在75分钟内变得陈旧。通过流式ETL可以解决这个问题,其中事件在发生时即时处理。瓶颈:ETL工具通常涉及编程技能。因此,组织建立了专门的数据工程团队来为ETL编写代码。随着组织内数据的多样性增加,需要编写越来越多的ETL工具。数据工程团队成为组织利用数据能力的瓶颈。维护:ETL工具需要由系统管理员定期运行和进行故障排除。底层基础设施系统需要持续更新,以应对增加的计算和存储容量,并确保可靠性。变更管理:输入表模式的更改要求更改ETL工具的提取代码。这使得变更变得困难,或者导致ETL工具被上游更改破坏。数据缺失:很可能必须将许多错误升级到数据的所有者、ETL工具的创建者或数据的使用者。这增加了维护开销,并且很多时候工具停机时间较长。由于这个原因,数据记录中通常存在很大的缺陷。管理:随着ETL流程的增多,越来越有可能由不同的流程执行相同的处理,从而导致相同信息的多个源。随着时间的推移,这些流程通常会发散,以满足不同的需求,导致为不同的决策使用不一致的数据。效率和环境影响:支持这些类型转换的基础设施是一个问题,因为它通常是全天候运行的,产生了显着的成本,并增加了碳足迹的影响。前面列表中的第一点(数据质量)经常被忽视,但随着时间的推移,它往往是最重要的。通常需要在数据“可信”可用于生产之前对数据进行预处理。来自上游系统的数据通常被认为是原始的,如果没有得到适当的清理和转换,它可能包含噪音甚至是错误的信息。必须专门为手头的任务构建数据处理工具。在考虑一个数据源时,这种情况是合理的,但总的收集(见图1-4)导致了混乱。存储系统的泛滥,加上为满足不同下游应用程序的需求而开发的定制数据管理解决方案,导致分析主管和首席信息官(CIO)面临以下挑战:他们的DWH/数据湖无法满足不断增长的业务需求。日益增长的数字化计划(以及与数字原生企业的竞争)已经将业务转变为系统中涌入大量数据的业务。为不同的数据科学任务创建单独的数据湖、DWH和特殊存储最终会创建多个数据孤岛。由于性能、安全性和治理方面的挑战,数据访问需要受到限制或受到限制。由于需要更新许可证并支付昂贵的支持资源,因此变得具有挑战性。很明显,这种方法无法满足新的业务需求,不仅因为技术复杂性,还因为这种模型涉及的安全性和治理要求。反模式:集中控制为了解决数据分散、分散并通过特定任务的数据处理解决方案进行管理的问题,一些组织尝试将一切都集中到由IT部门控制的单一的、单片的平台中。如图1-5所示,底层技术解决方案并没有改变,而是通过将问题交给一个组织来解决,使问题更容易解决。由一个独特部门实施的这种集中控制方式带来了自己的挑战和权衡。所有业务单元(BUs)— IT 本身、数据分析和业务用户在 IT 控制所有数据系统时都会遇到困难:IT IT 部门面临的挑战是这些数据孤岛涉及的各种技术。IT 部门很少拥有管理所有这些系统所需的所有技能。数据存储在本地和云端的多个存储系统中,管理 DWH、数据湖和数据集市的成本较高。跨不同来源定义安全性、治理、审计等内容也并不总是清晰。此外,这引入了一个获取数据访问权限的可伸缩性问题:IT 需要执行的工作量随着将作为一部分图像的源系统和目标系统的数量的增加而线性增加,因为这肯定会增加所有相关利益相关者/业务用户的数据访问请求。分析 阻碍有效分析流程的主要问题之一是无法访问正确的数据。当存在多个系统时,将数据移动到/从单片数据系统变得昂贵,导致不必要的 ETL 任务等。此外,预先准备好并随时可用的数据可能没有最新的源,或者可能存在提供更深度和更广泛信息的其他版本的数据,例如拥有更多列或更精细的记录。由于数据治理和运营问题,不可能让您的分析团队自由发挥,每个人都可以访问所有数据。组织通常最终会通过限制数据访问来换取分析灵活性。业务 获取业务可以信任的数据和分析结果是困难的。围绕限制您向业务提供的数据存在问题,以便确保最高质量。替代方法是开放访问业务用户所需的所有数据,即使这意味着牺牲质量。然后,挑战就变成了在数据质量和可信赖数据量之间取得平衡。往往情况是 IT 没有足够的合格的业务代表来推动优先事项和要求。这很快可能成为组织创新过程中的一个拖慢速度的瓶颈。尽管存在这么多的挑战,一些组织在多年来采用了这种方法,导致了在获取他们需要履行任务的数据方面受到延迟的业务用户的挫败和紧张。受挫的业务单元通常通过另一种反模式来应对,即影子 IT——整个部门开发和部署有用的解决方案以绕过这些限制,但最终使数据孤立问题变得更糟。有时会采用一种称为数据布局的技术方法。这仍然依赖于集中化,但数据布局是一个虚拟层,提供统一的数据访问。问题在于这样的标准化可能是一个沉重的负担,并且对于组织范围内对数据的访问来说可能会引入延迟。然而,数据布局对于试图访问客户专有数据的 SaaS 产品是一种可行的方法——集成专家提供了从客户模式到 SaaS 工具期望的模式的必要翻译。反模式:数据集市和 Hadoop由于围绕一个独立管理的系统存在问题,为了解决这个问题,一些企业采用了另外两种反模式:数据集市和无治理的数据湖。 在第一种方法中,数据被提取到本地关系型和分析数据库中。然而,尽管被称为数据仓库,但由于可伸缩性约束,这些产品实际上是数据集市(适用于特定工作负载的企业数据子集)。数据集市允许业务用户设计和部署自己的业务数据到结构化的数据模型中(例如,零售、医疗保健、银行、保险等领域)。这使得他们能够轻松获取关于当前和历史业务的信息(例如,上个季度收入金额、上周玩过你上次发布的游戏的用户数量、网站帮助中心停留时间与过去六个月收到的工单数量之间的相关性等)。几十年来,组织一直在使用各种技术(例如 Oracle、Teradata、Vertica)开发数据集市解决方案,并在其上实施多个应用程序。然而,这些本地技术在容量方面受到严重限制。IT 团队和数据利益相关者面临着扩展基础设施(纵向)的挑战,寻找关键人才,降低成本,最终满足提供有价值的见解的预期增长。此外,由于数据规模增长,这些解决方案往往成本高昂,因为您需要获得更多计算能力来处理它。由于可伸缩性和成本问题,基于 Apache Hadoop 生态系统的大数据解决方案应运而生。Hadoop 引入了使用低成本通用服务器进行分布式数据处理(横向扩展)的概念,使以前只能通过高端(非常昂贵)专用硬件实现的用例成为可能。在 Hadoop 顶部运行的每个应用程序都被设计为容忍节点故障,使其成为某些传统 DWH 工作负载的经济有效替代方案。这导致了一个称为数据湖的新概念的发展,迅速成为数据管理的核心支柱,与 DWH 一起。其思想是,在核心运营技术部门继续执行日常任务的同时,所有数据都被导出到一个集中的数据湖中进行分析。数据湖旨在成为分析工作负载和业务用户的中央存储库。数据湖已经从仅用于原始数据的存储设施发展为支持大量数据上进行高级分析和数据科学的平台。这使得整个组织能够进行自助式分析,但需要对高级 Hadoop 和工程流程有广泛的工作知识才能访问数据。与组织数据使用者无法理解的无治理数据混乱相结合,技能差距和数据质量问题意味着企业很难从本地数据湖中获得良好的投资回报。现在您已经了解了几种反模式,让我们专注于如何设计一个数据平台,以提供对整个生命周期内的数据的统一视图。创建一个统一的分析平台数据集市和数据湖技术使IT能够构建第一代数据平台,打破数据孤岛,使组织能够从其所有数据资产中获取洞见。数据平台使数据分析师、数据工程师、数据科学家、业务用户、架构师和安全工程师能够更好地实时洞察数据,并预测业务随时间的发展。选择云而非本地部署DWH(数据仓库)和数据湖是现代数据平台的核心。DWH支持结构化数据和SQL,而数据湖支持原始数据和Apache生态系统中的编程框架。然而,在本地环境中运行DWH和数据湖存在一些固有的挑战,例如扩展和运营成本。这促使组织重新考虑其方法,并开始将云(特别是公共云)视为首选环境。为什么呢?因为它允许它们:通过利用新的定价模型(按使用量付费模型)降低成本通过利用最先进的技术加快创新速度使用“突发”方法扩展本地资源通过在多个区域和地区存储数据来规划业务连续性和灾难恢复利用完全托管的服务自动管理灾难恢复当用户不再受到基础设施容量的限制时,组织能够在其整个组织中实现数据的民主化并解锁洞察。云支持组织进行现代化改造,因为它通过卸载行政、低价值任务来最小化琐碎和摩擦。云数据平台承诺提供一个环境,在这里,您不再需要妥协,可以构建一个涵盖从数据收集到服务的端到端数据管理和处理阶段的全面数据生态系统。您可以使用云数据平台以多种格式存储大量数据,而无需妥协延迟。云数据平台承诺:集中的治理和访问管理增加生产力和降低运营成本在整个组织中更大范围的数据共享不同角色的扩展访问访问数据时延迟减少在公共云环境中,DWH和数据湖技术之间的界限变得模糊,因为云基础设施(具体而言,计算和存储的分离)实现了在本地环境中不可能的融合。今天可以将SQL应用于存储在数据湖中的数据,并且可以在DWH中存储的数据上运行传统的Hadoop技术(例如Spark)。在本节中,我们将为您介绍这种融合的基本原理,以及它如何成为可以彻底改变组织对数据看法的全新方法的基础;在第5到7章中,您将获得更多详细信息。数据集市和数据湖的缺点在过去的40年中,IT部门已经意识到数据仓库(实际上是数据集市)很难管理,并且可能变得非常昂贵。在过去运作良好的传统系统(例如本地的 Teradata 和 Netezza 设备)已经被证明很难扩展,非常昂贵,并且涉及与数据新鲜度相关的一系列挑战。此外,它们无法轻松提供现代功能,如访问 AI/ML 或实时特性,而不是在事后添加该功能。数据仓库的用户通常是嵌入在特定业务部门的分析师。他们可能对附加数据集、分析、数据处理和商业智能功能有一些想法,这些功能对他们的工作非常有益。然而,在传统公司中,他们通常无法直接访问数据所有者,也无法轻松影响决定数据集和工具的技术决策者。此外,因为他们无法访问原始数据,所以无法测试假设或更深入地了解底层数据。数据湖并不像它们看起来那么简单和经济实惠。虽然它们理论上可以轻松扩展,但组织在规划和提供足够存储空间方面通常面临挑战,特别是如果它们产生高度变化的数据量。此外,在高峰期为计算能力进行规划可能很昂贵,导致不同业务部门之间争夺有限资源。本地数据湖可能很脆弱,并需要耗时的维护。本应该开发新功能的工程师通常被降级为维护数据集群和为业务部门调度作业。对于许多企业来说,总体拥有成本通常高于预期。简而言之,数据湖并不创造价值,许多企业发现其投资回报率为负。对于数据湖,治理并不容易解决,尤其是当组织的不同部分使用不同的安全模型时。然后,数据湖变得孤立和分段,使得难以在团队之间共享数据和模型。数据湖的用户通常更接近原始数据源,并且需要使用数据湖工具和功能的编程技能,即使只是为了探索数据。在传统组织中,这些用户倾向于专注于数据本身,并经常与业务的其他方面保持距离。另一方面,业务用户没有编程技能,无法从数据湖中获取洞见。这种脱节意味着业务部门错过了获取洞见的机会,从而推动其实现更高收入、降低成本、降低风险和开发新机会的业务目标。数据仓库(DWH)和数据湖的融合鉴于这些权衡,许多公司最终采用混合方法,其中设置了一个数据湖以将一些数据升级到数据仓库,或者数据仓库有一个附加的数据湖用于额外的测试和分析。然而,由于多个团队正在构建适应其个别需求的数据架构,对于中央 IT 团队来说,数据共享和保真度变得更加复杂。与其拥有各自目标的独立团队——一个探索业务,另一个了解业务——不如将这些功能及其数据系统统一起来,创造一个良性循环,其中对业务的深入理解推动探索,而探索又推动对业务的更深理解。基于这一原则,数据行业开始转向一种新方法,即“lakehouse” 和“数据网格”(data mesh),它们能够很好地协同工作,因为它们有助于解决组织内的两个不同挑战:Lakehouse 允许具有不同技能集(数据分析师和数据工程师)的用户使用不同的技术访问数据。数据网格允许企业创建一个统一的数据平台,而不是将所有数据都集中在 IT 部门——这样,不同的业务部门可以拥有自己的数据,但允许其他业务部门以高效、可扩展的方式访问它。作为额外的好处,这种架构组合还带来了更严格的数据治理,这是数据湖通常缺乏的。数据网格赋予人们避免被一个团队拖慢的权力,从而启用整个数据堆栈。它将隔间打破成一个个较小的组织单元,在提供以联邦方式访问数据的架构中。数据湖仓数据湖仓架构是数据湖和数据仓库关键优势的结合(见图1-6)。它提供了一种低成本的存储格式,可被各种处理引擎访问,例如数据仓库的 SQL 引擎,同时还提供了强大的管理和优化功能。Databricks支持湖仓架构,因为它是基于Spark创建的,需要支持不是程序员的业务用户。因此,Databricks中的数据存储在数据湖中,但业务用户可以使用SQL进行访问。然而,湖仓架构并不局限于Databricks。在像Google Cloud BigQuery、Snowflake或Azure Synapse等云解决方案中运行的数据仓库允许您创建基于列存储的湖仓架构,该架构针对SQL分析进行了优化:它允许您将数据仓库视为数据湖,同时还允许在并行Hadoop环境中运行的Spark作业利用存储系统上存储的数据,而无需单独的ETL过程或存储层。湖仓模式相对于传统方法提供了几个优势:存储和计算的解耦,实现了: 廉价、几乎无限且无缝可扩展的存储 无状态、具有弹性的计算 支持ACID的存储操作 逻辑数据库存储模型,而非物理存储数据治理(例如,数据访问限制和模式演变)通过与商业智能工具的本地集成支持数据分析支持数据湖方法的典型多版本方法(即,青铜、白银和黄金)使用Apache Parquet和Iceberg等开放格式的数据存储和管理支持结构化或非结构化格式的不同数据类型具有处理数据实时分析的流式功能实现从商业智能到机器学习等各种工作负载的多样化应用然而,湖仓不可避免地是一种技术上的妥协。在云存储中使用标准格式限制了数据仓库多年来一直在完善的存储优化和查询并发性。因此,湖仓技术支持的SQL不如本机数据仓库的效率高(即,它需要更多的资源并且成本更高)。此外,SQL支持通常有限,不支持或效率极低的功能,例如地理空间查询、机器学习和数据操作。类似地,数据仓库提供的Spark支持有限,通常不如数据湖供应商提供的本机Spark支持高效。湖仓方法使组织能够实施一个支持任何类型工作负载的非常多样化数据平台的核心支柱。但是对于位于其之上的组织来说呢?用户如何充分利用平台的优势来执行他们的任务呢?在这种情况下,有一个新的运营模型正在形成,那就是数据网格。数据网格数据网格是一个技术、人员和流程的分散型运营模型,旨在解决分析中最常见的挑战之一——即在数据所有权必然分散的环境中对控制权进行集中的愿望,如图1-7所示。看待数据网格的另一种方式是,它引入了一种将数据视为自包含产品而非ETL流水线产品的方式。在这种方法中,分布式团队拥有数据生成并通过明确定义的数据模式为内部/外部消费者提供服务。总体而言,数据网格建立在数据仓库和数据湖领域的创新基础之上,结合了与公共云中数据仓库技术相关的可扩展性、按消费付费模型、自助API和紧密集成。 通过这种方法,您可以有效地创建按需数据解决方案。数据网格将数据所有权分散给领域数据所有者,每个领域数据所有者都负责以标准方式提供其数据作为产品(见图1-8)。数据网格还支持组织内各个部分之间的通信,以在不同位置分发数据集。 在数据网格中,从数据中提取价值的责任被委托给最了解该数据的人;换句话说,创建数据或将其引入组织的人必须负责从他们创建的数据中创建可消耗的数据资产,将其作为数据产品。在许多组织中,由于在整个组织中反复提取和转换数据而没有对新创建的数据明确的所有权责任,建立“单一真相”或“权威数据源”变得棘手。在数据网格中,权威数据源是源领域发布的数据产品,有明确定义的数据所有者和数据监护人负责该数据。从技术角度(lakehouse)和组织角度(数据网格)都能够访问这一统一视图意味着人们和系统能够以最适合其需求的方式获取数据。在某些情况下,这种体系结构必须跨越多个环境,从而产生了非常复杂的体系结构。让我们看看公司如何管理这一挑战。混合云在设计云数据平台时,可能一个单一的环境无法完全管理工作负载。这可能是由于法规约束(即,您无法将数据移动到组织边界之外的环境),或者由于成本(例如,组织在基础设施上进行了一些未达到寿命周期的投资),或者因为您需要的某种技术在云中不可用。在这种情况下,采用混合模式可能是一种可能的方法。混合模式是一种应用程序在各种环境中运行的模式。混合模式的最常见示例是将私有计算环境(如本地数据中心)与公共云计算环境结合在一起。在本节中,我们将解释这种方法在企业中的运作方式。混合云是必要的原因混合云方法被广泛采用,因为几乎没有大型企业完全依赖公共云。在过去几十年里,许多组织已经投资了数百万美元和数千小时用于本地基础设施。几乎所有组织都在运行一些传统的架构和业务关键的应用程序,它们可能无法迁移到公共云。由于法规或组织约束,它们可能还有无法在公共云中存储敏感数据的情况。允许工作负载在公共云和私有云环境之间迁移提供了更高级别的灵活性和数据部署的额外选择。有几个原因推动混合(即跨越本地、公共云和边缘的体系结构)和多云(即跨越多个公共云供应商,如AWS、Microsoft Azure和Google Cloud Platform [GCP]等)的采用。以下是选择混合和/或多云的一些关键业务原因:数据驻地法规: 由于一些行业或地区对数据存储位置有严格的法规要求,一些组织可能永远不会完全迁移到公共云。这也适用于那些在没有公共云存在和数据驻地要求的国家进行工作负载的情况。遗留投资: 一些客户希望保护他们的遗留工作负载,如SAP、Oracle或Informatica在本地的情况,但又希望利用公共云创新,比如Databricks和Snowflake等。过渡: 大型企业通常需要多年的过渡过程,将其现代化为云原生应用程序和架构。他们将不得不在多年的时间里采用混合架构作为中间状态。云突发: 有些客户主要在本地,不想迁移到公共云。然而,由于临时大批量作业、繁忙时段的突发流量或大规模机器学习训练作业,他们在满足业务服务水平协议(SLA)方面面临挑战。他们希望利用公共云中可扩展的容量或自定义硬件,避免在本地基础设施上进行规模扩大的成本。采用“本地优先”计算方法的解决方案,比如MotherDuck,正在变得流行。最佳选择: 一些组织选择在不同的公共云提供商中执行不同的任务,这是一种有意的策略,选择最能满足其需求的技术。例如,Uber使用AWS为其Web应用提供服务,但在Google Cloud上使用Cloud Spanner来运行其履行平台。Twitter在AWS上运行其新闻提要,但在Google Cloud上运行其数据平台。既然你了解了选择混合解决方案的原因,让我们看看在使用这种模式时可能面临的主要挑战;这些挑战是混合应该被视为一种例外的原因,目标应该是云原生。混合云的挑战企业在实施混合或多云架构时面临一些挑战:治理 在多个环境中应用一致的治理政策很难。例如,在本地环境和公共云之间通常以不同方式处理合规性安全策略。通常,在本地和云中数据的部分重复。想象一下,如果组织正在运行财务报告,如果数据存在于多个平台上,如何保证使用的数据是最新更新的副本呢?访问控制 用户访问控制和策略在本地和公共云环境之间有所不同。云服务提供商为其提供的服务有自己的用户访问控制(称为身份和访问管理,IAM),而本地使用诸如本地目录访问协议(LDAP)或Kerberos等技术。如何保持它们同步或在不同环境之间拥有单一的控制平面呢?工作负载互操作性 在跨越多个系统时,不可避免地会有需要管理的不一致的运行时环境。数据移动 如果本地和云应用程序都需要访问某些数据,那么这两个数据集必须保持同步。在多个系统之间移动数据成本高昂——需要人力成本来创建和管理管道,可能由于使用的软件而产生许可成本,最后但同样重要的是,它会消耗计算、网络和存储等系统资源。组织如何处理来自多个环境的成本?如何连接在各种环境中孤立的异构数据?在连接过程的结果中,数据最终存储在何处?技能集 拥有两个云(或本地和云)意味着团队必须了解并建立在两个环境中的专业知识。由于公共云是一个快速发展的环境,因此在一个云中提升员工的技能,更不用说在两个云中了,都会产生显著的开销。技能集对于招聘系统集成商(SIs)也可能是一个挑战——尽管大多数大型SIs都有每个主要云的实践,但很少有团队了解两个或更多的云。随着时间的推移,我们预计雇佣愿意学习定制本地技术的人将变得越来越困难。经济学 数据分布在两个环境之间可能带来意想不到的成本:也许您在一个云中有数据,想要将其提供给另一个云,从而产生出站费用。尽管存在这些挑战,混合设置仍然是可行的。我们将在下一小节中看看如何解决这些问题。为什么混合云可以奏效云服务提供商意识到这些需求和挑战。因此,他们为混合环境提供了一些支持,主要分为三个方面:选择 云服务提供商通常对开源技术做出重大贡献。例如,尽管 Kubernetes 和 TensorFlow 是在 Google 开发的,但它们是开源的,因此在所有主要云平台上都存在用于托管执行环境的版本,甚至可以在本地环境中使用。灵活性 诸如 Databricks 和 Snowflake 的框架允许您在任何主要公共云平台上运行相同的软件。因此,团队可以学习一套在任何地方都适用的技能。需要注意的是,在多云环境中工作的工具所提供的灵活性并不意味着您已经摆脱了锁定。您将需要在(1)框架级别锁定和云级别灵活性之间进行选择(由 Databricks 或 Snowflake 等技术提供),以及(2)云级别锁定和框架级别灵活性之间进行选择(由云原生工具提供)。开放性 即使工具是专有的,由于采用了开放标准和导入/导出机制,其代码也以可移植的方式编写。因此,例如,即使 Redshift 只在 AWS 上运行,但查询是用标准 SQL 编写的,并且存在多个导入和导出机制。综合考虑,这些功能使得 Redshift、BigQuery 和 Synapse 成为开放平台。这种开放性允许使用 Teads 等用例,其中数据使用 AWS 上的 Kafka 进行收集,使用 Google Cloud 上的 Dataflow 和 BigQuery 进行聚合,然后写回到 AWS Redshift(见图1-9)。云服务提供商正在通过大力投资于开源项目,致力于提供选择、灵活性和开放性,以帮助客户在多个云中使用。因此,多云数据仓库或混合数据处理框架正在变为现实。因此,您可以构建更好的云软件生产、发布和管理,以实现您期望的混合和多云部署,而不是按照供应商的规定。边缘计算混合模式的另一种体现是当您希望计算能力跨越通常的数据平台边界,可能直接与某些连接设备进行交互。在这种情况下,我们谈论的是边缘计算。边缘计算将计算和数据存储更靠近生成数据并需要处理数据的系统。边缘计算的目标是提高响应时间并节省带宽。边缘计算可以解锁许多用例,并加速数字转型。它在许多应用领域都有广泛的应用,例如安全、机器人技术、预测性维护、智能车辆等。随着边缘计算的采用和走向主流,对于各行业来说有许多潜在的优势:更快的响应时间 在边缘计算中,数据存储和计算的能力被分布并在需要做出决策的地方提供。不需要往返到云端可以降低延迟,实现更快的响应。在预防性维护中,它有助于阻止关键机器操作的故障或危险事件的发生。在活动游戏中,边缘计算可以提供所需的毫秒级响应时间。在欺诈防范和安全场景中,它可以防范隐私泄漏和拒绝服务攻击。间歇性连接 在偏远资产(如油井、农田泵、太阳能场或风车)存在不可靠的互联网连接可能使监测这些资产变得困难。边缘设备在本地存储和处理数据的能力确保在互联网连接有限的情况下不会发生数据丢失或操作失败。安全性和合规性 边缘计算可以消除设备和云端之间的大量数据传输。可以在本地筛选敏感信息,只将关键的数据模型构建信息传输到云端。例如,在智能设备中,监听诸如“OK Google”或“Alexa”的语音处理可以在设备本身上进行。潜在的私人数据无需收集或发送到云端。这使用户能够构建适用于企业安全性和审计的适当安全和合规框架。经济实惠的解决方案 围绕物联网采用的一个实际关切是由于网络带宽、数据存储和计算能力而导致的前期成本。边缘计算可以在本地执行大量数据计算,使企业能够决定哪些服务在本地运行,哪些发送到云端,从而降低整体物联网解决方案的最终成本。这就是在类似 Rust 或 Go 等现代编译语言构建的 Open Neural Network Exchange(ONNX)格式中进行嵌入模型的低内存二进制部署可以发挥作用的地方。互操作性 边缘设备可以充当传统和现代机器之间的通信联络点。这使得传统工业机器能够连接到现代机器或物联网解决方案,并立即从传统或现代机器中获得洞察力。所有这些概念使得架构师在定义其数据平台时具有极大的灵活性。在第9章中,我们将更深入地探讨这些概念,并看到这种模式正在成为标准。应用人工智能许多组织因需要采用人工智能技术而被迫设计云数据平台——在设计数据平台时,确保其具备支持人工智能用例的未来性是非常重要的。考虑到人工智能对社会的巨大影响以及其在企业环境中的传播,让我们迅速深入了解如何在企业环境中实施人工智能。您将在第10章和第11章中找到更深入的讨论。机器学习如今,一种被称为监督机器学习的人工智能分支取得了巨大的成功,以至于人们更经常将术语“人工智能”用作该分支的总称。监督机器学习的工作原理是向计算机程序展示许多例子,其中正确答案(称为标签)是已知的。机器学习模型是一个标准算法(即完全相同的代码),具有可调参数,可以“学习”如何从提供的输入到标签。然后,这样一个学到的模型被部署用于对那些正确答案未知的输入进行决策。与专家系统不同,不需要通过编程显式地将规则传递给人工智能模型以做出决策。由于许多现实领域涉及到专家难以表达其逻辑的人类判断,让专家简单地为输入示例打标签比捕捉其逻辑更为可行。现代的国际象棋算法和医学诊断工具使用机器学习。国际象棋算法从人类过去玩过的游戏记录中学习,而医学诊断系统则通过专业医生为诊断数据打标签而学习。生成式人工智能是最近变得非常强大的人工智能/机器学习分支,它不仅能理解图像和文本,还能生成逼真的图像和文本。除了能够在营销等应用中创建新内容外,生成式人工智能还简化了机器与用户之间的交互。用户能够用自然语言提出问题并使用英语或其他语言自动执行许多操作,而无需了解编程语言。为了使这些机器学习方法运作,它们需要大量的训练数据和现成的定制硬件。因此,采用人工智能的组织通常从构建云数据/机器学习平台开始。机器学习的用途机器学习在工业界取得惊人的普及有几个关键原因:数据更容易获取。 获取标记数据比捕捉逻辑更容易。每一种人类推理都有难以编码的例外情况,这些例外情况会随着时间的推移而被编码。让一组眼科医生标记一千张图像比让他们描述如何识别血管出血更容易。重新训练更容易。 当机器学习用于推荐物品给用户或运行营销活动等系统时,用户行为会迅速适应变化。持续训练模型变得很重要。这在机器学习中是可能的,但使用代码要困难得多。更好的用户界面。 一类被称为深度学习的机器学习已被证明能够在非结构化数据(如图像、视频和自然语言文本)上进行训练。这些类型的输入通常很难进行编程。这使得您可以将真实世界的数据用作输入,比如考虑一下,当您只需拍摄支票的照片而无需在网页表单中键入所有信息时,存款检查的用户界面会变得更好。自动化。 机器学习模型理解非结构化数据的能力使得许多业务流程可以自动化。表单可以轻松数字化,仪表盘可以更容易读取,工厂车间可以更容易监控,因为可以自动处理自然语言文本、图像或视频。成本效益。 赋予机器理解和创建文本、图像、音乐和视频的机器学习API每次调用成本仅为几分之一,而雇佣人类执行这些任务的成本则高得多。这使得在推荐等情况下使用技术成为可能,而个人购物助手的成本则无法承受。协助。 生成式人工智能可以使开发人员、营销人员和其他白领工作者更加高效。编码助手和工作流合作伙伴能够简化许多企业职能的部分,比如发送定制的销售电子邮件。考虑到这些优势,不难理解哈佛商业评论的一篇文章发现,人工智能通常支持三个主要的业务需求:自动化业务流程,通常是自动化后勤行政和财务任务。通过数据分析获取洞察。与客户和员工互动。机器学习通过使用数据示例解决这些问题,无需为每件事都编写自定义代码,从而增加了可扩展性。然后,诸如深度学习等机器学习解决方案允许解决这些问题,即使数据包含非结构化信息,如图像、语音、视频、自然语言文本等。为什么选择云进行人工智能?设计云数据平台的一个关键动机可能是组织迅速采用深度学习等人工智能技术。为了使这些方法运作,它们需要大量的训练数据。因此,计划构建机器学习模型的组织将需要建立一个数据平台,以组织和向其数据科学团队提供数据。机器学习模型本身非常复杂,训练这些模型需要大量称为图形处理单元(GPU)的专用硬件。此外,人工智能技术,如语音转录、机器翻译和视频智能,往往作为云上的软件即服务(SaaS)提供。此外,云平台提供了民主化、更容易操作和跟上技术最新进展的关键能力。云基础设施要点是高质量的人工智能需要大量的数据。一篇著名的论文标题为“深度学习的规模是可以预测的,经验证明”发现,对于自然语言模型的5%改进,需要训练的数据量是用于获得第一个结果的两倍。最好的机器学习模型不一定是最先进的,而是那些在足够高质量的数据上进行训练的模型。原因在于越来越复杂的模型需要更多的数据,而即使是简单的模型如果在足够大的数据集上进行训练,性能也会提高。为了让您了解完成现代机器学习模型训练所需的数据量,图像分类模型通常在一百万张图像上进行训练,而主要的语言模型则在多个 terabyte 的数据上进行训练。正如图1-10所示,这种数据量需要大量高效的定制计算——由加速器(如GPU)和称为张量处理单元(TPU)的定制应用特定集成电路(ASICs)提供——以利用这些数据并理解它。许多最近的人工智能进步都可以归因于数据规模和计算能力的增加。云中大型数据集与支持它的众多计算机之间的协同作用已经促使机器学习取得了巨大的突破。这些突破包括将语音识别中的词错误率降低了30%,是传统方法中20年来取得的最大增益。民主化建模机器学习模型,特别是在复杂领域,如时间序列处理或自然语言处理(NLP),需要对机器学习理论有所了解。使用诸如PyTorch、Keras或TensorFlow等框架编写用于训练机器学习模型的代码需要了解Python编程和线性代数。此外,机器学习的数据准备通常需要数据工程专业知识,评估机器学习模型需要高级统计知识。部署机器学习模型和监控它们需要了解DevOps和软件工程(通常称为MLOps)。毫无疑问,很少有组织具备所有这些技能。鉴于此,对于传统企业来说,利用机器学习解决业务问题可能会很困难。云技术提供了几种选项来使机器学习的使用民主化:机器学习APIs: 云提供商提供可通过API调用的预构建机器学习模型。此时,开发人员可以像使用任何其他Web服务一样使用机器学习模型。他们只需具备针对表述状态转移(REST)Web服务进行编程的能力。此类机器学习API的示例包括Google Translate、Azure Text Analytics和Amazon Lex,这些API可以在不了解NLP的情况下使用。云提供商还提供用于文本和图像生成的生成模型作为API,其中输入只是文本提示。可定制的机器学习模型: 一些公共云提供“AutoML”,即端到端的机器学习管道,只需点击鼠标即可进行训练和部署。AutoML模型执行“神经架构搜索”,通过搜索机制实质上自动进行机器学习模型的架构设计。虽然与人类专家选择有效模型相比,训练时间较长,但对于没有能力设计自己模型的业务线来说,AutoML系统足够使用。请注意,并非所有的AutoML都相同,有时所谓的AutoML只是参数调整。确保您获得的是定制的架构,而不仅仅是在预构建模型之间进行选择,并仔细检查哪些步骤可以自动化(例如,特征工程、特征提取、特征选择、模型选择、参数调整、问题检查等)。简化的机器学习: 一些数据仓库(例如BigQuery和Redshift)提供了仅使用SQL对结构化数据进行机器学习模型训练的能力。Redshift和BigQuery通过分别委托给Vertex AI和SageMaker来支持复杂模型。诸如DataRobot和Dataiku之类的工具提供了用于训练机器学习模型的点对点界面。云平台使生成模型的微调比以前更容易。机器学习解决方案: 一些应用非常常见,可以购买和部署端到端的机器学习解决方案。Google Cloud上的Product Discovery为零售商提供了端到端的搜索和排名体验。Amazon Connect提供了一个由机器学习驱动的可立即部署的联系中心。Azure Knowledge Mining提供了一种挖掘各种内容类型的方式。此外,Quantum Metric和C3 AI等公司为几个行业的常见问题提供了基于云的解决方案。机器学习构建块: 即使整个机器学习工作流没有现成的解决方案,其中的部分工作可能仍然可以利用构建块。例如,推荐系统需要匹配项目和产品的能力。Google Cloud提供了一种称为“两塔编码器”的通用匹配算法。虽然没有端到端的后勤自动化机器学习模型,但您可以利用表单解析器更快地实现该工作流程。这些功能使企业能够在没有深度专业知识的情况下采用人工智能,从而使人工智能更广泛地可用。即使企业具有人工智能方面的专业知识,这些功能也非常有用,因为您仍然需要决定是购买还是构建机器学习系统。通常情况下,机器学习的机会比解决这些问题的人更多。鉴于此,允许使用预构建工具和解决方案执行非核心功能具有优势。这些即插即用的解决方案可以立即提供大量价值,而无需编写定制应用程序。例如,可以通过API调用将自然语言文本的数据传递给预构建模型,将文本从一种语言翻译为另一种语言。这不仅减少了构建应用程序的工作量,还使非机器学习专家能够使用人工智能。在谱的另一端,问题可能需要自定义解决方案。例如,零售商通常会构建机器学习模型来预测需求,以便了解需要备货多少产品。这些模型通过公司的历史销售数据学习购买模式,结合内部专业直觉。另一个常见的模式是使用预构建的即插即用模型进行快速实验,一旦证明了ML解决方案的价值,数据科学团队就可以以定制的方式构建它,以获得更高的准确性,希望能够在竞争中脱颖而出。实时ML基础设施必须与现代数据平台集成,因为实时、个性化的机器学习是产生价值的地方。因此,分析速度变得非常重要,因为数据平台必须能够实时摄取、处理和提供数据,否则就会失去机会。这还需要与行动的速度相辅相成。机器学习推动基于客户上下文的个性化服务,但必须在客户上下文切换之前提供推断——在大多数商业交易中,机器学习模型需要在关闭窗口内为客户提供行动选择。为了实现这一点,您需要使机器学习模型的结果能够实时到达行动点。能够实时为机器学习模型提供数据并在实时获取机器学习预测是防范欺诈和发现欺诈之间的区别。为了防范欺诈,需要实时摄取所有支付和客户信息,运行机器学习预测,并将机器学习模型的结果实时提供回支付站点,以便在怀疑欺诈时拒绝付款。实时处理可以在其他情况下节省成本,例如客户服务和购物车放弃。在呼叫中心捕捉客户的不满并立即升级情况对于提供有效服务至关重要——一旦失去客户,重新获取他们将花费更多的钱,而在当下提供良好服务的成本较低。同样,如果购物车有被放弃的风险,提供5%的折扣或免费运输等诱因的成本可能会低于需要让客户回到网站所需的更大力度的促销。在其他情况下,批处理根本不是一个有效的选择。为了使Google Maps允许驾驶员避开交通拥堵,需要实时交通数据和实时导航模型。正如您将在第8章中看到的那样,云服务的弹性和自动扩展能力在本地很难实现。因此,最好在云中进行实时机器学习。MLOPSML在公共云中更好的另一个原因是,使ML运营化是困难的。有效和成功的ML项目需要对数据和代码进行运营化。观察、编排和执行ML生命周期被称为MLOps。在生产中构建、部署和运行ML应用程序涉及多个阶段,如图1-11所示。所有这些步骤都需要进行编排和监视;例如,如果检测到数据漂移,可能需要自动重新训练模型。必须持续对模型进行重新训练并进行部署,确保它们可以安全地部署。对于传入的数据,您必须执行数据预处理和验证,以确保没有数据质量问题,然后进行特征工程,接着进行模型训练,最后进行超参数调整。除了讨论的与数据相关的监控方面之外,您还需要对任何运行中的服务进行监控和运营化。生产应用程序通常是24/7/365持续运行的,定期会有新的数据进入。因此,您需要具备工具,使得轻松编排和管理这些多阶段的机器学习工作流程,并能够可靠地重复运行它们。像Google的Vertex AI、Microsoft的Azure Machine Learning和Amazon的SageMaker这样的云AI平台提供了整个机器学习工作流的托管服务。在本地进行这项工作需要您自己组合底层技术并自行管理集成。在编写本书时,MLOps功能正在以极快的速度添加到各种云平台上。这引出了一个附带的观点,即随着机器学习领域的快速变化,将构建和维护机器学习基础设施和工具的任务委托给第三方,并专注于与核心业务相关的数据和见解,会更为明智。总而言之,基于云的数据和人工智能平台可以帮助解决与数据隔离、治理和容量相关的传统挑战,同时使组织能够为未来更加重要的人工智能能力做好准备。核心原则设计数据平台时,制定关键设计原则并确定希望分配给每个原则的权重可能会有所帮助。您可能需要在这些原则之间进行权衡,拥有一个所有利益相关者都同意的预定评分卡可以帮助您在不必返回第一原则或受到最强烈抗议声响影响的情况下做出决策。以下是我们建议的数据分析堆栈的五个关键设计原则,尽管相对权重会因组织而异:提供无服务器分析,而不是基础设施。 为完全托管的环境设计分析解决方案,尽量避免搬移和转移的方法。专注于现代无服务器架构,使数据科学家(我们广义上使用此术语来指数据工程师、数据分析师和ML工程师)能够将注意力完全集中在分析上,远离基础设施考虑。例如,使用自动化数据传输从系统中提取数据并提供用于跨任何服务进行联合查询的环境。这消除了维护自定义框架和数据管道的需要。嵌入端到端ML。 允许组织将端到端的ML操作化。不可能构建组织需要的每个ML模型,因此确保您正在构建的平台能够嵌入民主化的ML选项,如预建的ML模型、ML构建模块和更易于使用的框架。确保在需要自定义训练时,可以访问强大的加速器和可定制的模型。确保支持MLOps,以便部署的ML模型不会漂移并且不再适用。简化整个堆栈上的ML生命周期,以便组织可以更快地从其ML倡议中获取价值。推动整个数据生命周期的分析。 数据分析平台应提供一套全面的核心数据分析工作负载。确保您的数据平台提供数据存储、数据仓库、流数据分析、数据准备、大数据处理、数据共享和变现、商业智能(BI)和ML等服务。避免购买需要集成和管理的临时解决方案。更全面地看待分析堆栈将允许您打破数据隔离,使用实时数据为应用程序提供动力,添加只读数据集,并使查询结果对任何人都可访问。启用开源软件(OSS)技术。 在可能的情况下,确保开源是您平台的核心。您希望确保您编写的任何代码都使用开源标准,如标准SQL、Apache Spark、TensorFlow等。通过启用最佳的开源技术,您将能够在数据分析项目中提供灵活性和选择性。为增长而构建。 确保您构建的数据平台能够满足组织预期面临的数据大小、吞吐量和同时用户数量的规模。有时,这将涉及选择不同的技术(例如,对于某些用例使用SQL,对于其他用例使用NoSQL)。如果这样做,请确保您选择的两种技术能够互操作。利用已被世界上最具创新性的公司证明并用于运行其关键任务的解决方案和框架。总体而言,这些因素按照我们通常建议的顺序列出。由于企业选择进行云迁移的两个主要动机是成本和创新,我们建议您优先考虑无服务器(用于节省成本和使员工摆脱例行工作)和端到端ML(用于启用的广泛创新)。在某些情况下,您可能希望优先考虑某些因素而不是其他因素。对于初创公司,我们通常建议最重要的因素是无服务器、增长和端到端ML。为了加快速度,可以牺牲综合性和开放性。高度受监管的企业可能会优先考虑综合性、开放性和增长,而不是无服务器和ML(即监管机构可能要求在本地进行)。对于数字原生企业,我们建议按照端到端ML、无服务器、增长、开放性和综合性的顺序。总结这是关于数据平台现代化的高层介绍。从数据生命周期的定义开始,我们看了数据处理的演变,传统方法的局限性,以及如何在云上创建统一的分析平台。我们还研究了如何将云数据平台扩展为混合平台,并支持AI/ML。本章的主要要点如下:数据生命周期有五个阶段:收集、存储、处理、分析/可视化和激活。这些需要由数据和ML平台支持。传统上,组织的数据生态系统由独立的解决方案组成,导致组织内部产生数据孤岛。数据移动工具可以打破数据孤岛,但它们会带来一些缺点:延迟、数据工程资源瓶颈、维护开销、变更管理和数据缺失。在IT内部集中控制数据会导致组织面临一些挑战。IT部门没有必要的技能,分析团队得到质量较差的数据,业务团队不信任结果。组织需要构建云数据平台,以获取最佳架构、处理业务单元的整合、扩展本地资源并规划业务连续性。云数据平台利用现代方法,旨在通过重新构建数据、打破数据孤岛、使数据民主化、强制数据治理、实时决策和使用位置信息进行数据驱动创新,以及从描述性分析无缝过渡到预测性和规范性分析。所有数据可以从操作系统导出到一个集中的数据湖进行分析。数据湖充当分析工作负载和业务用户的中央存储库。然而,缺点是业务用户没有编写代码对抗数据湖的技能。数据仓库是支持SQL的集中式分析存储,业务用户熟悉这一点。数据湖仓库基于这样一个观念,即所有用户,无论其技术技能如何,都可以并且应该能够使用数据。通过提供一个使数据可访问的集中和基础框架,可以在湖仓库顶部使用不同的工具来满足每个用户的需求。数据网格引入了将数据视为独立产品的一种方式。在这种方法中,分布式团队拥有数据生成,并通过明确定义的数据模式为内部/外部消费者提供服务。混合云环境是满足企业现实情况的务实方法,如收购、当地法律和延迟要求。公共云提供管理大型数据集和按需提供GPU的方式,使其成为所有形式的ML,特别是深度学习和生成AI不可或缺的工具。此外,云平台提供了民主化、更容易操作和跟上技术进展的关键能力。云数据平台的五个核心原则是优先考虑无服务器分析、端到端ML、全面性、开放性和增长。相对权重将因组织而异。现在您知道自己想要的方向,在下一章中,我们将探讨达到目标的策略。
0
0
0
浏览量2099
攻城狮无远

《流式Data Mesh》第三章:领域所有权

第三章:领域所有权流数据网格的四个支柱之一是领域所有权。定义数据领域可能会很困难,特别是当数据经常与多个领域共享,或者当一个领域依赖于另一个领域的数据时。 然而,在其核心,数据领域的定义是相当简单的。领域是生成逻辑分组的数据所涉及的人员和系统。一个领域指的是与共同目的、对象或概念相关的互相关联的数据领域。这些数据最终会被共享(并可能被转换)为发布的数据产品。数据领域还具有输出端口,定义了数据产品的共享方式,以及输入端口,定义了如何从其他领域消费数据(参见图3-1)。这些端口构建了领域之间的互连,从而创建了数据网格。这些端口代表数据产品的生成和消费。在流数据网格中,这些端口是流数据产品。 领域内部的系统和团队不会暴露给网格。它们帮助支持流数据产品的管理和开发。在本章中,我们将概述一些方法来确定领域,以确保领域在流数据网格中无缝运行。识别领域图3-1显示,团队和系统包含在一个领域中,因此可以影响其定义。团队通过成为他们所在业务领域和维护开发的应用程序的专家来影响定义。系统通过其物理位置和所持有的数据来影响定义。团队和系统聚集在一起,作为一个整体来创建流数据产品。重要的是要理解,影响数据产品生产的因素比影响数据产品消费的因素对领域的识别更为重要。消费领域只期望对数据产品进行保证,而生产者负责提供这种保证。我们将在本书中讨论其中的一些因素。我们在本书中经常使用的类比是在杂货店购买农产品。作为购物者,您希望在购买和最终消费之前检查农产品。看到农产品受到保护,使购物者对其清洁程度有了保证。例如,看到有机标签可能使消费者对他们的食品产品质量有信心。对于农民和经销商来说,为其产品传达这种保证和质量需要付出很大的努力。可辨识的领域根据源对齐的数据来识别领域非常容易。只需在数据产生或生成的地方创建一个领域。使用和理解数据的事物往往会围绕着这些数据聚集。这包括应用程序、工程师和业务专家,他们负责解决业务问题。同样地,基于应用程序团队或业务问题来识别领域也很容易。只需在解决业务问题的团队所在地创建一个领域。地理区域团队之间存在大的地理距离,需要通过异步通信(如电子邮件和Slack)交流,这会使得将数据保留在单个领域变得困难。远距离不仅使团队之间的沟通变得困难,还可能导致数据产品的速度变慢。因此,在这种情况下,通过地理区域来确定领域可能是有价值的。图3-2展示了一个简单的数据流水线,它从两个数据源获取数据,进行连接操作,并将结果数据产品发布到数据网格中。如果其中任何一个箭头需要跨越遥远的地理区域,那么流水线可能无法满足数据产品消费者的SLA期望。流水线还会遇到一些问题,因为许多用于处理数据的工具并不适合被拉伸到如此远的距离。在这种情况下,如果其中一个数据源位于远程地区,将为其创建一个新的领域并让其为当前领域发布数据产品将是有益的。在图3-3中,源数据库的摄取由一个新的领域(领域2)执行。然后,领域2将数据产品发布到数据网格中。原始领域(领域1)现在成为领域2的数据产品的消费者,并在发布自己的数据产品之前进行其转换。原始领域的分离强制领域2提供满足领域1 SLA要求的数据产品。那么,既然领域2仍然需要将其数据从远距离提供给领域1,它如何提供更好的SLA呢?解决方案是领域2在远距离之间复制其数据产品,以便将数据产品本地提供给领域1,而无需扩展其数据流程。数据产品的复制对领域1来说是不可察觉和未知的,数据保持在运动中且实时。有关此模式的详细信息将在第7章中讨论。在另一个领域内使用另一个领域的数据产品来增强数据产品很容易被误解为子领域。然而,两者之间存在区别。在前面的示例中,我们将领域2视为一流的领域,由专门的团队维护其数据产品,该团队了解数据的业务背景,并能够提供经过转换、高质量可靠的数据产品,以供其他领域使用。子领域在控制领域内的方面发布时采用了更具层次结构的方法。虽然两种方法相似,但存在区别。子域和子数据网格在数据网格架构的四个支柱中,并没有禁止我们在较大的域中创建子域。但在数据网格中,子域应该被视为普通的顶级域。域-子域层次结构只是元数据,它将作为额外信息提供给数据产品的消费者,以帮助他们决定是否使用数据产品。我们还可以利用继承和域与子域之间的关系来创造一些创造性的手段。这为创建更易管理的自助工具提供了机会,尽管实现上可能会更加复杂。类似地,创建一个子数据网格,即存在于域内的数据网格,可能也太复杂了。为了将一个数据网格与其他网格分隔开来,所需的元数据可能过于庞大而难以管理。将域及其产品保持平坦,以便更容易发现和搜索,可能更加简单和优雅。域仍然可以存在于一个类似于总体结构的单一实体中,但将子域模式数据作为元数据提供给域可能更可行。这样,一些子域数据(不是全部)可以仅作为信息提供,成为数据产品的描述的一部分。数据主权在多个地理区域存在将需要对每个地区的数据主权法规(如GDPR)具有特定的了解。这些规定强制要求私人数据的物理存储和传输位置,以及数据的使用方式。数据主权是保护敏感的私人数据,并确保其始终在所有者的控制下。数据主权规则是特定于国家的,因此捕获数据起源的位置在每个数据点的传承中也至关重要。 在数据网格的上下文中,跨多个地区分割域的原因不仅是技术上的,也涉及法律问题。对特定地区的治理规则有全面的了解对于避免巨额罚款至关重要。在许多情况下,这些罚款可能适用于受影响的整个数据集中每条违规记录,而不仅仅是暴露的记录。对于单个域,了解多个数据主权规则可能会令人不知所措,难以实施。按照数据主权规则将域分离可使您在任何数据在数据网格中共享之前就开始考虑数据治理。 当治理放在首位时,规则集可以立即在代码中强制执行,而不是事后补救,这显然是有风险的。在域的开发早期开始考虑的一些事项包括如何在数据主权规则的范围内加密、标记、混淆或过滤数据,同时仍允许数据的民主化。随着用户在滥用数据资源方面变得越来越聪明,可以预期数据主权规则将发生变化。规则的实施需要具备灵活、易于部署的特点,以便能够在不涉及大量代码更改和大规模部署的情况下更改需求。还要了解数据主权规则可能仅影响整个数据集中的几个属性或字段。在数据集的元数据层中能够识别或注释这些私有属性将使数据主权规则的实施更具配置能力,并适应变化。有关如何实施安全和治理的更多详细信息将在后续章节中讨论。混合架构混合架构与前一节中的地理距离不遵循相同的逻辑,因为本地系统可能具有与基于云的系统不同的安全要求。对于本地数据产品无法向其他域提供公共端点进行消费可能是不可行的,甚至可能是不可能的。在这种情况下,如果一个域既有本地系统又有云提供商的系统,将所有数据资源保留在同一个域中,并对数据产品的消费者隐藏复杂和安全的网络拓扑结构可能更好。在图3-4中,云和本地数据中心位于同一个域中。本地源数据库的数据被复制到云端,在云端与另一个数据源进行丰富,然后作为数据产品公开。消费者只看到最终的数据产品。不仅数据被复制,元数据也被复制,以保留传承关系。如果本地域没有任何云基础设施,创建一个云基础设施可以使更多的数据消费者能够访问数据,这可能是有益的。多云跨多个云提供商扩展业务可以提高对任何单个云提供商中断的弹性。这显然会给付费客户带来更多的信心和保障。对于您的客户来说,以他们选择的云提供服务也是一种成本效益,这将使他们不需要为网络出口相关的成本支付费用(有关成本和后付费的详细信息将在本章后面进行讨论)。数据的复制再次发挥作用。类似于在地理区域之间进行复制,数据将在云提供商之间进行复制。这可以在单个域内或作为单独的域来完成,具体取决于使用情况。有几种方法可以实现这一点,我们将在以后的章节中讨论。灾备如果采用多云解决方案的原因是为了实现从单个云提供商进行灾难恢复(DR),更合适的做法是将数据复制的两端保持在同一个域内,以便将与DR相关的任务保留在域内。跨云提供商的解决方案(或应用程序)很可能会被设置为主备或主-主模式,如图3-5和3-6所示。图3-5展示了两个操作数据库位于不同的云服务提供商(CSPs)中。操作数据从CSP1复制到CSP2,以保持CSP2中应用程序的状态,以应对CSP1发生灾难的情况。这是一种主备灾难恢复解决方案:活动状态为CSP1,灾难/备用状态为CSP2。由于其被动状态,此解决方案无法充分利用CSP2的所有资源。只有在CSP1发生故障时,应用程序才会切换到CSP2,从而充分利用CSP2的资源。图3-6中的主-主灾难恢复(DR)解决方案利用了两个云服务提供商(CSP)的资源。只有使用失败的云提供商的应用程序才会切换到另一个CSP,而不是图3-5中的主备解决方案中的所有应用程序。数据的双向复制使得两个CSP之间所有应用程序的状态保持同步,以实现从任何一个CSP到另一个CSP的故障切换。在数据网格的背景下,主动-主动(active-active)和主动-被动(active-passive)解决方案均包含具有相同数据的数据库(通过复制考虑延迟)。在主动-被动方案中,从被动数据库捕获数据以作为数据产品进行公开,这将允许您限制对主动数据库的访问。在主动-主动方案中,从其中一个主动操作数据库中捕获数据是可以接受的。在流式数据网格中,这两种情况都有各自的优点,具体取决于产生领域提供的弹性保证。分析许多不太具备弹性的应用程序并不需要跨云服务提供商(CSP)的灾难恢复,但仍可能使用其它 CSP 上他们偏好的服务。例如,操作平面的数据存储可能位于一个 CSP 中,而分析平面可能位于另一个 CSP 中。这是一种常见的做法,因为许多数据科学团队更喜欢使用可能仅存在于一个 CSP 中的特定工具。在这种情况下,可以明确将操作和分析平面视为通过数据网格共享数据的不同领域。避免模糊的领域定义在没有明确定义的边界的情况下,领域之间似乎相互交织,所有权变得政治化或需要解释。例如,一个大型零售商很可能有多个领域。一个可能是与产品SKU相关的一组产品属性。另一组数据可能与客户有关,包含定义其人口统计信息的属性。这两个领域相互独立,并由各自理解适当业务背景的团队维护。这些领域可以转换和发布为数据产品,供另一个领域(如交易销售数据领域)使用。交易销售数据领域可以提供不仅发布销售数据,还整合有趣的产品属性和客户信息以推动报告和数据科学的数据产品。为了克服模糊的领域挑战,每个领域边界必须明确而明确。属于一起的业务领域、流程和数据需要保持在一起。此外,每个数据领域应该属于一个且仅属于一个敏捷或DevOps团队。数据领域内的数据集成点应该可管理,并且所有团队成员都能理解。我们建议将领域边界具体化且不可变。这有助于避免关于谁拥有什么数据的冗长讨论,并防止团队自由解释领域边界以适应自身需求。创建面向领域的结构是一个过渡过程,不仅涉及数据,还涉及人员和资源。在创建领域边界时,资源可能最终会与其他团队对齐,从而破坏和演变当前的团队结构。数据网格的整个概念与资源对齐同样重要,因此,在进行此过程时,重新调整资源不应被视为障碍。正如我们之前提到的,共享跨领域的数据会引发复杂性。在许多情况下,建议创建一个共享数据领域,以便以一种允许其他领域标准化和受益的方式提供集成逻辑。重要的是,保持此共享领域的规模较小,并将其命名约定概括为适应其他领域的逻辑上下文。对于重叠的数据需求,领域驱动设计提供了处理此复杂性的模式。表格 3-1 是这些模式的简要总结。领域驱动设计对于源对齐的领域数据和应用团队技术来识别领域,如果您的应用程序和相关数据源尚未定义,您需要一种方法来帮助定义它们。领域驱动设计(DDD)是一种方法论,通过将数据模型本身与核心业务概念相连接,帮助我们理解复杂的领域模型。从DDD中获得的理解为设计基于分布式、基于微服务的面向客户的应用程序奠定了基础。DDD将软件和其组件的实现与不断演化和变化的数据模型相连接。领域是您正在处理的业务世界和您试图解决的问题。这通常涉及需要作为解决方案的一部分进行集成的规则、流程和现有系统。DDD最初由Eric Evans在《Domain-Driven Design》一书中首次提出。该书关注三个原则:主要关注核心领域和领域逻辑。复杂的设计基于领域模型。技术和领域专家之间的协作对于创建能够解决定义的领域问题的应用程序模型至关重要。在进行DDD时,理解以下术语非常重要:领域模型领域逻辑边界上下文共通语言(Ubiquitous language)领域模型领域模型涵盖与您正在解决的问题相关的所有思想、知识、指标和目标。领域模型代表问题领域的词汇和关键概念,并应该确定领域范围内所有实体之间的关系。许多软件项目在开发初期就缺乏共同的术语、目标和提出的解决方案。这就是领域模型的作用所在,它作为问题解决和提出解决方案的清晰描述。此外,领域是您业务的世界,模型成为解决方案,而领域模型则充当关于问题的结构化知识。领域逻辑领域逻辑代表领域存在的原因或目的。其他可能使用的术语包括业务逻辑、业务规则或领域知识。领域逻辑在领域实体中定义,这些实体是数据和行为的结合体。它们在实体内部和之间强制执行逻辑,最终为领域所面临的问题提供解决方案。边界上下文在领域驱动设计(DDD)中,边界上下文是逻辑和复杂性聚集在一起的边界。它们有助于将业务逻辑和实体组织成组,以增加模块化和灵活性,从而可以创建领域和子领域。最终,这些边界上下文可以成为数据网格的领域或子领域。共通语言共通语言建立了一种术语,使领域专家和开发人员在设计领域时能够简化沟通。如果没有建立共通语言,领域的各个角色之间可能会出现沟通障碍,例如,在领域模型中明确表达问题,以便工程师能够将技术问题传达到业务逻辑层面。反之,领域专家能够将业务逻辑问题明确到更低级别的技术异常同样重要。这使得所有角色能够理解整个问题范围以及如何处理和向用户提供解决方案。DDD的结果是构建遵循事件驱动架构(EDA)的应用程序的解决方案。工程师使用EDA模式设计应用程序,该模式基于事件、关系、业务规则、边界上下文和在DDD过程中定义的共通语言。应用程序中生成的事件和实体成为业务领域中的数据,潜在地可以成为流式数据网格中的数据产品。在遵循DDD的过程中,领域专家帮助定义业务对象,并确保它们符合业务的整体背景。例如,在一个领域中,账户和员工可以被视为业务对象。这些业务对象在数据网格中被转化为数据产品。这可能需要与可能声称拥有相同或类似业务对象的其他领域团队会面,而该领域正在计划提供服务。正如本章前面讨论的那样,根据业务需求,创建共享数据领域或创建共享数据的副本可能是合理的选择。数据网格的领域角色一个领域有两个主要角色:数据产品工程师(或称数据工程师)和数据产品负责人(或数据产品经理、数据管控人员)。这些角色可以是同一个人,也可以是领域中专门的人员担任。数据产品负责人必须深入了解他们的数据消费者是谁,数据是如何被使用的,以及使用数据的方法。这将有助于确保数据产品满足使用案例的需求。数据产品工程师负责创建高质量、可靠且可被消费者使用的数据产品。可以通过最小的努力将这些领域角色纳入现有的领域角色中。数据产品工程师数据网格领域中最重要的角色之一是数据产品工程师,他负责构建、维护和提供数据领域的数据产品。这包括从操作性数据库中摄取数据、转换这些数据资产并将其发布到数据网格的相关任务。请参阅表3-2以获取数据产品工程师的任务列表。数据产品负责人或数据管控人员数据产品负责人或数据管控人员的角色是半技术性的,主要是为了理解数据使用模式,并确保数据产品能够满足消费者的需求。我们将使用数据产品负责人这个术语。这个人与最终用户和数据产品工程师进行沟通,并努力改进和演进产品。请参阅表3-3,了解与数据产品负责人相关的任务列表。数据产品负责人还负责确保基础设施的规划(使用自助服务工具),并确保具备适当的资源来开发和交付数据产品到数据网格,并确保数据高可用性、符合服务级别协议(SLA)并实施所有必要的安全措施。该角色不断监控使用情况和成本,确保客户满意,同时最大限度地减少任何产生的费用。流数据网格工具和平台的考虑为了实施流数据网格,我们将寻找现有的工具和平台。表3-4列出了一些最有用和流行的工具和平台。所选择的工具和平台决定了领域的接口将如何实施,换句话说,即领域之间如何生产和消费流数据产品。领域费用分摊在企业中,每个业务线都有预算来保持运营的顺利进行。在标准的数据仓库环境中,支付所需数据服务的责任方相对明确。然而,在数据所有权分散和分布的情况下,例如在数据网格中,谁来支付所有这些内容的费用就变得不清楚了。随着建立数据领域和数据产品的成本增加,将这些成本分摊给数据网格的消费者变得重要。以下是一些将数据网格部署所产生的费用按领域级别划分的示例:基于使用量的费用分摊一种领域费用分摊的解决方案是将特定的容器实例与下游用户或团队关联起来。当您希望限制下游消费者的服务托管级别时,这种解决方案非常有效。然而,该方法存在一些缺点。首先,当一组实例最初为终端用户进行配置时,很容易过度配置,导致消费者为比实际需要更多的资源付费。其次,由于服务在数据领域中共享,可能会发现消耗了一些最初未计费的资源。因此,可能需要对某些数据产品施加资源约束,可能会影响服务级别协议(SLA)。任务级别的资源费用分摊另一种解决方案是开发一种机制,让数据领域所有者计算每个数据产品的总成本。这种解决方案需要开发计量机制和费用分摊测量。当计量机制部署后,它跟踪请求的使用情况,包括网络、虚拟CPU和内存,以提供这些请求的服务。然后,通过费用分摊测量,领域所有者可以根据领域所运行的容器实例所产生的成本轻松地将成本与这些任务关联起来。这种解决方案允许领域所有者在共享基础设施上部署整个解决方案,而无需考虑基于使用量的费用分摊及其局限性。可以在任何时间点、任何集群和任何数据产品上计算领域使用的成本。成本分摊费用分摊在一个领域内可能有一些已知的数据产品消费者。在这种情况下,与其按照每个请求或基于使用量的模式计算领域使用的成本,也许更合理的是让各个团体同意在领域级别分摊数据产品消费的成本。各个团体将同意将领域运营的整体计算成本分摊给多个消费者,每个消费者负责固定百分比的成本。这种方法的优点是部署简便,因为不需要监控使用情况的机制。缺点包括无法预测和意外的使用量波动;消费者使用错误计算的总资源百分比,从而导致过度或不足计费;以及无法跟踪实际的消费者使用模式。数据产品费用分摊要实施费用分摊,需要配置一个监控工具来帮助衡量数据产品的使用情况和所使用的资源。这个监控工具可以作为数据网格控制平面的一部分,并随着在表3-4中描述的工具的配置而自动提供。在第4章中,我们将讨论如何监控数据产品并向消费者收费。总结在定义领域时,架构师需要考虑的不仅仅是在领域驱动设计中定义的业务对象。在本章中,我们提供了其他需要考虑的策略,这些策略将影响领域,例如地理区域、混合云、多云和灾难恢复需求。在第4章中,我们将展示这些领域如何构建并发布流式数据产品到流式数据网格中。
0
0
0
浏览量719
攻城狮无远

第一章:介绍数据可观测性

从前,有一个年轻的数据分析师,名叫Alex,他对数据充满了深厚的热情。Alex热爱数据能够帮助企业做出明智的决策,推动增长并取得成功的方式。然而,Alex也意识到误解数据或者对数据的可见性不足可能带来的风险。Alex正在与一位名叫Sarah的数据工程师一起进行一项关键项目。Sarah负责准备数据,确保它准备好供分析使用。随着他们深入研究项目,Alex和Sarah意识到有许多变量在起作用,而他们所使用的数据并不像他们最初想象的那样简单。有一天,在Alex进行分析以生成洞察力的过程中,他发现当天呈现的结果看起来很奇怪,与他此前看到的情况难以相关联。他去找Sarah讨论这个情况,但Sarah需要更多关于他以前的解释、期望以及他要求她检查什么的背景信息。在经过四天的合作、双人审查和多次头脑风暴后,Sarah和Alex发现,入站数据的半打变量的分布微小变化会在几个转换步骤后改变生成的洞察力。一些变量有更多的缺失值,因此在清理转换中被删除,另一些变量的平均值大幅增加,而一个数据集由于从操作源更好地提取数据而刷新,几乎是以前的两倍多。尽管最初Sarah和Alex认为数据的质量可能下降了,但事实上,数据只是发生了变化,他们对数据的假设需要调整。他们意识到,他们很幸运这发生在项目投入生产之前,而这种情况将来可能会发生多次。如果他们没有预见到这些变化,他们将陷入困境。这个经历让他们意识到数据可观测性的重要性。他们需要对数据、数据的转换和数据的使用情况有可见性,以便能够迅速对任何变化做出反应。他们开始采纳数据可观测性原则,并为数据管道添加所需的功能,以提供实时洞察数据、数据质量和数据使用情况。从那天开始,他们拥有了仪表板和通知系统,用于跟踪管道中数据的健康状况,并提醒他们需要关注的问题,以确保客户始终收到准确可靠的数据。通过这个经验,Alex和Sarah学到了数据是一个不断变化的生命体,需要持续监控和观察。他们意识到,如果没有数据可观测性,他们永远无法迅速应对变化,从而将项目的成功置于风险之中。如果您正在阅读这本书,可能是因为您像Sarah和Alex一样,在自己的数据工作中经历或预计将经历类似的情况。您理解数据的力量,但也明白即使微小的变化未被注意到可能会带来灾难性后果。但是您可能不知道需要做什么或如何入手。不用再担心了。在本书中,您将了解到Alex和Sarah是如何采纳数据可观测性原则的,以确保他们的数据管道可靠,客户获得准确可信的数据。通过将这些原则应用到自己的工作中,您可以避免不可靠数据的陷阱,为您的数据驱动项目的成功打下坚实的基础。在深入探讨数据可观测性在规模上的作用和提供之前,让我们首先看看数据团队的发展以及他们面临的挑战。扩展数据团队更多的角色,更多的责任,更多的工程。 数据团队是一组数据从业者,他们共同合作收集、处理、分析和解释数据,以生成洞察,并为决策提供信息,以推动更好的业务结果和提高组织绩效。由于数据团队在组织中扮演战略角色,他们的运营效率可能成为将高需求数据纳入关键业务运营的瓶颈。为了应对这一挑战,数据团队的发展类似于上世纪50年代的IT团队——专门的角色,如系统工程师、Web工程师或后端工程师被增加,以支持特定操作,而不是拥有通才角色。随着数据团队通过增加角色来增加其运营复杂性,会产生更多成员和团队之间的相互作用和相互依赖,进而增加了对更大可见性、透明度和标准化的需求。在本节的其余部分,我将以搜索趋势为例,说明数据团队的创建和扩展以及数据工程师角色。随之而来的影响和挑战将在下一节中进行讨论,该节还描述了其他角色,例如分析工程师。 让我们从谷歌趋势搜索开始(图1-1),搜索词为“数据团队”,时间跨度从2004年到2020年。我们可以看到,虽然数据团队一直存在,但对数据团队的兴趣始于2014年,并在2014年至2020年之间显著加速增长。尽管“大数据”方面的兴趣减少(如图1-2所示)。显然,这并不意味着不再需要大数据,但正如图1-3所示,2014年开始关注数据科学,因为它与价值生成的关联更加直观。然而,尽管数据科学的兴趣开始上升,但很明显,数据的可用性成为许多数据科学项目的障碍。数据科学团队并没有取代大数据团队。相反,数据团队已经拥抱了分析,因此增加了这个相对较新的数据科学家角色。随着数据和分析对公司的成功变得更加核心,向合适的人提供正确的数据仍然是一个持续的挑战。加特纳(Gartner)指出,到2018年,数据工程师已经成为应对数据可访问性挑战的关键因素,因此数据和分析领导必须“将数据工程学作为其数据管理战略的一部分”。因此,显然缺少一个专门负责为下游用例生成数据的角色。这就是为什么自从大约2020年以来,随着公司开始雇佣工程师专门帮助构建数据管道并整合来自不同源系统的数据,对数据工程师的搜索量显著增加的原因。如图1-3所示,今天数据工程正趋势迎头赶上数据科学家在搜索流行度上。正如之前介绍的,以这种方式扩展数据团队,增加人员、角色和责任,会带来自己的挑战。让我们在下一节深入探讨这个主题。扩展数据团队的挑战随着公司寻求扩大其数据使用,它们也必须扩展其数据团队。我将用以下示例来介绍挑战及其后果:数据团队中角色和责任的出现描述。随着团队的扩大而增加的运营复杂性的分析。数据发行管理(包括数据)的挑战以及它们对数据团队的心情和效率的影响。提醒注意在机器学习/人工智能之旅中避免失败。考虑一个起始只有一个人的数据团队,任务包括数据摄入、数据集成,甚至最终的报告生成。对于这个人来说,第一个项目可能是生成最近客户获取情况的视图。数据工程师需要从公司的客户关系管理(CRM)系统中查询和摄入源数据到数据湖,将其集成到数据仓库中,并创建一个报告,供销售总监了解过去几周内销售进展情况。这位初期的数据团队成员实际上是一把瑞士军刀:既涵盖数据工程又涵盖数据分析的职责,以交付项目成果(报告)。在更大的数据团队中,这项工作所需的技能在数据工程、数据分析和业务分析之间平衡。然而,在一个团队中,个人必须精通构建和运行管道所需的技术,掌握报告工具,并理解业务关键绩效指标(KPI)。值得注意的是,每个领域都需要具体的专业知识。到目前为止,这个人很开心,工作范围和责任如图1-4所示。当然,这个过程并不是那么简单。例如,应该遵循整个系统开发生命周期(SDLC)将数据投入生产并使其可供最终用户分析。然而,在一个非常抽象的层面上,上述的基本过程大致是我们通常认为的数据项目中的“产生价值”的过程。在只有一个团队成员和一个数据项目的情况下,整个数据生态系统相对来说是可控的。这个过程对于一个人来说足够简单,整个项目的责任也归因于个人。因为团队只有一个人,他们还与业务利益相关者(如销售总监)有密切的关系,因此在用例的使用和项目的业务目标方面建立了一些领域知识。虽然通常情况下这是业务分析师执行的角色,但在这种情况下,数据工程师也承担了这个角色。因此,如果用户提出任何问题,很明显应该由谁来解决。毕竟,他们不仅执行了过程的每个步骤,还了解了过程的每个领域。然而,随着利益相关者认识到数据可能提供的潜力和价值,对这个人提出了更多的新报告、信息、项目等请求。因此,需求的复杂性增加,最终,个人在当前设置下达到了生产能力的极限。因此,迫切需要扩展团队并投资于专门的成员,以优化生产效率。在接下来的部分中,我们将看到随着特定角色的创建,这个团队如何开始增长,如表1-1所示,表中还突出了每个角色的责任和依赖关系。市场上数据工程、数据科学和数据分析技能也存在短缺,因此扩展团队变得极为具有挑战性。这在一开始时尤为真实,因为一切都需要并行构建,如文化、成熟度、组织结构等。因此,更加重要的是保留你拥有的人才,这意味着要确保他们的角色和责任与其技能集相匹配。因此,在下一节中,我们将了解这些角色是如何被添加的,他们将被期望做什么,以及随着时间的推移团队将会发生什么变化。分工明确的角色与责任和组织复杂性这个单人团队已经达到了构建新报告的最大容量,并增加了另一名成员以跟上不断涌入的请求速度。如前所述,重要的是要专业化团队成员,以最大化他们的产出、满意度,当然还有工作质量。因此,现在有一名数据分析师根据与利益相关者定义的报告,基于数据工程师构建的数据集,来交付报告。分工角色的影响是数据工程师现在离利益相关者更远,失去了与业务需求的直接联系。从某种意义上说,数据工程师失去了对项目最终目的的部分可见性和最终结果的部分责任。相反,工程师将把所有的努力和精力集中在他们的可交付成果上——ETL、SQL或用于构建数据的任何框架或系统。图1-5中显示了团队及其交付物,我们可以看到距离和依赖关系正在发生变化。图1-5还显示了在数据团队采用新的管理体制后,已经交付了一些项目后所产生的工作范围。数据生态系统开始增长,端到端的可见性较少,因为它分散在两者的头脑中,责任分散在团队成员之间开始隔离。接下来,一名数据科学家被加入到团队中,因为利益相关者愿意探索建立无需人为干预的自动决策系统,或者拥有专门负责监督结果和扩大数据产生的价值的团队成员。如图1-6所示,这增加了更多的规模,这些项目将需要更多的数据,速度更快,机制更复杂,因为整个过程需要自动化以生成预期的结果。随着时间的推移和整个系统的扩展,问题不可避免地开始出现。正如Google的一篇论文《高风险人工智能中的数据级联》所指出的,任何项目中至少有一个数据问题发生的概率高达92%。由于系统已经发展得如此之多且如此之快,这些问题难以进行故障排除和解决。到底发生了什么?事实上,没有人知道。系统内部缺乏可见性,再加上其复杂性,使其对于甚至是那些建立它的人来说都成了一个完全的黑匣子(一段时间以前)。正如您在图1-7中所看到的,组织的数据生态系统已经变得像IT中的“传统系统”一样,只不过发生得更快。在接下来的部分中,我将带您深入了解如何处理这些问题以及它们对数据团队动态的影响。然而,在此之前,让我们分析一下这些数据问题的构成。数据问题的构成和后果解析数据问题对大多数组织来说是一个持续且常见的挑战。根据邓白氏(Dun & Bradstreet)的一项调查,42%的企业表示他们曾经遇到过不准确的数据。导致数据问题的最常见原因包括:监管 数据隐私或其他数据法规要求对数据的收集、摄入或存储进行更改,这可能会引发意想不到的问题。例如,通用数据保护条例(GDPR)的出台迫使零售公司更改了他们收集的数据,并将其限制为仅用于完全尊重客户的完整性的优化推荐引擎所需的数据。业务需求 不同的业务用例需要不同的数据配置。例如,某个业务用例可能依赖于收货地址而不是账单地址。因此,账单地址可以从数据中删除。然而,如果财务部门使用相同的数据,他们可能需要这些账单地址,但可能已经无法访问它们,因为这种用法不是“已知”的。人为错误 数据问题通常是由简单的人为错误引起的。例如,数据工程师意外地发布了一个删除所有金额列中负值而不是将它们设置为null值的转换应用程序。因此,数据用户获得的数据比预期的要少,从而降低了他们分析的准确性。潜在信息错误 这不是数据错误,而是对数据的解释错误。例如,由于运输工人罢工,蓝色运动鞋的销售量大幅下降,因为库存没有补充。然而,运动鞋的受欢迎程度非常高,买家们改买了棕色的。市场营销团队不知道实际情况(例如与运输效率和库存供应相关的潜在变量),估计棕色成为了新的蓝色。因此,生产了更多的棕色运动鞋,在一周后罢工结束时仍未售出。数据漂移错误 更加隐匿,这种错误也是对数据的误解,可能是由于潜在变量引起的;然而,最初的解释是准确的。随着时间的推移,数据真正发生了如此大的变化,以至于假设不再成立;因此,洞察力是错误的。这样的漂移示例可能发生在时尚领域,当某种产品受到年龄在28-31岁的人的喜爱时,但数据的范围是25-30岁和30-35岁。在这种情况下,明年,25-30岁范围(29岁的人)的兴趣可能会大幅下降,而移动到30-35岁范围(30-32岁的人);因此,相关产品的推荐必须更改以适应最具代表性的范围。导致数据问题的最具挑战性的方面之一是参与数据或应用程序更改的工程师通常不了解他们所做更改的影响。不幸的是,通常直到数据价值链的最后才会发现问题。总之,本书的整个目的是教大家如何在继续评估、沟通和验证对数据的理解和假设的同时,参与对任何此类情况作出快速反应的责任。通常情况下,正如我们在扩展数据团队故事中讨论的那样,业务用户正在像往常一样工作,运行报告等,通过直觉和他们以前使用数据的经验来认识到数字“看起来不对”。但是,在那时,已经太迟了。在发现不准确之前,可能已经基于错误数据做出了业务决策。没有时间来解决数据问题,数据团队努力寻找谁具有知识和技能来帮助解决问题。然而,通常不清楚谁负有责任,或者解决问题需要哪些知识和技能。分析师?工程师?IT?责任也可能会在一刹那间发生变化。也许分析师改变了某些信息计算的方式,现在影响到了销售报告,但甚至在此之前,数据工程团队可能已经调整了支持业务用户运行销售报告的销售分析工具的连接之一。为了尝试解决问题,每个人都依赖于每个人对所做的事情、工作原理、文档的位置是否准确、(代码)历史中最后更改的理解等方面的知识。毕竟,问题归结为在同事协作或艰难地(独自在黑暗中)手动查找和解决问题。这是因为没有人能确定哪些字段或表将影响到下游的数据使用者。解决问题所涉及的费用和时间以及对企业生产力、销售、总收入甚至声誉的负面影响都是显著的。邓白氏报告还告诉我们,近五分之一的企业因不完整或不准确的数据而失去了客户。将近四分之一的公司表示,数据质量差导致了不准确的财务预测,当然,其余的75%可能也因不准确的数据而受到其他用途的影响。不过,对于公司稳定性来说,25%的错误财务预测仍然令人印象深刻。持续的数据问题可能会导致在基于数据洞察力做出业务决策时缺乏信心。根据最近对1300名高管的研究,70%的受访者表示他们不确定他们用于分析和预测的数据是否准确。数据问题对数据团队动态的影响首先,数据问题由数据用户检测到,他们开始怀疑,因为收到的数据与预期不符,或者分析的结果与预期不一致。这些怀疑的直接副作用是在用户心中埋下了对产生数据的团队或整个数据平台的不信任情绪。图1-8描述了目前的情况,其中一个数据使用者开始对数据感到担忧,并想象刚刚发现的问题可能会在使用它的某些或所有应用程序中产生其他尚未发现的后果(也称为数据级联,问题传播)。在这种情况下,用户总是在对他们来说不合适的时候检测到问题。他们需要在那个时刻的数据;否则,他们就不会访问或检查数据。因此,用户陷入困境,当然会变得不高兴。事实上,用户不得不找出如何“修复”数据,而不是按计划工作。因此,最终的利益相关者将无法按时获得他们的数据。此外,找出如何修复数据并不是一件简单的事情,因为接收到的数据可能已经被生成它的任何应用程序损坏。在这一点上,要遵循的过程取决于(数据)操作的组织方式;一个示例可能是创建一个带有评论“数据不正确,请尽快修复”的“事故”(工单)。最终,在我们的示例过程中,有一个数据管理员负责有问题的数据。数据管理员负责解锁用户(至少是如此),并分析其他潜在后果,这可能会导致更多的事故。要解决这种事故,数据管理员将不得不涉及负责生成数据的团队或个人。现在,时间线取决于那些需要时间来诊断症状并提出解决方案的人的可用性。嗯,这听起来比实际情况要容易一些。该事故需要重新分配给制造商,制造商可能已经忙于正在进行的项目,不太可能立即找到问题。这还假设您能够找到正确的制造商!相反,可能会联系所有制造商,可能会召集他们开会,以了解情况。他们将对“问题”提出质疑(例如,“你确定这是个问题吗?”),并要求更多细节,这将回到已经生气的用户(很可能不会非常合作)。因此,涉及到触及这些数据并被确定为潜在罪犯的应用程序的应用程序必须进行彻底分析,其中包括以下任务:访问生产数据,通过运行一系列探索性分析(例如查询,在Spark中进行的聚合计算)手动验证和了解数据问题。考虑到对数据的访问权限可能尚未被授予。重复此操作,通过时光旅行数据(如果可能)来识别问题何时开始出现(例如,delta表可以帮助进行时光旅行,但仍然需要找到问题出现的时间)。访问应用程序的生产日志(通过SSH登录计算机或从日志平台访问),分析其行为,甚至审查其历史记录(版本等),以防应用程序的业务逻辑发生了更改,这可能会影响结果。考虑到与数据访问一样,日志可能需要特殊权限才能访问。分析业务逻辑,以追溯到应用程序消耗的原始数据,以识别潜在的数据级联(问题传播)。重复此过程,直到找到根本原因,知道哪些数据和应用程序需要修复,最终执行回填(以恢复真相)。那么,坦率地说,因为时间至关重要(请记住,用户感到不满),根本原因可能不会被跟踪,而是将应用两种临时的最终补丁之一:运行临时脚本(也称为数据质量工具)来“修复”导致过程失控的数据(例如,超出数据生命周期的范围等)。更新其中一个应用程序以“清理”接收到的数据。这涉及在本地解决问题,可能会产生尴尬的副作用,例如删除或填充空值,这可能会改变变量的分布,如果数字增加并对下游产生副作用(例如,糟糕的决策)。事实上,这个称为“故障排除”的过程困扰了许多人,其中许多人甚至不需要为此事受到。值得注意的是,虽然故障排除正在发生,但问题的范围已显著扩大,如图1-9所示。而且,在此数据上检测到的问题可能对其他项目、用户和决策产生其他后果(请记住,数据级联!),这会进一步扩大问题的范围,影响数据的所有用途。另一个称为影响分析的过程与我们刚刚涵盖的故障排除有些相似。但是,它的目标略有不同,因为它旨在预防或传达问题。事实上,不仅目标不同,而且影响分析也要复杂得多,更加敏感,因为它需要请求所有用户的时间来检查他们的数据、结果、分析或决策。更糟糕的是,有些人可能会发现决策在更长的时间内都是错误的,可能与问题出现的时间一样长,而一切都发生得悄无声息。在这一点上,考虑到故障排除和影响分析,数据问题的范围如图1-10所示一样大。数据管理员必须尽快解决这个问题。这就是为什么我们称数据为“悄无声息的杀手”。在我们的示例中,它使每个人都变慢,破坏了所有的信任,并产生了压力、愤怒和焦虑,却没有引发任何警报或“异常”(如在软件中)。事实上,数据不会引发警报或异常——至少目前不会;然而,有一种方法可以启用这种功能,我们将在下一章中看到。在这个分析中,我已经强调了与流程中所涉及的时间和人员相关的挑战,以及在临时修补上浪费的大量努力,这只会产生更多的数据负债。尽管这些挑战的重要性不言而喻,但在问题被检测到的时候,我们失去的另一无法估量的资源是信任,既对数据又对数据团队。这种信任已经建立了几个月,甚至几年(例如,对于敏感的基于人工智能的项目),但由于没有预期到如何维护它,它就像一堆纸牌一样在几秒钟内崩溃。这种情景和由此产生的后果对整体情绪产生了负面影响,导致了高度有才华的团队成员的沮丧和离职。因此,我们可以在事故管理过程中识别出许多问题,如果在错误的时间提出,可能会破坏情绪:谁对这个问题负有责任?为什么是我: 发现了问题? 应该对此质疑?为什么用户告诉我数据似乎不准确?数据真的不准确吗?什么应用可能会引起这个问题?什么上游数据可能是原因?这个问题的未知后果是什么(即还有其他项目在使用这个数据,也可能会受到影响)?理解为什么会提出这些问题使我们能够确定问题的根源,这超出了任何数据问题及其原因。需要解决的挑战不是数据本身;问题在于在数据团队成员和利益相关者之间缺乏明确的问责和责任,这一挑战得以加强,因为缺乏对野外数据过程(即在生产中,使用过程中)的可见性。因此,当这些问题由数据团队提出或提出时,它们会阻碍创造价值的障碍,因为它们将产生以下成本:需要大量时间来解决,在此期间数据仍然无法被使用者使用。需要大量的精力,并意味着在源头解决问题的能力受到了削弱,而本地的“补丁”很可能会成为默认的解决方案。引发焦虑,加剧对可交付成果、数据和供应商的信任丧失。这就是数据可观察性发挥关键作用的地方——它有助于更好地实现对数据和数据生态系统健康状况的可见性,并更好地分配(分发、分散)责任和责任。扩展人工智能的障碍在前一节中,我们介绍了在扩展数据团队时许多人面临的挑战。在本节中,我将重点关注组织期望从其数据团队中获得的最关键成就之一,即扩展其人工智能能力。然而,这些与可见性有关的挑战,在问题出现时限制了价值的实现,也在实施人工智能时带来了重大挑战。根据Gartner进行的分析(见图1-11),公司在开发其数据和人工智能计划时面临的最重要障碍包括数据量和/或复杂性、数据范围或质量问题以及数据可访问性挑战。好消息是,数据可观察性有助于解决这些障碍,但在我们深入讨论如何解决之前,让我们更深入地了解主要障碍以及它们存在的原因。调查结果表明,技术相关的挑战占据了问题的29%,现有基础设施的复杂性占据了11%。有趣的是,将近一半(48%)的受访者表示,他们面临的挑战与缺乏可见性以及对负责解决问题的人员缺乏明确性有关。让我们来看看一些常见的障碍:安全性/隐私担忧或潜在风险或责任 几乎有五分之一(18%)的受访者在Gartner的调查中将这些问题视为实施人工智能的最大障碍。 风险管理安全性涉及了解数据将由谁、为何以及出于什么目的使用。 如果数据本身不正确,数据产生的结果也不准确,这可能会导致糟糕的业务决策,从而存在潜在的风险或责任。 最后,还有担忧结果可能不符合某些伦理约束。数据量和复杂性 这个问题对8%的受访者来说是最大的障碍。 数据的复杂性和大小需要进行大量实验,以了解并从中提取价值。 由于在执行相同的大型和复杂数据集上进行实验时缺乏关于执行的可见性,例如分析和整理,因此它们是重复的。 然而,这个过程需要时间和精力。数据范围和质量 对于6%的受访者来说,数据质量问题是最大的障碍。 如果数据质量未知,那么很难对数据产生的结果或最终结果有任何信心。治理问题或担忧、对AI的好处和用途缺乏理解以及难以找到用例 总共有16%的受访者认为这些问题之一是实施人工智能的最大挑战。 数据治理是一个重要问题,因为文档编制是手动且耗时的,这意味着它不总是做得恰当,因此其整体影响和价值并不总是明显。 但是,如果没有良好的数据治理,将影响输入AI算法的数据质量,而且如果没有关于数据质量的可见性,利益相关者可能会担心数据的安全性以及AI输出是否准确。在撰写本文时,关于人工智能使用的一种中断出现了,即公众的可访问性,这是基于一种称为大型语言模型(LLM)的模型的文本生成的生成AI的使用。 使用此方法构建的最受欢迎的工具是OpenAI的ChatGPT,因为它使任何不精通人工智能的人都能够与算法进行聊天; 意味着进行结构化讨论,其中每个新答案都考虑到了讨论的余下部分(上下文)。 讨论通常旨在基于某些说明(是的,就像编码一样)生成内容,例如博客帖子、定义、结构化的观点、提案等。然而,从其普遍可用性的最初几周起,已经突出显示了这些大型模型中响应的准确性以及数据如何使用的问题。 例如,意大利曾经禁止访问该服务,因为认为它可能有助于传播错误信息和偏见。 尽管经过验证其符合年龄政策后已重新开放,但对不准确或带有偏见信息的明确说明并未提供类似的信息。对于数以百万计甚至数十亿人使用的此类工具来说,设置正确的期望(例如准确性、及时性等)是至关重要的,以避免基于错误的“生成”信息做出不适当的决策或全球性的动态。 这不过是将本节讨论的内容提升到另一层级的一个例子,即不仅适用于足够成熟以定期用于业务决策的公司,这可能会影响其生态系统,而且还适用于小企业和个人。到目前为止,我已经带您了解了扩展数据团队和人工智能的旅程,我们已经确定了一些挑战,例如缺乏可见性(数据管道、数据使用等),以及关于责任和责任的清晰度的缺乏,导致了不信任和混乱,最终导致对数据的兴趣或信心的丧失。 这凸显了数据管理的整体重要性。 因此,下一节将介绍其在规模上的挑战,这将使一切都能够结合起来,并将数据可观性作为解决这些挑战的一种粘合剂。目前的数据管理实践存的挑战与任何公司转型一样,数据转型及其相关的数据团队的创建也引发了一个问题,即这些团队应该在组织中的哪个位置。是否应该有一个中央数据团队?它应该隶属于IT吗?或者,也许每个业务领域都应该有一个数据团队?它们应该隶属于IT,还是应该在每个领域都有一个团队?但是,它们如何进行协作,如何保持一致性和效率呢?等等,等等。这些问题以许多不同的方式得到回答;数据网(在第3章中讨论)是为数不多(甚至可以说是唯一一个)通过重新思考所有层面的数据管理,包括体系结构、文化和组织层面,来解决这些问题的方法之一。然而,不管数据团队在组织中的位置如何,让我们分析一下扩大这些团队对数据管理的影响。数据管理是一个广泛的主题,在文献中有着广泛的定义。对于这个分析,我将集中讨论DAMA(数据管理协会)在其《数据管理知识体系》第2版(DMBOK2)书中所定义的数据管理方式。在DMBOK2中,数据管理涵盖了许多领域,如数据架构、元数据和数据质量。所有这些领域都参与了提升数据价值,数据治理旨在规定必须遵守的政策,以确保高效生成数据价值,并符合组织的核心原则。这在框架中显示的“数据管理之轮”中得到了很好的体现,如图1-12所示。随着越来越多的企业变成数据驱动型企业,具有数据洞察力的业务团队和数据团队正在组织中蔓延。因此,数据文化不再局限于一个明确定义的专业中心,而是一种价值观,必须纳入整个组织的价值观中。因此,轮廓中划定的领域已经发展,以适应不同的情况和需求。自2017年以来,最突出的一个领域是数据治理,它在下一节中进行了讨论。例如,我们将讨论这种曝光如何影响其他领域,如数据质量和元数据。规模化的数据治理影响许多挑战源于数据管理实践和技术的演变,但我将集中讨论一个具体的挑战:如何在规模化的情况下控制数据文化的维持,不仅要尊重数据治理原则,还要尊重所得政策的定义和实施。数据治理确定了政策,每个领域都负责定义、规划和执行实施。然而,随着实施的进行,引入了许多不同领域之间的依赖关系,再加上一切都在扩展;没有一个协调一致、明确定义和全局控制的层面,任何出现问题的领域都可能导致系统崩溃。这就是为什么我建议不一定要改变数据管理的结构,如DMBOK2中所介绍的,而是通过一个额外的领域来扩展它,围绕着数据治理领域。这个新领域是数据可观察性,它将通过为组织提供衡量其系统内数据的健康和使用情况,以及整个系统的健康指标的能力,来扩展组织的数据管理实践。然而,数据可观察性的更详细定义将在“可观察性领域”一节中进行解释。将数据可观察性放在这个位置上使每个人都负责实施它。其原则和政策应该在所有领域中得以整合。结果如图1-13所示;然而,这并不意味着一定需要一个数据可观察性的角色、团队或部门。它陈述了在规模化情况下,控制必须变得明确,因为数据可观察性在较小规模下可以以更加临时的方式处理。在本书的其余部分,我们将探讨可观察性如何从IT DevOps领域延伸,并如何将其应用于数据和数据团队。此时,应该清楚横向挑战(数据管理)和局部挑战(在数据团队内部)已经成为近年来扩展数据战略和实现对数据投资最大回报的瓶颈。现在让我们讨论一个全面解决这些挑战的解决方案,即数据可观察性。数据可观察性来拯救到目前为止,我已经讨论了随着数据团队的发展所面临的挑战,以及在组织试图扩展其数据和分析战略时,缺乏可见性和对责任不清晰会产生的作用。然而,这并不是我们第一次遇到这样的挑战;最接近的例子是当IT迅速扩展时,这导致了DevOps文化的发展。DevOps多年来发展壮大,因为越来越多的IT实践(例如服务网格)需要更多的最佳实践和相关工具来有效支持它们。其中最著名的最佳实践示例可能是CI/CD(持续集成和持续部署),但在基础架构或应用程序层面上的可观察性也已成为旨在提供更多可见性并建立对其应用程序和系统的信心的任何架构的一部分,同时加快上市时间并减少停机时间。因此,相关市场已经出现并增长,以支持这些可观察性要求。各种公司已经开发了与DevOps相关的各种服务和技术,涵盖了IT环境各个成熟度水平(例如Datadog、Splunk、New Relic、Dynatrace等)。在IT领域,“可观察性”是指IT系统生成行为信息的能力,以允许外部观察者重建(建模)其内部状态。从而,持续的可观察性允许外部观察者持续地对内部状态进行建模。从根本上讲,观察者在系统正在运行时无法与系统交互(例如,我们无法登录到服务器);它只能观察到可以感知到的信息,因此被称为“观察”。现在,让我们讨论一下这些观察是什么。可观察性的领域IT系统天生复杂,由多个类别组成,这些类别的数量可以急剧增加,例如基础设施、云、分布式、机器学习、深度学习等。 然而,在本书中,我将避免过于详细,而是将可以“观察”的IT系统类别合并为几个领域之一,其中一个领域与数据相关,如图1-14所示。这些领域并不是完全独立的,因为它们在编码系统的复杂性时具有许多交互作用。这就是为什么Venn图似乎是最好的表示方式。在详细讨论“数据”领域和“数据可观察性”之前,让我们简要回顾一下其他领域:基础设施使用基础设施日志度量,您可以推断与内部基础设施组件相关的性能特征。当发生故障或未满足某些性能参数时,可以设置积极的可行操作警报。应用观察应用程序端点、版本、打开线程、请求数量、异常等,可以帮助确定应用程序的性能如何以及是否存在问题以及为什么存在问题。用户/目的了解和“观察”是谁在使用或实施应用程序,项目的目的以及项目的目标非常有用。这有助于了解最常见的项目或目标,检测重复的努力或组成专业中心。分析观察分析,从简单的转换到复杂的AI模型,有助于识别并了解数据的持续使用以及从中产生的见解。安全观察与安全相关的操作,例如授予访问权限或分配角色的修改,或有关哪些角色比其他角色更常用的度量,可以使安全系统的效率以及可能的改进领域可见。 其中一些领域在DevOps文献中已经得到了很好的涵盖。但是,我们专门关注数据可观察性。因此,我们得出结论,数据可观察性可以定义如下: 数据可观察性是系统生成关于数据如何影响其行为以及系统如何影响数据的信息的能力。 如果系统具有“数据可观察性”能力,那么系统就具有数据可观察性。 如果系统具有“可观察性”能力(基础设施、应用程序、用户、分析、安全和数据),那么系统就是(完全)可观察的。值得注意的是,Gartner已经定义了数据可观察性,其与我们的定义基本一致:数据可观察性是组织具有对其数据景观和多层数据依赖关系(如数据流水线、数据基础设施、数据应用程序)进行广泛可见性的能力,以实现在可接受的数据SLA范围内迅速识别、控制、防止、升级和纠正数据中断的目标。 数据可观察性使用连续的多层信号收集、整合和分析来实现其目标,并向其提供更好的性能设计和更好的治理建议,以匹配业务目标。Gartner的定义还讨论了数据可观察性的用途(例如,防止问题)。但我没有将这作为我的定义的一部分,因为我想关注它是什么,而不是它的作用(另外,我不将苹果定义为能够满足我的饥饿的水果)。 尽管如此,了解数据可观察性的好处非常重要。在下一节中,我们将更详细地探讨数据可观察性的用途。 然而,我和Gartner都同意,应考虑重要的“多层”或“多领域”组件。然而,我使用了术语“领域”,因为层意味着存在一定程度的独立性,而实际上并非如此。如何让数据团队立即利用数据可观察性数据可观察性的主要用例目前主要集中在数据问题管理方面。然而,随着数据不断增加和其用途的扩展,将会出现更多的用例。低延迟数据问题检测数据可观测性与应用程序(其使用环境)越同步,问题与其检测之间的延迟就越小。事实上,数据可观测性可以在数据使用的确切时刻利用,以避免监控与使用之间的任何滞后。这将允许您尽快检测到数据问题,帮助您在用户之前发现问题。以这种方式利用数据可观测性可以减少问题的检测时间(TTD),因为数据工程师会及时收到警报,因为任何数据使用问题都会在实时中观察到(即同步可观测性)。高效的数据问题排除在大多数组织中,当出现数据问题时,数据工程师需要花费大量时间来找出问题所在、问题的根源以及如何解决它。而整个过程的每一步都需要大量时间。有了数据可观测性,解决问题的时间(TTR)会快得多,因为整个系统都有可见性,这要归功于上下文可观测性,它提供了有关数据及其使用背景的信息。这使得数据工程师能够在问题影响到下游业务用户之前解决问题。预防数据问题当作为整个开发生命周期的一部分实施,包括生产环境时,数据可观测性提供了对数据和数据生态系统健康的持续验证。持续验证可以显著提高应用程序的可靠性,预防数据问题,降低总拥有成本。分散式数据质量管理服务级别协议(SLAs)可以像在IT DevOps中用于确保可靠性和其他关键指标一样,管理和确保数据质量。这种新型的数据质量管理需要数据可观测性,一方面,它可以提供同步(接近实时)和持续验证数据的能力,进一步提高了现有服务级别协议(SLOs)的效率。但更重要的是,另一方面,数据可观测性将允许在使用情境(例如应用程序)内以其使用的粒度定义SLAs和SLOs。这个能力解决了数据质量管理计划中一个最重要的障碍,即由所有者、数据监护人或主题专家(SMEs)定义SLAs的问题,因为他们很难定义一组可以满足所有使用期望的约束条件。因此,他们不能提出一个单一的(中央的)SLAs集合,因为每种用例可能会以不同的方式感知SLAs。数据SLAs的关键区别在于它们可以采用非常多种多样的形式,几乎无限; 例如,对于来自随机CSV文件的单个字段,您可以有关于最小表示、空值数量、类别数量、偏度、0.99分位数等的SLAs。因此,利用数据可观测性以上下文化的方式(使用情境)来分散SLAs的定义对于管理数据质量、定义责任和明确角色和责任的文化至关重要。补充现有的数据治理能力还记得之前提到的 DAMA-DMBOK2 数据治理框架吗?因为数据可观测性是该架构的一部分,并围绕数据治理的所有领域,它提供了对在数据级别互动的所有不同组件的可见性。这使得数据团队能够自动创建文档,使用相同类型的数据、数据存储和数据建模,同时提供更多关于不同数据模型的可见性,了解已发布到数据目录的数据,运行了哪些数据分析,以及使用了哪些主数据。未来和更远的未来通过更好地理解数据可观测性的不同用例,您现在应该能够理解数据可观测性如何用于优化您的数据系统以及数据团队。在接下来的章节中,我将更深入地介绍如何设置这些用例,并介绍捕获每个用例所需信息的最有效和有价值的方法的最佳实践。总结数据可观测性是现代组织的一项重要能力,这些组织依赖数据来实现其目标。随着数据团队规模和复杂性的不断增长,建立促进透明度、治理和数据验证的实践变得越来越重要。本书从数据可观测性的视角着重关注数据治理的技术方面。通过采用简单而强大的习惯,组织可以确保其数据工作在整个数据生命周期内可见、可控制和可管理。接下来的章节将探讨数据可观测性的核心原则以及使系统具备数据可观测性的最佳实践和方法。
0
0
0
浏览量315
攻城狮无远

第二章:数据可观测性的组成部分

正如在第1章中介绍的,数据可观测性是与其他领域(如应用程序或分析)相交的(IT)可观测性领域的一部分。在本章中,我们将介绍如何将数据可观测性及其交互添加到系统中,如图2-1所示。正如在第1章中讨论的,数据可观测性通过结合所有领域为观察者提供了更广泛的观察,以解释系统的内部状态。然而,如果不遵守一些预防措施,这种组合本身可能成为一个挑战。本章将深入介绍观察是什么以及它们应该包含什么。本章介绍了数据可观测性的三个基本组成部分:观察可访问的通道、描述观察结构的观察模型以及为您的数据系统提供主动能力的期望。数据可观测性信息通道数据可观测性的第一个组成部分是向观察者传递观测结果的通道。这些通道包括日志、跟踪和指标。这些通道适用于可观测性的所有领域,并且不严格与数据可观测性相关联。接下来的章节将定义可观测性的三个主要通道。您可能已经熟悉这些通道,但如果不熟悉,有数百本书籍和博客深入探讨了它们的定义。如果您想深入了解,请阅读《Distributed Systems Observability》这本书的第一章,该书专门介绍了这些通道。我还推荐阅读《Observability Engineering》的第二部分,以及《Cloud Observability in Action》。日志日志在任何系统中都非常常见。它们通常是文件中的文本行(称为日志文件),表示应用程序在执行过程中发生的事件。因此,日志是由IT系统生成的最常见的观测通道。它们可以采用多种形式(例如,自由文本行、JSON),旨在封装有关事件的信息。日志中的一行(通常,日志是一系列行的流)是记录操作的结果。记录是IT中几十年来的最佳实践,尤其是在基础设施、应用程序和安全领域。日志已被用于调试和优化IT系统或流程。甚至已经开发了日志的标准,如Syslog,它规定了日志的结构,并允许通过中央系统来控制异构基础设施。虽然日志对捕获系统行为的信息至关重要,但使用日志来重新创建多步骤的过程是困难的。这是因为日志包括系统内的所有活动,而进程的日志可能与其他并发进程交错,或分散在多个系统中(例如,分布式系统、服务网格)。跟踪优秀的书籍《可观测性工程》将跟踪描述为一种“基本的软件调试技术,通过在程序执行期间记录各种信息来诊断问题”。跟踪可以被看作是日志的一个特定情况 - 步骤的重连,这些步骤由一个进程执行。因为跟踪代表了同一进程的所有事件之间的关联,所以它们允许从日志中有效地派生整个上下文。这个概念已经被扩展,以满足分布式系统的需求,通常被称为分布式跟踪,其中跟踪事件(也称为跨度),如调用 web 服务、访问文件或查询数据库,会在不同的系统中产生,但作为全局跟踪(通常是一个 ID)相互关联。跟踪和它们的跨度是一种有效的方式,可以跟踪服务和服务器之间的操作,因为它们传递了服务器、服务和事件时间戳等信息。因此,观察者可以轻松浏览服务上的日志,在给定时间内分析他们需要生成观察结果的特定事件的日志。实际上,跨度还传递了可能对观察者来说有意义的日志,观察者可以当场分析,而不仅仅是用来重新连接跟踪信息。然而,一些系统可能尚不支持分布式跟踪;因此,跟踪在本地不被支持(不可观察)。这些系统可能已经被标记为不本地可观察的。在这种情况下,一种解决方案可能是利用现有的日志来解释它们的内容,以根据分散在日志行中的信息重建跟踪(这种可能性在第6章中有所涉及)。观察的最后一个通道是度量,与日志和跟踪密切相关,因为两者都可以包含度量。然而,由于其简单性,度量在与日志和跟踪相比具有重要优势。指标每个系统状态都有一些可以用数字表示的组成部分,而这些数字会随着状态的变化而变化。度量提供了一种信息来源,使观察者不仅可以使用事实性信息来理解,而且可以相对容易地利用数学方法从大量度量中获取见解(例如,CPU负载、打开文件的数量、平均行数、最小日期)。由于它们的数值性质,需要记录它们。这就是为什么度量曾经是日志的一部分,同时也出现在跟踪中。因为它们的用途非常直接,多年来我们看到了简化度量的发布或收集的标准,独立于常规日志和跟踪。然而,在图2-2中,我展示了一个所有组件都混合在一起的情况(这种情况经常发生);两个应用程序正在发送有关形成跟踪的事件的日志,而在非结构化(已记录的)文本中的某处提供了多个度量。这可以让您更好地理解为什么标准已经出现,以简化跟踪和度量的收集和使用。根据我所描述的内容,谈论日志、度量和跟踪似乎有点奇怪,因为它们都是由数据产生的(比如数字“3”不能记录、测量或跟踪任何东西;它只是一个数字)。这就是为什么将数据可观测性视为与其他领域交叉的领域至关重要,比如应用程序和分析,这意味着来自多个领域的日志、度量和跟踪产生的信息必须相互连接。让我们在下一节来解决这个问题。观察模型为了确保全面的数据可观测性,关键是要对来自不同渠道和格式、涉及多个领域的观察进行建模。在接下来的章节中,我将介绍一个模型,该模型对数据可观测性的维度进行了编码,并支持了我在第1章中提出的用例的解决方案。我想强调的是,我提出的模型是我在许多项目和用例中看到的可行模型。虽然随着时间的推移,模型可能会发生变化,包括更多的复杂性或更多的关系,但我打算详细展示,只要它们之间的连接足够充分,可以避免逆向工程或近似,来自不同领域的观察如何轻松地共同工作。这个模型至少会为您提供一个可行的起点,用于生成代表您需要观察的系统状态的信息。但是,您可以将其视为一个核心模型,您可以扩展以满足额外的、可能是定制的需求。在第3章中,我将解释您可以使用的不同策略,以自动生成模型的大部分信息,并在第4章中提供与多种常见数据技术(如Python pandas、Apache Spark、SQL等)相关的现成配方。在图2-3中,该模型被设计为一个实体图,其中每个实体提供了关于系统状态的一些信息,以及它们如何相互关联。链接清楚地标识了数据可观测性与其他领域之间的交叉点。例如,稍后在本章中,您将了解到数据源(数据可观测性)与可访问它的服务器(基础设施可观测性)之间存在链接;因此,链接本身是这两个领域之间交集的一部分。图2-3中提出的模型旨在结构化数据观察,以最大程度提高外部观察者对其整体可解释性,并提供一种通用语言,可以独立于参与观察的人员、时间和技术使用。为了提高模型的可读性,它分为三个空间:物理空间(在图2-3中位于左侧)将观察与有形实体相连接。即使服务器可能是虚拟的,用户可能是系统或应用程序用户,物理空间代表一个可以物理检查的空间。静态空间(在图2-3中位于中间)表示相对缓慢变化的实体,与动态空间形成对比。它表示系统的状态,可以手动构建。动态空间(在图2-3中位于右侧)是系统中正在发展或变化得如此之快,以至于无法整合、文档化或手动构建的部分。这是自动化甚至不是一个问题,而是一个必要性。在每个空间中,引入了实体以实现某种可观察性。让我们逐个介绍这些实体,以了解它们的目的以及它们与其他实体的关系如何在数据可观测性中发挥作用。物理空间在本节中,我将介绍数据可观测性领域中可以被视为有形的信息:服务器和用户(或它们的虚拟替代品,如容器和系统用户),如图2-4所示。服务服务器与运行被观察的IT系统的机器相关联。它旨在提供有关物理设备的信息。然而,在云计算时代,它也可以代表容器、虚拟机、Pod等,提供对基础设施观察的访问。因为我正在定义核心模型,所以将服务器组件保持简单,没有编码容器、虚拟机或集群等内容,这些可以作为模型的扩展。然而,关于服务器的核心信息必须帮助观察者进入其他系统(例如Datadog)以分析基础设施的状态。为了确保观察者具备这种能力,服务器必须传达的信息类型包括IP地址、容器ID、机器名称或任何能够适当识别底层基础设施的信息。在“数据源”部分,我们将看到服务器如何以及何时帮助观察者。用户在数据使用环境中,涉及多种类型的用户,以不同的方式参与其中。最明显的两种用户是数据用户和数据消费者。但是,我们还可以考虑数据所有者、数据架构师和在数据治理或更一般的数据管理组织中定义的其他角色。在数据可观测性中,我们将更具体地考虑数据生产者和数据消费者,通过他们与数据或系统的互动——核心模型显示了两个示例,将在“应用程序版本”和“应用程序执行”部分讨论。我们有兴趣观察用户,因为这可能是观察者可以用来改善对情况的理解的最简单的可操作信息来源。我们可以简单地询问用户有关数据问题或他们可以提供的任何有助于我们理解过程的信息;他们通常具有有关数据以及系统应该如何工作的某些知识。 与服务器一样,用户可以是虚拟的抽象,比如不包含个人信息的标识符(例如GitHub帐户ID)或所谓的系统或服务帐户,例如root或sparkuser,这些帐户向观察者提供了关于用户对系统行为(例如安全性)或其责任(例如隐私)的影响的提示。在后面与系统内的应用程序相关的部分,我将讨论随时拥有这些信息的优势。 尽管到目前为止,我已经将我们对物理空间的考虑限制在用户和服务器上,但其他元素,如地理位置(例如托管数据库的服务器的地理坐标)也可以成为物理空间的一部分。但是,我建议从一个简单的模型开始,以获得所需的核心可见性,然后逐渐添加其他用例。也就是说,地理位置信息非常适合用户和基础设施可观测性领域。现在让我们讨论静态空间,其中包括大多数专有的数据可观测性实体。静态空间物理空间允许观察者将与数据有关的事件与可以访问的“事物”联系起来(即使是虚拟的)。在本节中,我将介绍静态空间(见图2-5),它赋予了观察者分析系统在静止状态下的能力,或者在时间维度对其状态的影响不如物理空间大(例如,服务器的存在,用户的代码更改)。这些实体与数据和应用程序以及它们的结构演变相关,并在静态空间内发挥作用。数据源数据源提供有关存在哪些数据以及如何使用它的信息,无论这是一个简单的CSV文件、Kafka主题还是数据库表格。本地文件路径、主题名称和表格名称通常是数据源的一部分信息。数据源存在于静态空间中,因为数据源不会迅速改变。当然,文件可以复制或移动到其他位置,但是模型会将复制或移动的文件视为新的数据源。在数据库中重命名表格也类似于移动它。不过需要注意的是,重命名和移动数据源可能会带来重大后果。由于应用程序期望数据位于特定位置,因此在生产环境中,数据可能已被移动(例如,mv),应用程序可能会失败。在这种情况下,将会有多个数据源实体,每个数据源位置一个,因为将数据源从一个位置移动到另一个位置是将其复制然后删除。这个复制然后删除的操作实际上是一个转换,将由其自己的实体表示——这个衍生将在本章后面介绍。数据源还是一个观察的要素,位于数据可观测性领域的主要组成部分,并与服务器相链接,这是与基础设施可观测性领域的交叉部分。一些突出显示可以获得数据源可观测性的用例包括:数据迁移到/从另一台服务器数据重复或冗余数据在服务器上的可用性(例如,服务器迁移或升级)数据访问(即,如果机器与访问数据的物理连接)然而,数据不仅仅是一个来源,它还是多种类型信息的存储库。在下一节,我们将探讨这意味着什么。schema尽管前面介绍的数据源提供了关于如何访问数据的信息,但它并没有涵盖用户可能在数据中找到的信息类型。模式实体提供了有关数据源结构的可见性。模式是元数据的一个重要组成部分(其他组成部分可能包括定义、标签等),它传达了关于可用于数据源的字段(或列)的信息,包括那些可能是深层的(嵌套的)。模式的每个字段至少都有一个名称和一个关联的类型,该类型可以是本机的(例如,字符串、整数、地址)或概念性的(例如,地址、SKU_ID),也可以两者都有。尽管模式位于静态世界中,但它仍然是一个不断变化的实体。对于数据库数据源,它提供了一个动态示例,以表格的形式,可以在其中添加、删除或重命名列。但是,模式使跟踪这些更改相对容易。模式与数据源相关联,因为它允许观察者识别数据源中可用的信息类型。由于数据源的模式可能会发生变化,模型可以通过以两种不同但非排他的方式保留所有模式版本来编码这些变化。每种方法都支持有趣的用例:数据源模式的每次修改都会创建一个新的模式实体,以及变更或版本的时间。以下是观察者如何利用这些信息的示例: 立即查看新旧模式之间发生了什么变化。 验证新模式是否与新要求匹配。 察觉由于转换错误而导致的未经计划的更改。数据源的每个数据使用者都会创建一个与其自身数据源使用相关的模式实体。这使观察者有机会: 意识到由于缺少信息而导致其使用失败,并提出信息请求。 确认可以用来改进其结果的新信息。 适当地更改其使用方式以匹配重命名的信息。有趣的是,根据语义,模式是一个观察,要么是数据可观测性的(a)主要组件的一部分,要么是(b)数据和分析可观测性之间的交叉点。在模型的这一部分,我们可以恢复数据的相当大部分可见性。然而,我们仍然缺少大量关于数据在系统的其余部分,特别是在应用程序和分析中的运行方式的信息。本节的其余部分将专注于实现这两个领域内的数据可观测性。血缘血统实体,更准确地说是技术数据血统,可能是最难发现的信息,因此可见性最低(截至撰写本文) - 结果,它也是最少被使用的观察,因此我们仍然有很多需要从中学习的地方。关于如何使用血统的文献很多(本节末尾将列出一些示例),但关于如何具体收集它的信息很少。血统(literal translation: 行 + 年龄)是指数据源之间的直接连接。数据源的血统是其一组直接上游数据源及其血统。技术数据血统可以位于数据源级别或字段级别。我们应该考虑到,与数据源级别相比,模式级别更复杂,因为它提供了有关数据源模式如何相互连接的信息。这导致了一个连通图,其中数据源的每个字段都有其自己的血统,将其与上游数据源的字段连接起来,如图2-6所示。数据源之间的连接是通过执行输入(源)到输出(结果)的转换的应用程序创建的。这些转换可以包括以下事件:由Java Web服务器发出的SQL查询,将表格(输入)转换为JSON HTTP响应(输出)。由报告工具生成的SQL,从一系列表格(输入)计算和呈现(输出)关键绩效指标。Spark作业从多个Parquet文件(输入)创建数据仓库中的视图(输出)。Kafka应用程序使用流API消耗主题(源)并生成另一个主题(输出)。机器学习脚本训练模型(输出),使用从CSV文件(输入)创建的特征矩阵。即使人类将数据从纸上的表格(输入)复制到Excel表格(输出),也算是一种血统。血统中包含的信息本质上是输入和输出数据源和/或模式之间的映射。为了将此映射与数据源和模式编码在一起,图2-3中呈现的模型显示血统与模式相关联,从而实现以下用例:立即了解遵守转换所需的最小信息子集,有助于优化。通过沿着列维度对数据的子集进行分区,从而实现进一步的处理优化。将转换与数据源存储和位置分离,以便更轻松地进行变更管理(例如,从在数据湖中处理文件的代码到在湖仓库中的表上执行分布式SQL)。从输入和输出之间的依赖关系生成语义(用法)信息。尽管在概念上很简单,但血统具有以下特点,因此变得复杂:难以记录:所有数据源都必须记录,并且必须应用于所有字段。可能会更改:即使是对SQL查询的简单修改也会更改数据血统。数量众多:公司的数据上不断进行许多转换。即使是一个简单的CRUD应用程序也由许多转换组成。尽管存在这些复杂性,我将血统信息留在静态空间的一部分,因为血统依赖于将数据转换为创建所需输出的人,而静态空间为我们提供了观察这些变化的能力。在考虑血统时,我们还可以看到它与以下方面非常匹配:分析可观测性 血统包含了分析结构以及如何使用数据进行分析。用户/目的可观测性 血统编码了可以使用数据源实现的内容。应用程序可观测性 血统由应用程序执行,数据应用程序存在来执行血统。在讨论血统对用户/目的可观测性和应用程序可观测性的贡献时,还有很多探讨的空间,我们将在后续进一步讨论。应用应用程序提供了数据可观测性的另一个方面,因为它是提供数据可见性的关键。事实上,应用程序位于所有信息的中心:它们是用户的产物,是分析的容器,是数据的消费者和生产者,它们在基础设施上运行,同时配置或实施它们的安全性。必须记录有关应用程序的信息,以便观察者可以理解哪个系统正在运行以及相关的逻辑。我倾向于将应用程序视为通往可观测性其他领域的大门,因为应用程序在所有方面都相对中心。事实上,提到一个应用程序可能会很简单,例如:Spark作业的groupId:artifactId。Tableau工作簿的路径。Python脚本的组织/模块名称。一个编排器描述符(例如,GCP Workflow)中的步骤URN,用于调度SQL。图2-3显示了“应用程序”没有指代任何具体内容,但因为它仍然位于可观测性模型的最中心位置,所以必须要执行。我将在“应用程序执行”中解释如何执行此操作。有人可能会认为血统应该直接连接到应用程序。例如,SQL在Oracle中连接两个表并在Snowflake中创建视图是在将运行它的应用程序中编写的,因此在这两个实体之间创建了直接连接。然而,我认为这种观点在静态空间中过于强烈地将“数据”和“应用程序”可观测性领域耦合在一起。在一些繁琐的情况下,这种直接连接是完全不合适的:血统修改:当血统被修改并创建了新的血统时,初始血统不再是应用程序的一部分。在这种情况下,血统应该与应用程序版本连接(参见“应用程序版本”)。血统在运行时生成:当血统在运行时生成时,例如动态生成的SQL,所有SQL都不存在于应用程序中,直到运行和使用。在这种情况下,血统将连接到应用程序的运行时(执行)(参见“应用程序执行”)。当然,应用程序与血统并不是完全断开的;它只是不会直接连接到血统。相反,它通过两个组件,应用程序存储库和应用程序版本,扮演了嵌入大部分观察模型的容器的角色。应用程序存储库应用程序存储库通常被认为是在图2-3中所呈现的模型中的离群值,因为它提供了应用程序源代码的存放位置等信息(例如,源代码、报告配置等)。乍一看,似乎与数据之间没有或几乎没有关联。然而,应用程序存储库提供的信息旨在编码数据可观测性与应用程序可观测性之间的关联。由于应用程序的源代码可能会移动,应用程序存储库可以表示其最新位置(希望不会同时有多个位置),并暗示其以前的位置。我选择将这种时间性的概念编码为不同的实体——应用程序版本中。存储库的主要用途之一是为观察者提供应该分析系统的确切位置。例如,如果某个应用程序在生成数据源时出现问题,知道应用程序存储库的位置会告诉观察者应该查看的确切位置。然而,他们还需要知道应用程序的版本,以便适当地分析情况——尤其是在问题发生时的版本,这就是应用程序版本有用的地方。应用程序版本应用程序版本是静态空间中的最后一个实体,也是最关键的一个,因为它是其他实体之间的粘合剂。应用程序版本是观察模型中的入口点,用于指示在出现与数据相关的问题时应用程序的确切运行版本。版本可以是:版本号(例如,v1.0)Git 哈希(例如,d670460b4b4aece5915caf5c68d12f560a9fe3e4)时间戳(例如,1652587507)版本允许观察者浏览应用程序仓库中正在运行的应用程序的历史记录。版本还在进行根本原因分析时非常有用。如果版本太旧,可能表明应用程序尚未更新。如果版本未知/更新,可能表明引入了逻辑错误或未知的转换。您将看到,版本与血统或更接近核心数据可观测性实体(例如,数据源、模式)没有直接关联。因此,为了在应用程序和数据可观测性之间建立桥梁,我们必须考虑正在执行、读取、转换和生成的内容。这就是动态空间的作用。动态空间动态空间(参见图2-7)允许观察者利用使用数据的系统行为。其主要目的是在运行时(即数据运行时上下文)创建可见性。此外,环境的状态对于运行时发生的事件非常敏感,例如由于现实世界事件而导致数据更改,由于虚拟机重启而导致应用程序被终止等。换句话说,它非常动态和不可预测(至少是确定性的),这就是我称之为动态空间的原因。动态空间的组成结构如下所述,从应用程序执行开始。应用程序执行应用程序执行提供给观察者有关正在运行的应用程序及其对数据的使用的信息。如前所述,应用程序是打开观察模型中其他组件更多可见性的工具,因为其源代码描述了使用数据的分析(例如,数据血统)。通过捕获实际运行情况及其执行,应用程序执行向观察者传递信息,例如:ID:允许观察者确定哪些应用程序可观察信息是需要审查的(例如,在应用程序日志系统中)。启动日期:允许观察者了解上述信息的相关期间。当前配置及其在执行之间的更改:快速识别不正确的设置。应用程序执行直接连接到其他实体,例如应用程序(是什么)、服务器(在哪里)和用户(谁)。因为观察者了解应用程序执行及其与应用程序内部某些数据使用行为的连接(请参阅血统执行和数据指标),观察者将能够:连接到服务器以分析它可能如何影响执行,或者更好的是,使用服务器的可观察性信息来获取此可见性,而无需连接到它(例如Splunk、Elastic)。确切知道哪些应用程序正在运行或用于在服务器上运行,并分析其性能或负载。轻松快速地识别执行操作的人员,因此可能能够帮助描述情况。但是与应用程序的连接如何?图2-3中呈现的模型不将应用程序执行与应用程序相连接,因为观察者需要了解有关应用程序执行的最重要信息是其版本。此外,由于版本涉及源代码,因此可以推断它也涉及应用程序。拥有关于正在执行的版本的信息非常强大。此信息提供了即时的见解,例如部署的版本是否不是最新版本,或者哪些更改可能导致问题(例如git diff)。此外,请考虑应用程序可能会在不同服务器(例如,环境)上的不同版本中执行,使本节中描述的信息变得更加有价值。血缘执行血统执行实体可能不太明显,但也是最复杂的,因为它可能会有大量的实例。 血统执行简单地提供有关血统执行的信息。因此,它可以代表许多不同的事物,例如SQL作业的执行、Python转换、复制命令、机器学习训练等。血统执行旨在向观察者提供有关血统行为的明确信息,包括连接到数据源发生的频率和时间等信息。我不会说血统执行在本质上传达了大量信息,但它是观察模型的基石。没有血统执行,第1章中介绍的数据可观察性的大多数用例都是幻想,或者依赖于最佳努力的协调,这不可避免地会导致虚假的相关性(主要是在协调数据和应用程序可观察性领域)。血统执行在与其他核心实体(如血统和应用程序执行)的连接方面信息丰富。如果与血统的关联清晰,观察者就可以知道哪些血统是可观察的,并可以进一步分析这种关联。 血统不能单独执行,因为它只表示数据源(模式)之间的连接,而不表示这些连接是如何实现的。这些信息在代码中可用,可以使用应用程序版本观察。例如,数据转换可以在Spark RDD中实现,然后在Spark DataFrame中实现,然后在Spark SQL中实现,然后在Snowflake中实现为物化视图,依此类推。为了理解血统(转换)是如何执行的,观察者必须使用前一节介绍的应用程序执行的信息。这就像血统是由应用程序自身启动的子程序(或子应用程序、子进程)执行的,例如,当从Kafka消耗某些事件时,应用程序会在表中创建一个条目,然后执行相关的血统。 此外,应用程序不限于运行单个血统,或者每个血统只运行一次,甚至总是运行相同的血统。由于这种多样性,使观察者能够看到这种极端(非确定性)情况至关重要,因为不可想象这种复杂性可以保存在一个人的大脑中。 由于血统执行在模型中的核心地位,观察者获得了对图2-8中显示的内容的整体可见性(但不限于此)。在图论中,我们将其称为遍历(在遍历图时收集信息)。我将其称为精神震撼,因为观察者可以轻松地深入浏览信息的许多层,如图2-8所示,从血统到更改其中一个涉及的模式的用户。 但是,我们可以通过数据度量进一步扩展这种可见性。关于数据度量的一个令人兴奋的事情是,它有潜力以自下而上、可扩展和实时的方式支持数据质量。数据度量数据度量是可观测性中度量规格的一部分。在讨论可观测性时,数据度量通常是最常疑似的对象,因为它们在分析中很容易被利用来推断或预测(未来或依赖)事件,例如,使用它们与多元时间序列分析、马尔可夫链等的时间依赖性。在数据可观测性的上下文中,我专注于我们可以直接与数据相关联的度量,例如描述性统计。然而,数据度量实体带有一个细微之处,可能对其有巨大的影响——度量始终与其分析使用(例如,转换)相关联。数据度量的作用是向观察者提供关于在其消耗或生成期间数据的值在某些条件下是什么样子的信息。至少可以考虑三种度量类型:单变量与所观察的数据源的一个字段/列相关的度量。例如:列的描述性统计(例如,最小值、最大值、空值、级别)分布(例如,密度、偏度)公式(例如,平方和、乘积)多变量多个字段/列的组合的结果。例如:公式(例如,两个字段的乘积之和)组合(例如,科尔莫哥洛夫-斯米尔诺夫)外部与其他数据源的字段进行聚合的结果。这种度量非常复杂,甚至可以被认为是边界度量,因为它与关键绩效指标(KPI)或结果过于接近。重要的是要注意,这些度量不旨在取代数据项目(例如,数据科学、分析报告)分析阶段所需的探索性分析。这些度量的目的是允许在其使用的上下文中理解数据,而不是在之前。换句话说,数据度量的主要用途不是为潜在用户提供他们可能发现的初步见解。尽管如此,对于数据度量的潜在用户来说,获得对数据可靠性的信任至关重要,因为如果数据源在其他用途上是可靠的,那么他们自己的数据使用也很可能是可靠的。请记住,信任(特别是与数据质量相关的信任)需要时间,当数据的可靠性与真实且可理解的用途相联系时,信任会更加坚定。因此,数据度量必须与暴露给观察者的数据源相连接,以展示行为(正确术语应该是状态)。在图2-3中,我展示了数据度量与模式相连接。这是因为度量高度依赖于数据源在使用过程中存在的字段。最后,这些度量必须与数据源的使用相关联,由线性执行表示,该线性执行指示了数据源组合何时执行。这种连接具有前所未有的观察模型能力,使观察者能够了解数据系统的状态。例如,它允许观察者执行以下分析:数据转化的百分比及其波动对输出的影响在分析中涉及的数据子集(通过筛选器)的统计变化的影响用于机器学习训练的数据使用时间段的增加及其对性能的影响(例如,一年与18个月的数据)。这些是数据度量和数据可观测性模型的关键组成部分,它们使观察者能够更深入地理解数据的状态和行为。到目前为止,我已经向您介绍了一个系统需要产生的观察结果,以被视为数据可观测。现在,我将扩展这种能力,以包括系统本身、其创建者或用户对数据的使用方式。期望到目前为止,我们已经介绍了系统需要生成的观察信息的类型和实体,以便让观察者能够了解数据在系统中的行为。在这一部分,我将讨论数据可观察性的第三个组成部分,它涉及在数据不断满足其内容(例如,年龄可用,至少有10,000行)或行为(例如,从收入中减去支出是正数)的假设时创建可见性。这些假设通常来自业务规则、数据探索或仅仅是数据定义,它们指导了必须应用的转换。因此,当执行这些转换的应用程序被部署时,这些假设可以变成期望,因为它们似乎在开发过程中已经得到足够的验证,可以继续进入生产环境。正如你将看到的,这些期望可以来自数据生态系统的不同部分(例如,用户、应用程序等)。如果期望对观察者可见,它将为观察者提供关于观察到的数据状态与其预期行为的对比的关键见解。通过设置具体的期望,就像在软件开发中的测试一样,观察者可以更好地了解与期望一致的数据行为。此外,如果某些期望未能实现,观察者立刻知道需要关注的地方,就像测试报告告诉软件开发人员需要集中注意力的地方一样。因此,它们提供了关于观察者期望发生的事情的可见性,代表了积极或消极的事件。例如,知道一个期望,比如“地址总是以数字开头”,对99%的条目来说是真实的,可以确保不能使用的数据只有1%。没有事故。它只为观察者提供两个信息:期望本身以及它是否为真或为假。另一方面,如果已知一个转换会失败,如果辅助数据不完整,那么当它变为假时,可以创建、触发一个事件,并做出相关的决策(例如,根本不执行转换)。在设置期望时,您将希望既为数据的行为制定规则,又检测异常。下一节将讨论期望的这两个方面,以及应用程序如何成为其自身的观察者,并对数据行为的状态做出决策。规则规则是开发和监控中常用的工具;它基本上是一个函数,用于评估从数据中提取的信号(例如,行数、字段列表),并与一系列检查(例如,“> 10,000”)进行比较,这些检查编码和验证了期望是否得以满足。如果当前信号通过了规则,它将返回一个“true”响应;如果没有通过,它将返回一个“false”。函数的逻辑通常非常简单,因此很容易将结果与输入关联起来。例如,使用机器学习模型返回结果的规则更像是异常检测器而不是规则。因此,用于数据可观察性的规则将使用由模式、数据度量、血统等传达的信息创建。例如,如果数据度量报告字段“年龄”的空值数量,一个规则可以是“年龄”的空值数量必须低于10,或者低于5%,甚至不能在两次执行相同血统的情况下增加超过0.01%。另一个使用模式信息的示例是“年龄”字段必须存在且为整数类型的X。实际上,规则分为两类:单状态规则这些规则仅在给定时间的系统状态信息上执行,就像示例中的要求“年龄”的空值必须低于10一样。状态信息也可以组合以创建内在检查,例如将空值数量与总数进行比较(例如不超过10%)。多状态规则这些规则需要多个状态,例如一个或多个时间段的状态、连续状态或抽样状态。多状态规则甚至可以包括从可用状态中随机抽取的状态列表。每个类别都可以分为单变量和多变量,当一个规则使用来自同一状态的一个或多个观测时。图2-9说明了这四种变体。虽然观察模型的实体是根据可用信息计算或提取出来的,但规则则不太具体,需要更多的洞察力来创建。所需的洞察力可以来自多个来源,如:人类知识和经验系统的历史与其他系统的相似之处让我们看看创建规则的两种方法。明确的规则明确的规则是由用户创建的,作为他们正在构建的假设的表示,他们期望这些假设在他们的应用程序运行并在生产中使用时保持为真。这些假设主要来自以下渠道:经验:一个人对数据或所涉及的用例的经验是生成规则的绝佳方式,因为它基于过去的经验或应用知识。例如,业务规则以不同方式经验:作为对业务应该如何进行的限制,作为断言业务结构的一种方式,或作为控制或影响业务行为的方式。我还指的是基于多年经验形成的直觉或直觉,这些直觉或直觉是基于用例或实际经验(如网络等)形成的假设。但这并不意味着这些假设总是可靠的。一个人可能存在偏见或误解整个情况。然而,考虑到这些“直觉”假设仍然是值得的,因为它们比没有强得多,更重要的是,它们表达了用户的期望。然后,如果这些规则没有得到满足,由于系统的可观察性,团队可以进行分析以确定规则的有效性,并可能舍弃或调整它们。探索:在使用数据创建管道、报告、特征和其他类型的数据转换时,人们将学习数据,或至少更好地理解数据。因此,将形成并将这些假设整合到基于这些学习的应用程序中。例如,如果数据没有重复项或营业额始终大于零,您可能会认为这些条件是真实的。然后,您可能不会明确地制定其规则,或者可能不会在要实施的业务逻辑中指定它们,尽管这些假设是规则的绝佳来源。帮助制定规则的这一过程的一种方法是创建数据概要文件(使用分析器)。分析数据会生成一系列从提供的数据中推断出的静态规则。但是,这些规则需要谨慎使用,并经过仔细审查,利用团队的经验来选择最合适的规则,同时避免引入规则过于接近手头数据并因此可能在生产中迅速失败的情况。使用数据分析仪的使用将在第6章中进一步详细介绍。它们将被称为扫描仪。发现:当应用程序和数据正在使用时,可以发现新的案例,例如未知的未知(意外的案例)或边际案例(未考虑业务逻辑)。一旦发现,它们通常被视为需要分析和最有可能修复的事件。如第1章所讨论的,必须进行多个流程,例如根本原因分析、故障排除和影响分析,以确定和修复问题。流水线的每个阶段都可以发现(从历史中学到的)新规则的机会,包括在管道的不同阶段之间的事件之间的交互或因果关系(强、一致且不变的相关性)。还值得注意的是,随着世界的不断变化(例如,产品被抵制、法律被调整和平台被升级),规则可能已经过时。因此,必须考虑对这些规则进行维护。我经常鼓励团队至少在使用或生产数据的应用程序的每个新版本中迅速审查规则,以便进行验证。验证规则需要一个简单的反馈流程;但是还有其他选择,例如使用辅助规则。辅助规则在前一节中,我介绍了完全是人工分析结果的规则。在这里,我将介绍一种替代方法,允许发现规则。 虽然规则不能基于非显式行为,但它们仍然可以通过简单的分析(例如,启发式方法)或甚至是学习(例如,关联规则挖掘)来发现。这些规则是在观察的协助下创建的,因此它们是“数据驱动的”。辅助规则的强大之处不仅在于能够创建新规则,还能更新现有规则,甚至是显式规则。如果有效地完成,辅助规则可以减轻长期维护规则所需的负担。 引入辅助规则到数据可观察性系统的方法是保持人与系统的互动。因为规则在结构上是全面的,所以可以由拥有知识来审查、接受或拒绝这些规则的人进行审查。以这种方式验证规则对于提高规则的信任级别以及最终提高规则检测的问题准确性非常重要。例如,辅助系统可以估计(例如,基于自相关性)某个值似乎在19,945.62左右,并且标准差在783.09左右,因此可以提出一些规则,例如遵循某些分布,这些分布也可以根据观察估计或作为观察本身(例如Kullback–Leibler分布与一些分布的差异)。只要我们有足够的观察数据,可能性是无限的。也就是说,随着时间的推移,辅助规则还应该利用在发现过程中积累的知识,即在事后创建的规则,这些规则提供了如何预测未知事件的信息。推荐和更新规则的机会是自动异常检测无法比拟的优势,我将进一步讨论这种方法。然而,这种异常检测通常不涉及人类,它们的控制程度较低,而且它们的随机性不会产生关于受控制的内容的高度确定性。为了提高和加强这种感觉,这正是数据可观察性的目标,我将首先讨论规则与服务级别协议之间的关系。与服务级别协议(SLA)/服务水平目标(SLO)的关联服务级别协议(SLAs)和服务水平目标(SLOs)已经存在了相当长的时间,用于建立合同,并且是避免关于服务预期质量的主观争议的一种方式。它们有助于建立和维护生产者和用户之间的信任关系,这对于双方都能够自信地参与有价值和可持续的关系至关重要。这种关系也存在于数据领域。数据团队在某种程度上是一种服务;数据团队为专用用例生产数据,或者将其提供给未来使用。因此,数据团队和数据消费者/用户都有期望,即使这些期望没有被明确表达、没有被记录下来,并且在涉及数据时也没有被直观地共享。这已经导致了我在第一章中提到的问题。SLAs和SLOs是消除这些问题的良好解决方案。数据的SLAs是参与方之间的合同,包括可以满足的期望,如果不能满足,就会应用罚款或其他处罚。罚款/处罚的重要性可能与在期望未被满足的情况下经过的时间长度一样重要。因此,“检测时间”和“解决时间”等运营指标对于可持续成功至关重要。要在数据中建立SLA,需要获取用户的意见,以确保服务质量足够高,使他们实际使用数据。在SLA中考虑越多的用户,服务就越好,服务水平就越高,关系就越好——只要确保期望的相关成本是可以接受的。在制定数据的SLA时,需要考虑以下几个复杂因素:如何定义约束 用户可能很难定义重要的约束,因为可能会有大量可能的约束条件。考虑到数据源中的每个字段都可以有10个以上的关联指标(例如,对于年龄字段,可以有最小值、最大值、平均值、标准差以及分位数、估计的分布、空值、负值等)。数据的使用会有所不同 每个用户对数据都会有不同的用例,因此会有不同的期望和约束。大量用户 随着自助数据和分析的普及,用户数量不断增加,从而创建了更多的数据用例和约束。这就是数据可观察性至关重要的地方。数据观察可以用作服务级别指标(SLIs),通过提供有关数据和用户行为的足够信息,可以识别、辅助和维护规则。由于生产者和消费者都生成这些观察结果,因此更容易就SLIs中的哪些关键绩效指标应该用于确定数据团队可能满足哪些SLAs达成共识。这将为所有SLAs设定标准,同时保持为其他SLIs在SLAs发现过程中稍后使用的机会敞开。同时,生产者和消费者都可以将它们用作SLOs(或仅作为信息来源,例如通过通知系统)。没有数据可观察性,如果考虑到可以在100多个列上计算的指标数量,乘以不同检查的数量,那么范围就太广了。这是如此之大,以至于对于双方来说,没有充分的证据证明它们的有效性,以及一种有效地随时间改进它们的方法,这似乎令人望而生畏,甚至不可能承诺一部分期望。自动异常检测规则是确保数据符合期望的一种强大方法,尤其是在数据使用的上下文中。然而,规则依赖于人们来创建和验证它们(至少我强烈建议如此)。对人们时间和可用性的依赖无疑是数据验证与数据规模成倍增长的瓶颈。已经达到高度成熟水平的公司需要考虑这一点。此外,辅助规则在结构上有一定的约束,因为它们必须是明确的并且可以完全解释,这可能是相对于其他更不透明的解决方案(如基于学习的规则)的一个弱点。考虑到规则的目的是拦截未达到的期望,我们还没有讨论的另一种方法是使用可用的数据可观察性信息进行自动异常检测。异常是与预期状态不同的系统状态。这是显式编码为规则和隐式发现为不符合期望的期望之间的联系。在我们的情况下,系统状态对应于基于核心观察模型的数据可观察性信息的历史记录。因此,这些状态也可以看作是具有许多随机但相关(非独立)变量的随机过程(每个实体的实例都可以视为一个变量)。因此,自动异常检测是利用一些基于学习的方法来推断或预测何时应将状态视为异常的过程。可以使用的方法有很多,包括机器学习:监督学习:这需要对数据可观察性信息(或状态本身)进行标记才能正常工作。因此,需要花费时间来收集足够的数据来训练算法,以及为团队提供标记异常的高准确度。无监督学习或统计分析:无监督学习方法可以帮助根据其统计属性识别异常。例如,统计学习方法可以自动识别异常值,并假设异常可能显示异常行为,从而在不需要详细的先验知识的情况下检测潜在的质量问题。增强学习和主动学习:这种方法非常先进,仍然需要团队的一些时间。代理需要进行大量模拟以进行验证,但主动学习实质上是依赖人在环中的。我的意图不是深入讨论每种方法,因为这至少需要另一本书,而且每个主题已经有足够的论文和书籍了。然而,让我强调一下,尽管这些方法在理论上可以扩展,但它们仍然有一些不足之处:冷启动问题:为了使异常检测高效运行,需要数据和人来确保学习阶段的可靠性。黑盒担忧:迄今为止介绍的方法自动生成异常,但对于原因几乎没有信息。因此,团队必须重构上下文并理解原因。性能挑战:异常检测不能百分之百准确。因此,需要控制团队的性能,以避免警报疲劳,即团队开始忽略检测到的异常。回到黑盒担忧,值得注意的是,已经付出了大量的努力来使机器学习模型具有解释性(这在我的谦虚意见中是分析可观察性的一部分)。这个挑战并没有完全解决,尤其是对于深度学习等方法来说。然而,密切关注这种能力的发展是减少黑盒担忧影响的必要途径。我将在这一部分的最后提到另一种方法,这种方法很少被考虑用来解决黑盒担忧,它提供了规则和异常检测的有趣组合——有限状态机的学习。当数据可观察性信息不断增长,尤其是如果触发的规则也被标记为真阳性或假阳性时,这种方法可能是有希望的。防止垃圾输入,垃圾输出数据社区应该为反对“垃圾进,垃圾出”(GIGO)的借口而战斗的一个原因是,GIGO只是一种借口,而不是一种解释。当有人生气并询问为什么他们收到了无关紧要的信息时,唯一提供的答案是信息无关紧要,因为最初的数据本身就是无关紧要的。首先,它并没有解释任何问题,而是被视为对问题重要性的回避,不会建立信任,因为制作者并不觉得有责任尽力避免这种情况。坦白说,我们都曾经历过这种情况,而且都曾身处其中的双方。换句话说,这是一种“推卸责任”的做法,害处大于好处。与之相反的观点是,如果一个应用程序能够意识到垃圾输入,它就应该禁止生成垃圾输出。要做到这一点,应用程序需要能够执行以下操作:评估提供的数据是否为垃圾将经分类的数据标记为垃圾但为什么这种行为如此常见是一个有趣的问题。答案必须与在生产中的行为缺乏可见性以及对问题重要性的了解有关。此外,这种情况极度时间敏感,通常涉及一些权力不平衡(管理者-贡献者,买方-卖方,用户-提供者等),这使得情况非常尴尬。此外,受到这种批评的人有一种天然的倾向,认为他们必须能够解决所有情况并避免所有问题。让我们明确一点:这不是人们的期望。人们期望的是能够分析问题,并在理想情况下能够预见、恢复并保持信任,而不是避免一切!当数据由外部来源提供并且我们对数据几乎没有或根本没有影响时,理解这一点尤为重要,在这种情况下,似乎不可能避免一切,同时又不能责怪制作者(我的意思是,指责制作者)。好消息是,数据可观察性的第三个组成部分,即期望,满足了这些确切的要求。通过实施数据可观察性,以下是可能的情况:输入数据在应用程序生成其结果之前被标记为垃圾。然后,应用程序可以选择: 不继续执行,避免生成垃圾输出 尽力清理数据,确保清理过程是可观察的,然后继续执行根据结果的资格,将输入数据标记为垃圾。然后,应用程序可以选择: 回滚数据并记录 记录数据为垃圾的原因,因为事后发现垃圾 与第一种情况一样,尝试清理传入数据或其输出,仍然确保这些过程是可观察的这种逻辑可以使用两个不互斥的过程来实现。第一个是使用条件。前置条件和后置条件条件语句在编程中常常被使用,比如在实现一个HTTP端点时。例如,如果端点的业务逻辑期望接收一个以UTF-8编码的JSON有效负载,其中包含在customer顶级字段下的非空整数age字段,并且这些条件没有得到满足,应用程序可以决定向客户端返回错误,或者在某些情况下,比如在继续之前重新编码,可以宽松处理编码。此外,如果HTTP端点的副作用是更新表并获取数据的当前状态,但它获取了多行而不是一行,它可以采取诸如回滚、删除或仅返回错误的操作,让客户端了解其可能发送的垃圾数据。我刚刚描述的内容对于任何程序员来说都非常自然:它甚至不仅仅是最佳实践;它是直观使用的。因此,想法就是在创建数据应用程序、流水线等时直接使用这种模式。 在数据应用程序中直接实现前置条件和后置条件的有趣优势是,它受到良好的保护,无需依赖其他外部组件来确保其可行性。当然,用于构建数据的系统需要允许这种做法,而在某些系统中,尤其是低代码和无代码工具(例如ETL、报告工具)中,这种做法并没有被系统性地考虑到。在下一节中,我将介绍电路断路器,从概念上讲,它们与此处描述的条件并没有太大的不同,但在系统尚未(或不支持)支持条件的情况下,在结构上提供了一种替代方法。电路断路器电路断路器起源于电气电路领域。在电气系统中,电路断路器是一种安全装置,旨在保护电路免受损坏。这个设备关闭电路,允许电流流动,直到满足可能导致电路断开的某些条件。在这种情况下,该设备会打开电路,禁止电流流通,直到解决了潜在问题并采取措施再次关闭电路。这个想法被Michael Nygard重新应用于软件架构,并得到了Martin Fowler的支持。在软件架构中,一个额外的组件包装一个函数,以防止在某些条件下执行该函数。预防措施可以简单到在调用包装器时直接返回错误,并且电路处于断开状态时。在数据管道中,这可能更多地是预/后条件的替代方案,特别是在用户无法在用于创建管道的系统中引入条件的情况下,例如在低代码无代码系统或受限制的界面(无论是图形界面还是API)。因此,系统在管道中有额外的组件(电路),以防止在不满足条件的情况下断开它。事实上,以这种方式实现的电路断路器类似于应用程序外部的预/后条件。电路断路器的好处是它们更容易在事后添加(特别适用于遗留系统)。然而,它们也有缺点。例如,如果条件依赖于前后步骤(例如,特定用例的SLA/Os、自定义检查),那么电路断路器将是另一个需要维护并与步骤逻辑保持一致的应用程序。因此,电路断路器可能会存在于每个步骤之前和之后,这将使管道的长度变为原来的三倍。总结数据可观测性不是一次性的项目,而是需要所有数据从业者 embrace 的一种文化转变。通过养成习惯和在整个组织中标准化观察,数据可观测性变得自然而根深蒂固,成为数据文化的一部分。通过采用标准化的观察模型,组织可以增强团队和项目之间的沟通与协作。此外,数据可观测性引入了一个框架,用于利用观察来评估假设和验证期望。在下一章中,我们将深入探讨数据可观测性在组织中的影响,探讨其对体系结构和跨不同角色和领域的沟通的影响。
0
0
0
浏览量2016
攻城狮无远

《Delta Lake Up & Running》第二章:开始使用Delta Lake

在上一章中,我们介绍了Delta Lake,并了解了它如何为传统数据湖添加了事务保证、DML支持、审计、统一的流式和批处理模型、模式强制执行以及可扩展的元数据模型。在本章中,我们将亲自尝试Delta Lake。首先,我们将在安装了Spark的本地机器上设置Delta Lake。我们将在两个交互式shell中运行Delta Lake示例:首先,我们将运行带有Delta Lake软件包的PySpark交互式shell。这将允许我们输入并运行一个创建Delta表的简单两行Python程序。接下来,我们将使用Spark Scala shell运行类似的程序。尽管本书没有详细介绍Scala语言,但我们希望演示Delta Lake可与Spark shell和Scala一起使用。接下来,我们将在您喜爱的编辑器中创建一个名为helloDeltaLake的Python起始程序,并在PySpark shell中以交互方式运行程序。本章中设置的环境和helloDeltaLake程序将成为我们在本书中创建大多数其他程序的基础。一旦环境准备就绪,我们就准备深入研究Delta表格格式。由于Delta Lake使用Parquet作为底层存储介质,我们首先简要了解Parquet格式。由于当我们稍后研究事务日志时,分区和分区文件扮演着重要角色,因此我们将研究自动分区和手动分区的机制。接下来,我们将转向Delta表格,研究Delta表格如何在_delta_log目录中添加事务日志。本章的其余部分将专门用于事务日志。我们将创建并运行多个Python程序,以研究事务日志条目的详细信息,记录了哪些类型的操作,以及何时以及如何编写Parquet数据文件以及它们如何与事务日志条目相关。我们将查看更复杂的更新示例及其对事务日志的影响。最后,我们将介绍检查点文件的概念,以及它们如何帮助Delta Lake实施可扩展的元数据系统。获取标准的Spark镜像在本地机器上设置Spark可能会让人望而却步。您需要调整许多不同的设置,更新包等。因此,我们选择使用Docker容器。如果您尚未安装Docker,可以从其官方网站免费下载。我们使用的具体容器是标准的Apache Spark镜像。要下载该镜像,您可以使用以下命令:docker pull apache/spark 在您拉取了镜像之后,可以使用以下命令启动容器:docker run -it apache/spark /bin/sh Spark的安装位于/opt/spark目录中。PySpark、spark-sql和所有其他工具位于/opt/spark/bin目录中。有关如何使用容器的更多说明,您可以在本书的GitHub存储库的自述文件中找到。使用PySpark与Delta Lake如前所述,Delta Lake运行在现有存储之上,与现有的Apache Spark API完全兼容。这意味着如果您已经安装了Spark或按照前一部分中的说明使用了容器,那么开始使用Delta Lake将会很容易。有了Spark,您可以安装delta-spark 2.4.0包。您可以在其PySpark目录中找到delta-spark包。在命令行中输入以下命令:pip install delta-spark 安装完delta-spark后,可以像这样交互式运行Python shell:pyspark --packages io.delta:<delta_version> --conf "spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension" --conf "spark.sql.catalog.spark_catalog= org.apache.spark.sql.delta.catalog.DeltaCatalog" 这将为您提供一个PySpark shell,您可以通过其交互式运行命令:Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ ’_/ /__ / .__/\_,_/_/ /_/\_\ version 3.2.2 /_/ Using Python version 3.9.13 (tags/v3.9.13:6de2ca5, May 17 2022 16:36:42) Spark context Web UI available at http://host.docker.internal:4040 Spark context available as ’sc’ (master = local[*], app id = local-1665944381326). SparkSession available as ’spark’. 在Shell中,您现在可以运行交互式的PySpark命令。我们总是通过使用Spark创建一个range()来进行快速测试,从而生成一个DataFrame,然后可以将其保存为Delta Lake格式(有关更多详细信息,请参见“创建和运行一个Spark程序:helloDeltaLake”)。 以下是完整的代码:data = spark.range(0, 10) data.write.format("delta").mode("overwrite").save("/book/testShell") 以下是完整的运行:>>> data = spark.range(0, 10) >>> data.write.format("delta").mode("overwrite").save("/book/testShell") >>> 这里我们看到了创建range()的语句,然后是写入语句。我们可以看到Spark的执行器正在运行。当您打开输出目录时,您将找到生成的Delta表(关于Delta表格式的更多细节将在下一节中介绍)。在Spark Scala Shell中运行Delta Lake您还可以在交互式Spark Scala shell中运行Delta Lake。根据Delta Lake Quickstart中的说明,您可以使用以下方式启动Scala shell,并添加Delta Lake包:spark-shell --packages io.delta:<delta_version> --conf "spark.sql.extensions=io.delta.sql.DeltaSparkSessionExtension" --conf "spark.sql.catalog.spark_catalog= org.apache.spark.sql.delta.catalog.DeltaCatalog" 这将启动交互式Scala shell:Spark context Web UI available at http://host.docker.internal:4040 Spark context available as 'sc' (master = local[*], app id = local-1665950762666). Spark session available as 'spark'. Welcome to ____ __ / __/__ ___ _____/ /__ _\ \/ _ \/ _ `/ __/ '_/ /___/ .__/\_,_/_/ /_/\_\ version 3.2.2 /_/ Using Scala version 2.12.15 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_311) Type in expressions to have them evaluated. Type :help for more information. scala> 在Shell中,您现在可以运行交互式的Scala命令。让我们在Scala上进行类似的测试,就像您在PySpark Shell中所做的一样:val data = spark.range(0, 10) data.write.format("delta").mode("overwrite").save("/book/testShell") 以下是完整的运行示例:cala> val data = spark.range(0, 10) data: org.apache.spark.sql.Dataset[Long] = [id: bigint] scala> data.write.format("delta").mode("overwrite").save("/book/testShell") 同样,当您检查输出时,您会发现生成的Delta表。在Databricks上运行Delta Lake对于本书后面的示例,选择了Databricks社区版来运行Delta Lake。选择这个平台来开发和运行代码示例,因为它是免费的,简化了Spark和Delta Lake的设置,不需要您自己的云账户,也不需要提供云计算或存储资源。使用Databricks社区版,用户可以访问一个具有完整笔记本环境和已安装Delta Lake的最新运行时的集群。如果您不想在本地计算机上运行Spark和Delta Lake,还可以在云平台上的Databricks上运行Delta Lake,例如Azure、AWS或Google Cloud。这些环境使得开始使用Delta Lake变得更加容易,因为它们已经安装了Delta Lake的版本。云的额外好处是您可以创建具有任意大小的真正的Spark集群,潜在地可以跨越数百个节点,拥有成千上万个核心,用于处理几十TB或PB级别的数据。在云中使用Databricks有两种选项。您可以使用其受欢迎的笔记本,也可以使用Databricks实验室的开源工具dbx,将您喜欢的开发环境连接到云上的Databricks集群。 dbx是由Databricks实验室提供的工具,允许您从编辑环境连接到Databricks集群。创建和运行一个Spark程序:helloDeltaLake安装delta-spark包后,创建你的第一个PySpark程序非常简单。按照以下步骤创建PySpark程序:创建一个新文件(我们命名为helloDeltaLake.py)。添加必要的导入语句。至少需要导入PySpark和Delta Lake:import pyspark from delta import * 接下来,创建一个SparkSession builder,加载Delta Lake的扩展,如下所示:# Create a builder with the Delta extensions builder = pyspark.sql.SparkSession.builder.appName("MyApp") \ .config("spark.sql.extensions", \ "io.delta.sql.DeltaSparkSessionExtension") \ .config("spark.sql.catalog.spark_catalog", \ "org.apache.spark.sql.delta.catalog.DeltaCatalog") 接下来,我们可以创建SparkSession对象本身。我们将创建SparkSession对象并打印出其版本,以确保对象有效:# Create a Spark instance with the builder # As a result, you now can read and write Delta tables spark = configure_spark_with_delta_pip(builder).getOrCreate() print(f"Hello, Spark version: {spark.version}") 为了验证我们的Delta Lake扩展是否正常工作,我们创建了一个范围并以Delta Lake格式写入它:# Create a range, and save it in Delta Lake format to ensure # that your Delta Lake extensions are indeed working df = spark.range(0, 10) df.write \ .format("delta") \ .mode("overwrite") \ .save("/book/chapter02/helloDeltaLake") 这样就完成了您的入门程序的代码。您可以在书的代码存储库的/chapter02/helloDeltaLake.py位置找到完整的代码文件。如果您想编写自己的代码,这个代码是一个很好的起点。要运行程序,我们只需在Windows上启动命令提示符,或在MacOS上启动终端,并导航到包含我们代码的文件夹。然后,我们可以使用以下命令启动PySpark,将程序作为输入:pyspark < helloDeltaLake.py 当我们运行程序时,我们会得到我们的Spark版本输出(显示的版本将取决于读者安装的Spark版本):Hello, Spark version: 3.4.1 当我们查看输出时,我们可以看到我们已经写了一个有效的Delta表。 Delta Lake格式的详细信息将在下一节中介绍。在这一点上,我们已经成功安装了PySpark和Delta Lake,并且能够编写和运行一个具有Delta Lake扩展的完整的PySpark程序。既然您可以运行自己的程序,我们准备好在下一节中详细探讨Delta Lake格式。Delta Lake格式在本节中,我们将深入探讨Delta Lake的开放式表格格式。当我们使用这种格式保存文件时,实际上是在写入具有额外元数据的标准Parquet文件。这个额外的元数据是启用Delta Lake核心功能的基础,甚至可以执行传统关系数据库管理系统(RDBMS)中通常看到的DML操作,如INSERT、UPDATE和DELETE,以及众多其他操作。由于Delta Lake将数据写出为Parquet文件,我们将更深入地研究Parquet文件格式。我们首先会写出一个简单的Parquet文件,并详细查看Spark写入的相关内容。这将让我们对本书中要使用的文件有一个很好的理解。接下来,我们将以Delta Lake格式写出一个文件,注意到它触发了创建包含事务日志的"_delta_log"目录。我们将详细研究这个事务日志以及它是如何用于生成单一数据源的。我们将看到事务日志是如何实现第1章中提到的ACID原子性属性。我们将看到Delta Lake是如何将一个事务分解为单独的原子提交操作,并将这些操作记录在事务日志中,以有序、原子单位的方式。最后,我们将研究几种用例,并调查写入了哪些Parquet数据文件和事务日志条目,以及这些条目中存储了什么内容。由于每个事务都会写入一个事务日志条目,可能会导致出现多个小文件。为确保这种方法仍然可扩展,Delta Lake将每隔10个事务(在撰写本文时)创建一个检查点文件,其中包含完整的事务状态。这样,Delta Lake读取器只需处理检查点文件和随后写入的少量事务条目。这实现了一个快速、可扩展的元数据系统。Parquet文件Apache Parquet文件格式是过去20年来最流行的大数据格式之一。Parquet是开源的,因此它可以在Apache Hadoop许可下免费使用,并与大多数Hadoop数据处理框架兼容。与基于行的格式(如CSV或Avro)不同,Parquet是一种面向列的格式,这意味着每个列/字段的值都存储在一起,而不是在每个记录中。图2-1显示了基于行的布局和面向列的布局之间的差异,以及如何在逻辑表中表示这些差异。图2-1展示了与行布局不同的是,列布局按列值的顺序依次存储,这种列格式有助于逐列进行压缩。此格式还支持灵活的压缩选项和可扩展的数据类型编码模式,这意味着可以使用不同的编码来压缩整数和字符串数据类型。Parquet文件由行组和元数据组成。行组包含来自同一列的数据,因此每列都存储在同一个行组中。Parquet文件中的元数据不仅包含有关这些行组的信息,还包含有关列(例如最小/最大值、值的数量)和数据模式的信息,这使Parquet成为一个具有附加元数据以支持更好数据跳跃的自描述文件。图2-2显示了Parquet文件由行组和元数据组成。每个行组包括数据集中的每一列的列块,而每个列块由一个或多个包含列数据的页组成。要深入了解Parquet文件格式的更多文档,请访问Apache Parquet的网站和文档。Parquet文件的优势由于其面向列的格式、存储布局、元数据和长期受欢迎,Parquet文件在分析工作负载和处理大数据时具有以下几个强大的优势:高性能Parquet文件是一种面向列的格式,它们能够更好地进行压缩和编码,因为这些算法可以利用每一列中存储的相似值和数据类型。对于I/O密集型操作,这种压缩数据可以显著提高性能。在Parquet文件的情况下,当列值一起存储时,查询只需读取查询所需的列,而不需要在基于行的格式中读取所有列。这意味着列格式可以减少需要读取的操作数据量,从而提高性能。Parquet文件中包含的元数据描述了数据的一些特征,其中包括有关行组、数据模式以及最重要的列的信息。列元数据包括最小/最大值和值的数量等信息。这些元数据一起减少了每个操作所需读取的数据量(即数据跳跃),从而实现更好的查询性能。经济高效由于Parquet文件能够更好地利用压缩和编码,这使得数据本身更加经济高效。压缩后的数据在存储文件时占用更少的磁盘空间,从而降低了存储空间和存储成本。互操作性由于Parquet文件在过去20年中非常流行,特别是对于传统大数据处理框架和工具(例如Hadoop),它们得到了广泛支持,并提供了出色的互操作性。创建一个Parquet文件在图书存储库中,位于/chapter02/writeParquetFile的Python程序在内存中创建一个Spark DataFrame,并使用标准的PySpark API以Parquet格式将其写入/parquetData文件夹。data = spark.range(0, 100) data.write.format("parquet") \ .mode("overwrite") \ .save('/book/chapter02/parquetData') 在我们的情况下,当我们查看写入磁盘的内容时,我们看到以下内容(根据您的本地机器不同,您可能会看到不同的结果):Directory of C:\book\chapter02\parquetData 10/17/2022 10/17/2022 511 part-00000-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00001-a3885270-...-c000.snappy.parquet 10/17/2022 517 part-00002-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00003-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00004-a3885270-...-c000.snappy.parquet 10/17/2022 517 part-00005-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00006-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00007-a3885270-...-c000.snappy.parquet 10/17/2022 517 part-00008-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00009-a3885270-...-c000.snappy.parquet 10/17/2022 513 part-00010-a3885270-...-c000.snappy.parquet 10/17/2022 517 part-00011-a3885270-...-c000.snappy.parquet 一个刚接触大数据领域的开发者此时可能会感到有些震惊。我们只写入了100个数字,为什么最终会得到12个Parquet文件呢?这需要进行一些详细说明。首先,我们在写入操作中指定的文件名实际上是一个目录名,而不是文件名。正如您所见,目录/parquetData 包含了12个Parquet文件。当我们查看 .parquet 文件时,可能会看到我们有12个文件。Spark是一个高度并行的计算环境,系统试图让Spark集群中的每个CPU核心保持繁忙。在我们的情况下,我们在本地机器上运行,这意味着我们的集群中只有一台机器。当我们查看系统的信息时,我们可以看到我们有12个核心。当我们查看写入的.parquet文件的数量时,我们会发现我们有12个文件,这与我们集群中的核心数相等。这是Spark在这种情况下的默认行为。文件的数量将等于可用核心的数量。假设我们向代码中添加以下语句:data = spark.range(0, 100) data.write.format("parquet") \ .mode("overwrite") \ .save('/book/chapter02/parquetData') print(f"The number of partitions is: {data.rdd.getNumPartitions()}") 从输出中我们可以看到,的确有12个文件:The number of partitions is: 12 尽管在仅写入100个数字的情况下可能看起来有些过于复杂,但可以想象在读取或写入非常大的文件时,将文件拆分并并行处理可以显著提高性能。在输出中看到的 .crc 文件是循环冗余校验文件。Spark使用它们来确保数据没有被损坏。这些文件通常非常小,因此与它们提供的实用性相比,它们的开销非常小。虽然有一种方法可以关闭生成这些文件,但我们不建议这样做,因为它们的好处远远超过开销。输出中的最后两个文件是 _SUCCESS 和 _SUCCESS.crc 文件。Spark使用这些文件来提供一种确认所有分区都已正确写入的方法。创建Delta表到目前为止,我们一直在使用Parquet文件。现在,让我们将前一节中的第一个示例保存为Delta Lake格式,而不是Parquet(代码:/chapter02/writeDeltaFile.py)。我们只需要将代码中的Parquet格式替换为Delta格式,如下所示:data = spark.range(0, 100) data.write \ .format("delta") \ .mode("overwrite") \ .save('/book/chapter02/deltaData') print(f"The number of filesis: {data.rdd.getNumPartitions()}") 我们得到相同数量的分区:The number of files is: 12 当我们查看输出时,我们会看到添加了 _delta_log 文件:Directory of C:\book\chapter02\deltaData 10/17/2022 16 .part-00000-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00001-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00002-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00003-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00004-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00005-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00006-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00007-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00008-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00009-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00010-...-c000.snappy.parquet.crc 10/17/2022 16 .part-00011-...-c000.snappy.parquet.crc 10/17/2022 524 part-00000-...-c000.snappy.parquet 10/17/2022 519 part-00001-...-c000.snappy.parquet 10/17/2022 523 part-00002-...-c000.snappy.parquet 10/17/2022 519 part-00003-...-c000.snappy.parquet 10/17/2022 519 part-00004-...-c000.snappy.parquet 10/17/2022 522 part-00005-...-c000.snappy.parquet 10/17/2022 519 part-00006-...-c000.snappy.parquet 10/17/2022 519 part-00007-...-c000.snappy.parquet 10/17/2022 523 part-00008-...-c000.snappy.parquet 10/17/2022 519 part-00009-...-c000.snappy.parquet 10/17/2022 519 part-00010-...-c000.snappy.parquet 10/17/2022 523 part-00011-...-c000.snappy.parquet 10/17/2022 <DIR> _delta_log 24 File(s) 6,440 bytes _delta_log 文件包含了对数据执行的每个操作的事务日志。Delta Lake事务日志Delta Lake事务日志(也称为DeltaLog)是自Delta Lake表创建以来记录在表上执行的每个事务的顺序记录。它对Delta Lake的功能非常关键,因为它是其重要特性的核心,包括ACID事务、可扩展的元数据处理和时间旅行功能。事务日志的主要目标是允许多个读取器和写入器同时在给定版本的数据文件上操作,并为执行引擎提供额外的信息,如数据跳过索引,以进行更高性能的操作。Delta Lake事务日志始终向用户显示数据的一致视图,并充当单一的真相来源。它是跟踪用户对Delta表所做的所有更改的中央存储库。当Delta表读取器首次读取Delta表或在上次读取后已被修改的开放文件上运行新查询时,Delta Lake会查看事务日志以获取表的最新版本。这确保了用户文件的版本始终与最近查询时的主记录同步,并且用户不能对文件进行分歧和冲突的更改。事务日志如何实现原子性在第一章中,我们了解到原子性保证了对文件执行的所有操作(例如,INSERT、UPDATE、DELETE或MERGE)要么完全成功,要么完全不成功。如果没有原子性,任何硬件故障或软件错误都可能导致数据文件部分写入,从而导致数据损坏或至少是无效的数据。事务日志是Delta Lake提供原子性保证的机制。事务日志还负责处理元数据、时间旅行以及大型表格数据集的大幅提高元数据操作速度。事务日志是自创建以来对Delta表格执行的每个事务的有序记录。它充当单一的真相来源,并跟踪对表格所做的所有更改。事务日志使用户能够推断他们的数据,并信任其完整性和质量。简单的规则是,如果一个操作没有记录在事务日志中,那么它就从未发生过。在接下来的章节中,我们将通过几个示例来阐明这些原则。将事务拆分成原子提交每当您执行一组操作来修改表格或存储文件(例如插入、更新、删除或合并操作),Delta Lake 将将该操作分解为一系列原子的、离散的步骤,由表2-1中显示的一个或多个操作组成。这些操作以有序的原子单位(称为提交)的形式记录在事务日志条目(*.json)中。这类似于Git源代码控制系统跟踪更改的方式,以原子提交的形式。这也意味着您可以重放事务日志中的每个提交以获得文件的当前状态。 例如,如果用户创建一个事务来向表中添加新列,然后添加数据到该列,Delta Lake会将此事务分解为其组成操作部分,并一旦事务完成,将它们作为以下提交添加到事务日志中:更新元数据:更改模式以包括新列。添加文件:为每个新添加的文件。文件级别的事务日志当您写入一个Delta表时,该文件的事务日志会自动创建在 _delta_log 子目录中。随着您继续对Delta表进行更改,这些更改将自动记录为有序的原子提交在事务日志中。每个提交都以JSON文件的形式写入,从0000000000000000000.json开始。如果您对文件进行额外的更改,Delta Lake将按升序生成附加的JSON文件,因此下一个提交将被写入为0000000000000000001.json,下一个为0000000000000000002.json,依此类推。 在本章的其余部分,出于可读性的目的,我们将使用事务日志条目的缩写形式。而不是显示长达19位的数字,我们将使用长达5位的缩写形式(因此您将使用00001.json而不是更长的记法)。 此外,我们将缩短Parquet文件的名称。这些名称通常如下所示: part-00007-71c70d7f-c7a8-4a8c-8c29-57300cfd929b-c000.snappy.parquet 为了演示和解释,我们将将这样的名称缩写为part-00007.parquet,省略GUID和snappy.parquet部分。 在我们的示例可视化中,我们将以动作名称和受影响的数据文件名称来可视化每个事务条目;例如,在图2-3中,我们在一个单一的事务文件中有一个删除(文件)操作和另一个添加(文件)操作。对同一文件进行多次写入在本节中,我们将使用一组图表,详细描述每个代码步骤。对于每个步骤,我们显示以下信息:实际的代码片段显示在第二列。在代码片段旁边,我们显示作为代码片段执行结果的Parquet数据文件。在最后一列中,我们显示事务日志条目的JSON文件。我们为每个事务日志条目显示操作和受影响的Parquet数据文件名称。对于这个第一个示例,您将使用图书存储库中的chapter02/MultipleWriteOperations.py来展示对同一文件进行多次写入。以下是图2-4中不同步骤的逐步说明: 首先,将新的Delta表写入路径。一个Parquet文件已写入到输出路径(part-00000.parquet)。第一个事务日志条目(00000.json)已在_delta_log目录中创建。由于这是文件的第一个事务日志条目,记录了一个元数据操作和一个添加文件操作,指示添加了一个分区文件。 接下来,我们向表中追加数据。我们可以看到已写入一个新的Parquet文件(part-00001.parquet),并在事务日志中创建了一个附加的条目(00001.json)。与第一步类似,该条目包含一个添加文件操作,因为我们添加了一个新文件。 我们再次追加更多数据。再次写入一个新的数据文件(part-00002.parquet),并向事务日志中添加了一个新的事务日志文件(00002.json),包含一个添加文件操作。请注意,每个事务日志条目还将包含一个提交信息操作,其中包含了事务的审计信息。出于可读性的目的,我们在图表中省略了提交信息日志条目。写操作的操作顺序非常重要。对于每个写操作,数据文件始终首先被写入,只有在该操作成功完成时,才会向_delta_log文件夹添加一个事务日志文件。只有当事务日志条目成功写入时,事务才被视为已完成。读取Delta表的最新版本当系统读取Delta表时,它将遍历事务日志以“编译”表格的当前状态。读取文件时的事件顺序如下: 首先读取事务日志文件。 基于日志文件的信息,读取数据文件。 接下来,我们将描述先前示例(multipleWriteOperations.py)中写入的Delta表的顺序。Delta将读取所有日志文件(00000.json、00001.json和00002.json)。它将根据日志信息读取三个数据文件,如图2-5所示。请注意,操作的顺序还意味着在事务日志中不再引用的数据文件可能存在。实际上,在更新或删除的情况下,这是一个常见的情况。Delta Lake不会删除这些数据文件,因为如果用户使用Delta Lake的时间旅行功能(在第6章中介绍),这些文件可能会再次被需要。您可以使用VACUUM命令来删除旧的、过时的数据文件(也在第6章中介绍)。写操作中的故障场景接下来,让我们看看如果写操作失败会发生什么。在前面的写入场景中,假设图2-4中的第3步写入操作在中途失败。可能已经写入了部分Parquet文件,但事务日志条目00002.json尚未写入。这将导致图2-6中所示的情景。正如您在图2-6中所看到的,最后一个事务文件丢失。根据之前指定的读取顺序,Delta Lake将读取第一个和第二个JSON事务文件,以及相应的part-00000和part-00001 Parquet文件。Delta Lake的读取器将不会读取不一致的数据;它将通过前两个事务日志文件读取一个一致的视图。更新场景最后一个场景包含在chapter02/UpdateOperation.py代码库中。为了保持简单,我们有一个包含患者信息的小型Delta表。我们只跟踪每位患者的患者ID和患者姓名。在这个用例中,我们创建一个包含四名患者的Delta表,每个文件中有两名患者。接下来,我们从另外两名患者添加数据。最后,我们更新了第一名患者的姓名。正如您将看到的,这次更新产生了比预期更大的影响。完整的更新场景如图2-7所示。在这个示例中,我们执行以下步骤:第一个代码片段创建了一个Spark DataFrame,其中包含四名患者的患者ID和姓名。我们使用 .coalesce(2) 将DataFrame写入一个Delta表,强制数据写入两个文件。结果,我们写入了两个文件。一旦part-00000.parquet和part-00001.parquet文件被写入,就创建了一个事务日志条目(00000.json)。请注意,事务日志条目包含两个添加文件操作,指示添加了part-00000.parquet和part-00001.parquet文件。接下来的代码片段追加了另外两名患者(P5和P6)的数据。这导致了part-00002.parquet文件的创建。同样,一旦文件被写入,就会写入事务日志条目(00001.json),事务就完成了。再次请注意,事务日志文件有一个添加文件操作,指示添加了一个文件(part-00002.parquet)。代码执行一个更新操作。在这种情况下,我们想要将患者ID为1的患者的姓名从P1更新为P11。目前,患者ID为1的记录存在于part-0中。为执行更新,读取part-0并使用映射操作符来更新任何匹配患者ID为1的P1记录为P11。一个新文件被写入为part-3。最后,Delta Lake写入了事务日志条目(00002.json)。请注意,它写入了一个移除文件操作,表示移除了part-0文件,以及一个添加操作,表示添加了part-3文件。这是因为来自part-0的数据被重写到part-3中,而所有已修改的行(以及未修改的行)都被添加到了part-3中,使part-0文件变得过时。 请注意,Delta Lake不会删除part-0文件,因为用户可能希望通过时间旅行回到过去,而在这种情况下,文件是必需的。VACUUM命令可以清理未使用的文件,如此操作详细介绍在第6章中。现在我们已经看到了在更新期间数据是如何写入的,让我们看看在读取中如何确定要读取的内容,如图2-8所示。读取将按以下方式进行:首先读取第一个事务日志条目(00000.json)。该条目告诉Delta Lake包括part-0和part-1文件。接下来读取下一个条目(00001.json),告诉Delta Lake包括part-2文件。最后读取最后一个条目(00002.json),通知读取器移除part-0文件并包括part-3。结果,读取器最终会读取part-1、part-2和part-3,得到了图2-8中所示的正确数据。扩展大规模元数据现在我们已经看到事务日志如何记录每个操作,我们可以有许多非常大的文件,其中包含数千个事务日志条目,针对单个Parquet文件。Delta Lake如何扩展其元数据处理,而不需要读取数千个小文件,这将对Spark的读取性能产生负面影响?Spark在读取大文件时效果最佳,那么我们该如何解决这个问题?一旦Delta Lake写入程序将提交记录到事务日志,它将在“_delta_log”文件夹中以Parquet格式保存一个检查点文件。Delta Lake写入程序将在每10次提交后继续生成一个新的检查点。检查点文件保存了表在特定时间点的整个状态。注意,“状态”指的是不同的操作,而不是文件的实际内容。因此,它将包含添加文件、删除文件、更新元数据、提交信息等操作,以及所有上下文信息。它将以原生Parquet格式保存这个列表。这将允许Spark快速读取检查点。这为Spark读取器提供了一个“快捷方式”,可以完全重现表的状态,避免重新处理数千个小的JSON文件,这可能效率低下。检查点文件示例以下是一个示例(如图2-9所示),我们执行多次提交,结果生成了一个检查点文件。该示例使用了书籍存储库中的代码文件chap02/TransactionLogCheckPointExample.py。这个示例包括以下步骤:第一段代码创建一个标准的Spark DataFrame,其中包含多个病人的数据。请注意,我们对DataFrame应用了coalesce(1)事务,将数据强制放入一个分区。接下来,我们将DataFrame以Delta Lake格式写入存储文件。我们验证只有一个part-0001.parquet文件被写入。我们还看到在_delta_log目录中创建了一个单个事务日志条目(00000.json)。该目录条目包含了part-00001.parquet文件的添加文件操作。在接下来的步骤中,我们设置一个循环,循环次数为range(0, 9),将创建一个新的病人行,然后从该元组创建一个DataFrame,并将DataFrame写入存储文件。由于循环了九次,我们创建了九个额外的Parquet文件,从part-00001.parquet到part-00009.parquet。我们还看到了九个额外的事务日志条目,从00001.json到00009.json。在第3步中,我们创建了一个额外的病人元组,将其转换为DataFrame,并将其写入Delta表。这创建了一个额外的数据文件(part-00010.parquet)。事务日志中有一个标准的日志条目(00010.json),包含了part-00010.parquet文件的添加文件操作。但有趣的事实是它还创建了一个000010.checkpoint.parquet文件。这就是前面提到的检查点。每10次提交会生成一个检查点。这个Parquet文件以原生Parquet格式包含了在提交时表的整个状态。在最后一步,代码生成了两次提交,创建了part-00011.parquet和part-00012.parquet,以及两个新的日志条目,这些条目指向这些文件。如果Delta Lake需要重新创建表的状态,它将简单地读取检查点文件(000010.checkpoint.parquet),然后重新应用两个额外的日志条目(00011.json和00012.json)。显示检查点文件既然我们已经生成了checkpoint.parquet文件,让我们使用/chapter02/readCheckPointFile.py Python文件来查看它的内容:# Set your output path for your Delta table DATALAKE_PATH = "/book/chapter02/transactionLogCheckPointExample" CHECKPOINT_PATH = "/_delta_log/00000000000000000010.checkpoint.parquet" # Read the checkpoint.parquet file checkpoint_df = \ spark \ .read \ .format("parquet") \ .load(f"{DATALAKE_PATH}{CHECKPOINT_PATH}") # Display the checkpoint dataframe checkpoint_df.show() 请注意,我们在这里进行的是Parquet格式的读取,因为检查点文件确实是以Parquet格式存储的,而不是Delta格式。 checkpoint_df DataFrame的内容如下所示:+----+--------------------+------+--------------------+--------+ | txn| add|remove| metaData|protocol| +----+--------------------+------+--------------------+--------+ |null|{part-00000-f7d9f...| null| null| null| |null|{part-00000-a65e0...| null| null| null| |null|{part-00000-4c3ea...| null| null| null| |null|{part-00000-8eb1f...| null| null| null| |null|{part-00000-2e143...| null| null| null| |null|{part-00000-d1d13...| null| null| null| |null|{part-00000-650bf...| null| null| null| |null|{part-00000-ea06e...| null| null| null| |null|{part-00000-79258...| null| null| null| |null|{part-00000-23558...| null| null| null| |null| null| null| null| {1, 2}| |null| null| null|{376ce2d6-11b1-46...| null| |null|{part-00000-eb29a...| null| null| null| +----+--------------------+------+--------------------+--------+ 正如您所见,检查点文件包含了不同操作(添加、移除、元数据和协议)的列。我们看到了不同Parquet数据文件的添加文件操作,以及当我们创建Delta表时的更新元数据操作,以及初始Delta表写入导致的协议更改操作。 请注意,DataFrame.show()不会按顺序显示DataFrame记录。协议更改和更新元数据记录总是检查点文件中的第一条记录,然后是不同的添加文件操作。总结在我们开始探索Delta Lake的旅程时,一切都始于初始设置。本章介绍了如何在本地计算机上使用PySpark和Spark Scala shell设置Delta Lake,同时涵盖了必要的库和软件包,以使您能够运行带有Delta Lake扩展的PySpark程序。您还可以使用像Databricks这样的基于云的工具来简化此设置过程,以开发、运行和共享基于Spark的应用程序,例如Delta Lake。在了解如何启动Delta Lake之后,我们开始学习Delta Lake的基本组件,这些组件无疑支持了本书中我们将讨论的大多数核心功能。通过添加检查点文件以实现可伸缩的元数据和将标准Parquet文件添加到事务日志以支持ACID事务,Delta Lake具备了支持可靠性和可扩展性的关键元素。既然我们已经建立了这些基本组件,您将在下一章中学习有关Delta表上的基本操作。
0
0
0
浏览量279
攻城狮无远

《流式Data Mesh》第五章:联邦计算数据治理

第五章:联邦计算数据治理治理提供了互操作性和控制,使各个领域能够共同合作,同时保护每个领域免受不必要的活动干扰。此外,在流数据网中进行数据治理提供了一种集中分布式的方式来管理领域操作、元数据和服务定义。这就是我们在定义流数据网中的策略和控制时所关注的内容。 在流数据网中,联邦数据治理的任务是创建一个共享数据的领域之间和谐共存的社区,同时保持领域的自治性。第一章讨论了在流数据网中拥有多个网格(mesh)的概念,这些网格超越了数据范畴。其中之一就是用于流数据治理的网格。这种数据治理网格的概念类似于我们今天在社会中看到的州和联邦政府之间的关系。州或地方政府对特定地区拥有自治权,而联邦政府制定的政策会对州和地方层面的运作产生影响。这种关系的目标是创建和谐的社区。对于流数据网也是如此。我们可以将州和地方政府视为控制数据治理范围仅限于其领域边界的领域,而联邦计算数据治理则类似于联邦政府,强制执行旨在保持领域之间和谐的政策。 联邦计算数据治理中的"计算"意味着支持数据治理的规则和政策是以自助服务或自动化的方式实现的。自助服务和自动化以代码的形式编写,并由支持网格的工程师构建,我们称之为集中化工程师或集中化团队。 在本章中,我们将概述数据治理的重要原则,并确定在流数据网中管理这些原则的权威所有权。我们还将涵盖与流数据网相关的数据治理的计算方面。在本章中,我们将介绍一些数据治理的基础知识,并将其应用于流数据治理。然后我们将讨论元数据流数据产品的重要性以及其应包含的内容。我们将介绍一些简化元数据管理的工具,这些工具可以包括在流数据网中。最后,我们将介绍有助于数据治理的角色。流数据网格中的数据治理数据治理是一套政策、标准、流程、角色和责任,共同确保企业对数据的问责和拥有权。政策是企业本身或者更重要的是外部法律定义的围绕数据的规则和规定,如果违反这些规定,企业可能面临巨额罚款。这些政策还包括对标准的执行,以实现不同领域之间数据的互操作性和可用性,尤其是在像流数据网格这样的分散式数据平台中。这些政策通过授权、认证和保护私密或个人数据的方式来实施流程和数据控制。政策使用代表组、人员或系统的角色来创建围绕数据的访问控制。数据血缘图数据血缘图是将所有数据治理信息(政策、标准、流程和角色)聚合在一起的图形化工具。数据血缘提供了数据的完整历史:起源、转换、增强、进行转换的用户等。数据的使用者需要相信他们使用的数据是正确的数据。数据血缘提供了一种能够建立信任的视角。它通过绘制涉及数据的采集、转换、增强和清理的政策、标准、流程、角色和责任的步骤来实现这一目标。在图5-1中,数据治理政策清晰地标识为“GDPR过滤”和“标准化清洗”。血缘图中的批注还分别标识了领域和发布、组装数据产品的工程师。在流数据网格中的数据治理应满足以下要求: 在数据流动时,领域需要格外小心,确保受保护的数据不会违反可能会导致巨额罚款和损害客户信任的法规。领域需要确定影响流数据产品的法规,并提供领域工具,以便轻松保护受保护的数据。由于领域之间将共享数据,因此需要一套促进互操作性的标准。为实现这一点,需要一种使用模式定义数据模型的方式,以便领域可以轻松地编写和使用这些标准。在流数据网格中,定义角色或用户组对于保护受保护的数据至关重要。例如,一个整个领域可以属于一个用户组。这将使流数据产品所有者更容易地授予或撤销对其领域数据的访问权限。构建流数据产品的领域工程师需要简单的方法来混淆受保护的数据。例如,他们需要简单的工具,在数据离开领域之前对其进行加密。相应地,使用数据的领域需要一种解密相同数据的方法。类似地,领域工程师需要一种对数据进行标记化/去标记化的能力。领域工程师需要一种简单的方法来构建跨多个领域的数据血缘。他们需要保留与标准化和安全性相关的详细源信息和转换信息。流数据网格中的数据治理需要数据监管责任,这与数据产品所有者提供的责任基本相同。数据产品所有者对领域内的数据产品以及其元数据的质量、可访问性和安全性负有责任。这创建了消费者所依赖的高度透明性,并且是一个良好的流数据产品的一部分。流数据目录以组织数据产品流数据目录是一种应用程序,用于在企业中组织和管理数据及其元数据的清单,以实现其发现和治理。这适用于流数据产品,并为领域提供一个一站式的平台,用于在流数据网格中搜索和订阅流数据。它提供描述数据如何准备、格式化、起源以及其执行的安全性和法规的所有元数据。在流数据网格中,流数据目录应向用户展示的不仅仅是数据本身,还包括数据产品的可扩展性限制(计算限制等)。在接下来的章节中,我们将讨论构成发布的流数据产品元数据的组件。我们将讨论如何捕获这些数据,以防止丢失。最终,我们将将所有这些组件编制成一个联合模型,以便在流数据网格中共享这些元数据。元数据元数据通常是一个涵盖多个不同用法的术语。在数据平台的背景下,元数据通常被定义为包含以下信息的数据:表定义、列名、描述和关系数据资产的验证规则数据类型数据所有者包括源和应用的转换的血缘信息流数据产品附带的元数据应该提供足够的信息,使领域能够了解数据的来源、组装方式以及安全性。我们可以用一个比喻来更清楚地解释:数据产品就像杂货店里的产品。在购物时,我们希望确保产品来自值得信赖的制造商,并包含符合我们消费需求的成分。我们还确信该产品在安全和可信赖的地方制造,包装方式确保产品没有被篡改,且产品的保质期尚未过期。关于数据产品的元数据也是如此。了解对流数据产品所做的处理可以展现透明度和信任。许多企业环境通常存在多个“副本”数据,这些数据通常具有相似的内容,由完全不同的团队维护。这往往导致部门报告之间存在差异,因为真正的单一数据源,即使存在,也不为所有团队所理解或利用。数据产品通过创建一个企业内部和超企业信息的单一来源解决了这个问题——一个具有共同内容和更重要的共同元数据的受管控数据源。当元数据得到维护和发布时,企业内部的用户能够为其使用找到合适的数据源,了解数据的描述信息,解释可能发生的任何转换,并通过了解适当的数据类型来正确使用数据。如果数据的定义存在问题,用户可以提请产品所有者注意,并在迭代周期或功能请求中进行内容更正,以避免数据碎片化和工作重复。元数据与逐渐变化的维度数据并无不同,应予以相同对待。在流数据网格中,元数据必须作为一个数据产品本身进行发布。其定义必须可被数据产品的用户全局消费,同时也可被其他数据产品在数据丰富和增强中利用其他数据产品。随着元数据的变化,其影响必须实时可用,以确保所有数据产品保持同步。因此,在一个领域中暴露所有数据产品的元数据的流数据产品是必需的。这个要求可以利用构建流数据网格的相同流技术来实现。在本节中,我们将讨论四个元数据类别:模式、血缘、安全性和可扩展性。这四个类别提供了足够的元数据,使我们的流数据产品能够获得领域消费者的信任。模式(schema)在第3章中,我们介绍了领域驱动设计,并强调了它能够帮助业务专家和工程师构建领域模型的能力。这些模型自然地帮助揭示出在一个领域内成为数据产品的实体。这些领域实体和事件是通过模式进行定义的,这些模式成为了关于数据结构和安全性的规则的基础。这些规则最终成为了数据治理的业务驱动策略。假设在领域驱动设计阶段定义了一个名为Employee的实体(参见图5-2)。为了简单起见,我们只关心以下属性:ID、社会安全号码(SSN)、名字、姓氏和地址。Example 5-1将是对应的JSON模式,用于编码可以由工具和应用程序处理的实体。{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://example.com/product.schema.json", "title": "Employee", "description": "An employee in your company", "type": "object", "properties": { "empid": { "description": "Identifier that is not a SSN", "type": "integer" }, "SSN": { "description": "SSN", "type": "string" } , "fname": { "description": "First name", "type": "string" }, "lname": { "description": "Last name", "type": "string" }, "address": { "description": "Address", "type": "string" } }, "required": [ "empid", "SSN", "fname", "lname", "address" ] } 血缘数据血缘是数据从源头起始处经过的路径,途中的停留点以及其最终目的地。这包括数据通过的所有系统、数据的清洗过程、数据的增强方式以及数据的安全性信息。捕获所有这些元数据是困难的,因为其中许多系统和应用程序不共享信息。您需要通过从所有这些系统/应用程序中提取元数据并组装它们,以找到数据从当前位置(目的地)到源系统的路径。对于流式或批处理数据管道来说,血缘可能是最难获取的元数据。OpenLineage是一个开源的血缘平台,用于跟踪有关ETL应用程序的元数据。它为领域提供了一种无需组装大量元数据就能构建血缘的方式。它具有用于应用程序提交开始和结束ETL作业的REST、Java和Python API。它是一种在单个应用程序中收集、聚合和可视化数据管道的方式。OpenLineage使用一个名为Marquez的工具作为其用户界面和血缘存储库,如图5-3所示。同时请注意,OpenLineage最适合批处理过程,但您可以定义可以作为流式ETL作业的运行。您可以在流式数据管道的每个步骤中调用OpenLineage API,以将血缘信息添加到流式ETL血缘中,一直到消费领域。在图5-4中,您可以看到随着流式数据管道添加了更多组件,血缘图开始构建的情况。在调用OpenLineage API时,请确保提供足够的元数据,以便领域能够全面了解数据在流式处理过程中的转换和增强方式。例如:提供足够的信息,以便领域能够完全识别流式数据产品的来源系统,包括使用或创建数据的所有相关应用程序。识别被标记化或加密的字段,并确定如何推导或查找原始值。识别被过滤掉的字段,并说明过滤的原因。包括来自其他领域的其他流式数据产品的信息,这些产品被用作派生产品,以便OpenLineage可以构建超出领域范围的完整血缘图。在流式处理管道的每个步骤中,提供相关的模式(schema),以及它如何对数据进行增强或转换。如果可能,为每个步骤提供联系人,以便消费领域可以查询实现或问题的相关信息。在本书的GitHub存储库中,您将能够使用Docker运行OpenLineage,并使用存储库中提供的示例构建自己的血缘图。不幸的是,大多数流处理引擎不支持OpenLineage集成。在这些情况下,您需要在CI/CD过程中调用OpenLineage API来发送START和COMPLETE事件。这将构建血缘图和页面,可以从AsyncAPI生成的流式数据产品页面中引用。关于如何从AsyncAPI生成数据产品页面的内容,我们将在后续章节中介绍。安全安全信息通常在元数据中不足或甚至被省略。许多企业具有全球业务,将数据在全球范围内传输最终将受到诸如GDPR等数据隐私法规的约束。同样,如果数据与医疗保健有关,则受到HIPAA规定的法规约束。提供有关流式数据产品如何处理法规的信息非常重要,以便领域可以保持合规性。例如,在接受审计时,领域必须知道哪些字段被视为私密字段,该过程在哪里运行以保护该私密数据,并且是如何实施的。这些信息可以很容易地在血缘图中提供。审计员可以遍历图形以查看数据的来源和如何保护数据。当企业在全球区域拥有多个领域时,这一点尤为重要。应该有证据表明我们通过领域实践了可传递的数据保护和权限。对数据访问的信息也很重要。某些领域不应该有私密数据。血缘图应显示私密数据被过滤掉的情况,而其他具有访问权限的领域应该有一种方法来获取私密数据,因为它不应该在未受保护的情况下传输。基于角色的访问控制(RBAC)是一种限制对数据访问的模型。同样,基于属性的访问控制(ABAC)在字段级别限制访问。RBAC和ABAC使得组织对数据的可访问性更易于理解和实施。元数据中的访问角色提供了更高的可信度。大多数流式处理平台都具有一定级别的访问控制,并提供用于查询已定义的访问规则的API。描述流式数据产品的不同系统中的所有元数据创建了我们在前几章中提到的数据治理的“网格”。来自这个数据治理工具网格的元数据需要被汇总到一个单一的视图中,供领域查看。在下一节中,我们将介绍如何聚合这些元数据,并构建一个流式数据产品视图,以部署到流式数据目录中。可扩展性可扩展性不仅仅提供有关流数据产品本身的元数据是不够的,还需要提供关于它的服务方式和所提供的保证的元数据。 在第4章中,我们强调在数据派生品摄取时需要早期考虑可扩展性。这种可扩展性会传递到最终的流数据产品中。保留这些信息可以让数据消费者对数据产品的可扩展性有良好的理解。元数据应包括吞吐量(以兆字节每秒(MBps)计)和分区数量等信息。 您还应提供有关使用统计的元数据。域消费者可以了解有多少现有消费者正在读取数据产品。他们将能够确定数据产品是否已达到最大服务能力,并确定是否需要向数据产品所有者请求更多数据。他们还可以了解过去一年发生了多少次停机,以了解其正常运行时间的保证。 所有这四个类别的元数据都应该与流数据产品一起提供。在第4章中,我们使用了AsyncAPI中的标签来帮助用户提供有关流数据产品的更多元数据。接下来,我们将生成一个数据目录页面,描述流数据产品并提供链接到附加元数据的页面。从AsyncAPI生成数据产品页面在第4章中,我们生成了一个AsyncAPI YAML文档,用于定义流数据产品。由于AsyncAPI可以扩展,我们为数据产品积累的所有元数据可以用于填充或在AsyncAPI中引用。在本节中,我们将展示如何使用AsyncAPI YAML文档生成一个HTML页面,并填充它以展示域所希望了解的有关数据产品的信息。 在本书的GitHub存储库中,我们提供了示例供您克隆,参考示例5-2。git clone https://github.com/hdulay/streaming-data-mesh.git 命令的输出结果如图5-5所示。该页面包含了在AsyncAPI YAML中输入的所有流数据产品。此HTML页面可以用于发布到流数据目录中。或者,目录可以使用AsyncAPI插件从AsyncAPI YAML文档动态生成HTML页面。在图5-5中,我们添加了三个按钮,展示了如何将流数据网格工作流嵌入到HTML中。流数据网格工作流定义了用户如何请求访问并最终开始使用流数据产品。请注意,您完全可以控制生成此HTML文档的方式,并且可以符合满足您业务需求的工作流程。数据产品的消费者可以点击"请求访问"按钮来请求访问数据产品。"创建连接器"和"创建Spring客户端"按钮是数据产品的消费者在请求被批准后订阅流数据产品并将其拉入其域中的方式。有关流数据网格工作流的建议将在"访问工作流"中介绍。截至本书撰写时,没有开源的数据目录可以使用AsyncAPI作为发布流数据产品的方式。许多公司都构建了自己的数据目录,可以支持流数据产品。或者,由于AsyncAPI是对定义同步RESTful API的规范OpenAPI的扩展,我们可以使用现有的OpenAPI注册表(如Apicurio)来发布我们的流数据产品。Apicurio注册Apicurio是一个开源的注册表,用于存储文件。它通过REST API提供向存储中添加、更新和删除这些文件的功能。它支持的文件类型包括OpenAPI、AsyncAPI、GraphQL、Apache Avro、Google Protocol Buffers、JSON Schema、Kafka Connect schema、WSDL和XML Schema(XSD)。Apicurio还可以用作模式注册表,在本章后面我们将更详细地介绍它。目前,我们将使用它来注册我们的AsyncAPI YAML文档,作为发布流数据产品的一种方式。在GitHub存储库中,我们使用Docker来运行Apicurio并注册我们的AsyncAPI YAML文档。图5-6显示了在加载AsyncAPI后Apicurio中的页面。在Apicurio中,域可以通过名称、组、描述、标签、全局ID和内容ID来搜索数据产品。然后,他们可以使用在“内容”下找到的电子邮件联系该域。访问工作流图5-5是由一个AsyncAPI HTML生成器生成的,它在页面上添加了三个按钮:请求访问、创建连接器和创建Spring客户端。它们暗示了一个可能的工作流程,其中用户请求访问权限,数据产品所有者授予访问权限并发送访问凭证,然后用户可以使用这些凭证通过连接器或Spring客户端将数据消费到他们的领域中(参见图5-7)。企业可以添加额外的工作流程步骤,以满足其数据治理要求。组织任务,如授予访问请求,可能会令人困惑。在下一节中,我们将尝试解决这个问题。集中化与分散化角色和任务需要被指定为联邦(集中化)或领域(分散化)。有些任务和角色很容易分配,但其他任务可能会同时涉及集中化和分散化团队。在本节中,我们将尝试提供一些区分。集中化的工程师团队集中化的工程师专注于构建域使用的自助服务,用于构建和发布流式数据产品。 这些集中化的工程师还负责维护和管理所有域及其流式数据产品的元数据和安全性。 这些元数据包括以下内容:流式数据目录注册所有流式数据产品的位置,域可以在其中进行搜索。模式注册表域注册其模式的位置,但模式演化由集中化团队控制。OpenLineage可以集中地组装完整的血统图,允许在不同域之间进行链接。当域构建从其他域的流式数据产品派生的流式数据产品时,这一点非常关键。安全任务包括:集成对流式数据网格进行用户身份验证的系统,例如轻量级目录访问协议(LDAP)和单点登录(SSO)。启用域之间的加密连接。分散化(域)工程师分散化(域)工程师是指域内的应用工程师,他们通常对数据工程知之甚少或毫无了解。他们需要工具来获取、构建和发布流式数据产品,而无需太多编码工作。低代码和简单配置是这些工程师的期望,这样他们就可以更多地专注于支持业务的应用程序。这些工程师应该对自己的域模型有足够的了解,以了解什么样的流式数据产品更好。他们定义代表其数据的模式(schemas)。但是,他们并不完全控制模式的演化方式。模式的演化应由由中央团队管理的模式注册表控制。模式注册表将强制域演化其模式,以确保与已经使用其流式数据产品的域兼容,而不会产生破坏性的变化。域工程师还控制对其流式数据产品的授权(或访问)。他们需要了解将其数据提供给其他域是否会违反诸如GDPR或HIPAA之类的法规。他们需要拒绝不应订阅其数据的域的访问请求,或者提供经过过滤、标记化或加密的数据。授权对于流式数据产品应该已经内建在流式平台中,并通常以访问控制列表(ACLs)或基于角色的访问控制(rRBACs)的形式实现。域工程师应该具备所有必要的工具和服务,以便能够执行所有这些任务。总结在本章中,我们提到目前没有一个数据目录可以按照我们定义的访问工作流程进行操作。作为替代方案,您可以尝试扩展现有的流式数据目录以支持访问工作流程。否则,您需要从头开始构建一个支持访问工作流程的数据目录,并满足以下要求:支持不同角色的视图:数据产品工程师、数据产品所有者、数据产品消费者。能够在域注册新的或更新的流式数据产品时使用 AsyncAPI YAML 文档。支持对流式数据产品的访问工作流程请求。
0
0
0
浏览量1436
攻城狮无远

第四章:迁移框架

除非你在一家初创公司,否则你很少会从零开始构建一个数据平台。相反,你将通过从传统系统中迁移数据构建一个新的数据平台。在这一章中,让我们审视迁移过程——在迁移到新数据平台时应该做的所有事情。我们将首先提出一个概念模型和可能的框架,供你在现代化数据平台时遵循。然后,我们将讨论一个组织如何估算解决方案的总体成本。我们将探讨如何确保在迁移进行的同时安全性和数据治理得到落实。最后,我们将讨论模式、数据和管道迁移。你还将了解有关区域容量、网络和数据传输约束的选项。现代化数据工作流在开始制定迁移计划之前,您应该全面了解为何进行迁移以及您要迁移到何处的愿景。整体视角数据现代化转型应该被全面考虑。从俯瞰的角度看,我们可以确定三个主要支柱:业务成果 关注正在现代化的工作流,并确定这些工作流驱动的业务成果。这对于确定差距和机会所在至关重要。在做出任何技术决策之前,将迁移限制在与领导层确定的业务目标一致的用例中(通常在两到三年的时间范围内)。利益相关方 确定可能访问数据的个人或角色(其中一些团队可能尚不存在)。数据现代化的目标是使数据访问民主化。因此,这些团队将需要在数据现代化的最终状态技术中使用的任何工具(SQL、Python、仪表板)上变得数据有素,并能熟练运用。技术 您将不得不根据业务战略和团队能力来定义、实施和部署数据架构、建模、存储、安全、集成、可操作性、文档、参考数据、质量和治理。确保您不将迁移视为纯粹的IT或数据科学项目,而是作为技术方面只是更大范围的组织变革的子集。现代化工作流当你考虑进行现代化项目时,你的思维自然会偏向于你目前使用的工具所带来的困扰。你决定要升级你的数据库,并使其在全球范围内保持一致和可伸缩,所以你将其现代化为Spanner或CockroachDB。你决定要升级你的流处理引擎,并使其更具弹性和更易于运行,所以你选择了Flink或Dataflow。你决定不再调整数据仓库中的集群和查询,所以你将其现代化为BigQuery或Snowflake。这些都是不错的举措。在可能的情况下,无论何时你都应该升级到更易于使用、更易于运行、更具可伸缩性和更具弹性的工具。然而,如果你只是进行一对一的工具更改,你最终只会取得渐进式的改进。你将无法从这些升级中获得变革性的改变。为了避免这种陷阱,在你开始数据现代化之旅时,强迫自己思考工作流程,而不是技术。现代化数据工作流程意味着什么?考虑最终用户想要完成的整体任务。也许他们想要识别高价值客户。也许他们想要运行一项营销活动。也许他们想要识别欺诈行为。现在,从整体上考虑这个工作流程以及如何以尽可能便宜和简单的方式实现它。接下来,从第一原则出发,以你现代化的工具集实现这样的工作流程。在这样做时,大量依赖自动化:自动化数据摄取 不要编写定制的ELT流水线。使用现成的ELT工具,如Datastream或Fivetran,将数据导入数据仓库。实时转换数据并在物化视图中捕获常见的转换要比为每个可能的下游任务编写ETL流水线要容易得多。此外,许多SaaS系统将自动导出到S3、Snowflake、BigQuery等。默认使用流处理 将数据存储在一个结合了批处理和流处理存储的系统中,以便所有SQL查询都反映最新的数据(受一些延迟的影响)。任何分析工具都应该使用相同的框架处理流处理和批处理。在我们的示例中,生命周期价值计算可以是一个SQL查询。出于重用的目的,将其作为物化视图。这样,所有计算都是自动的,数据始终是最新的。自动扩展 任何期望你预先指定机器数量、仓库大小等的系统都将要求你关注系统而不是要完成的工作。你希望扩展是自动的,这样你就可以关注工作流程而不是工具。查询重写、融合阶段等 你希望能够专注于要完成的工作并将其分解为可理解的步骤。你不想调整查询、重写查询、融合转换等。让内置于数据堆栈中的现代优化器处理这些事务。评估 你不想为评估ML模型的性能编写定制的数据处理流水线。你只想能够指定采样率和评估查询,并收到有关特征漂移、数据漂移和模型漂移的通知。所有这些功能都应该内置到已部署的端点中。重新训练 如果遇到模型漂移,你应该重新训练模型的可能性为10次中有9次。这也应该是自动的。现代ML流水线将提供一个可调用的挂接,你可以直接将其与你的自动评估流水线相关联,以便你也可以自动进行重新训练。持续培训 模型漂移不是需要重新训练的唯一原因。当你有更多的数据时,可能需要重新训练。也许是当新数据着陆在存储桶中时。或者是在代码检入时。同样,这也可以自动化。一旦你确认需要一个完全自动化的数据工作流,你将看到一个相当模板化的设置,包括连接器、数据仓库和ML流水线。所有这些都可以是无服务器的,因此你基本上只需要配置,而不是集群管理。当然,你还将编写一些具体的代码:SQL中的数据准备TensorFlow或PyTorch等框架中的ML模型用于连续评估的评估查询我们能够将每个工作流程简化到这样一个简单的设置,这就解释了为什么一个集成的数据和AI平台是如此重要。改变工作流本身你可以通过使用现代数据堆栈使工作流程本身更加高效,使其更加自动化。但在这样做之前,你应该问自己一个关键问题:“这个工作流程是否有必要由数据工程师预先计算?”因为每当你构建一个数据流水线时,你都在进行预计算。这只是一种优化,不再多。在许多情况下,如果你可以使工作流程成为自助式和自发的,你就不必使用数据工程资源构建它。由于许多工作都是自动化的,你可以提供在完整历史交易记录上运行任何聚合(不仅仅是生命周期价值)的能力。将生命周期价值计算移到声明性语义层,用户可以在其中添加自己的计算。例如,Looker这样的工具就可以做到这一点。一旦这样做,你将获得整个组织中一致的关键绩效指标(KPIs)和用户有权构建常见度量库的好处。创建新指标的能力现在掌握在业务团队手中,这本来就是其所属的地方。四步迁移框架可以对构建数据平台的过程中可能遇到的许多情况应用一种标准化的方法。这种方法在很大程度上独立于数据平台的规模和深度——我们既用于现代化小公司的数据仓库,也用于为跨国企业开发全新的数据架构。该方法基于图4-1中显示的四个主要步骤:准备和发现: 所有利益相关方都应进行初步分析,以确定需要迁移的工作负载列表和当前的痛点(例如,无法扩展、处理引擎无法更新、不需要的依赖关系等)。评估和规划: 评估在前一阶段收集的信息,定义成功的关键指标,并计划每个组件的迁移。执行: 对于每个确定的用例,决定是停用、完全迁移(数据、模式、下游和上游应用程序)还是卸载它(通过将下游应用程序迁移到不同的源)。然后,测试和验证所做的任何迁移。优化: 一旦过程开始,可以通过持续迭代来扩展和改进它。首次现代化的步骤可以仅关注核心功能。准备与发现第一步是准备与发现。这涉及定义迁移的范围,并收集与将要迁移的工作负载/用例相关的所有信息。这包括分析来自企业各个利益相关方的广泛输入,如业务、财务和IT。请这些利益相关方:列出所有与迁移相关的用例和工作负载,附有它们的相关优先级,确保其中包括合规要求、延迟敏感性等相关信息。解释他们可以通过新系统获得的预期好处(例如查询性能、能够处理的数据量、流处理能力等)。提出市场上可能满足业务需求的解决方案。进行初步的总体成本拥有分析,以估算迁移的价值。辨认培训和招聘方面的需求,以建立一支有能力的团队。您可以使用问卷调查从应用程序所有者、数据所有者和选定的最终用户那里收集这些见解。评估与规划评估与规划包括以下活动:1. 当前状态的评估: 分析每个应用程序、工作流或工具的当前技术架构,通过收集和分析服务器配置、日志、作业活动、数据流映射、容量统计、查询和集群等信息。随着旧系统规模的增加,这项工作可能会变得耗时且容易出错。因此,请寻找可以自动化整个过程的工具(例如SnowConvert、CompilerWorks、AWS Schema Conversion Tool、Azure Database Migration Service、Datametica Raven等)。这些工具可以提供工作负载拆分、依赖关系映射、复杂性分析、资源利用率、容量分析、SLA分析、端到端数据血统以及各种优化建议。2. 工作负载分类: 利用在“准备与发现”步骤中使用的问卷收集的信息,结合评估阶段的深入见解,将所有已识别的工作负载分类并选择一种适当的迁移方法:Retire(退役): 工作负载最初将保留在本地,最终将被停用。Retain(保留): 由于技术限制(例如运行在专用硬件上)或出于业务原因,工作负载将保留在本地。这可能是暂时的,直到工作负载可以进行重构,或者可能被移到协作设施,其中数据中心需要关闭,而服务不能被移动。Rehost(重新托管): 工作负载将迁移到云环境中,利用其基础设施即服务(IaaS)功能。这通常被称为“搬迁和提升”。Replatform(重新平台化): 工作负载(或其中的一部分)将进行部分更改,以提高性能或降低成本,然后迁移到IaaS。这通常被称为“搬迁和改进”。在进行搬迁和提升后进行优化,通常从容器化开始。Refactor(重构): 工作负载(或其中的一部分)将迁移到一个或多个云完全托管的平台即服务(PaaS)解决方案(例如BigQuery、Redshift、Synapse)。Replace(替换): 工作负载将被第三方现成的或SaaS解决方案完全替代。Rebuild(重建): 工作负载将被完全重新设计,使用云完全托管的解决方案,并从头开始实施。这是重新思考应用程序并计划如何充分利用云原生服务的阶段。3. 工作负载群集化: 将那些不会被退役/重建的工作负载分组成一系列基于其相对业务价值和迁移所需工作的群组。这有助于在迁移过程中确定可以遵循的优先级。例如:Group 1(第一组): 高业务价值,低迁移工作量(优先级0—快速成功)Group 2(第二组): 高业务价值,高迁移工作量(优先级1)Group 3(第三组): 低业务价值,低迁移工作量(优先级2)Group 4(第四组): 低业务价值,高迁移工作量(优先级3)在图4-2中,您可以看到一个群集化示例,其中工作负载根据所描述的优先级标准被划分为各个组。在每个组中,工作负载可能有不同的迁移方法。在整个过程中,我们建议您遵循以下实践:使过程可衡量。 确保利益相关方同意并能够使用一些业务关键绩效指标(KPI)评估现代化的结果。从最小可行产品(MVP)或概念验证(PoC)开始。 将大型任务分解为较小的任务,并确保存在任何即将进行的工作的标准模板。如果没有,请进行概念验证,并将其用作以后的模板。寻找可以作为其他转换示例的快速成功案例(优先级0工作负载),同时也可以向领导层展示这种现代化可能引入的影响。估算完成所有活动所需的总时间。 创建一个整体项目计划(在必要时与供应商或顾问合作),以定义工作负载转换所需的时间、成本和人员。在关键阶段进行过度沟通。 确保利益相关方了解计划、需要多长时间以及关键组件是什么。确保在完成云项目的过程中提供价值,并通过组织中实际可用的项目建立信心。尽量确定关键时刻,并发送即时通信,总结已完成的工作的详细信息。既然您对准备进行下一次迁移的任务有了很好的了解,让我们看看您应该如何着手进行。执行对于每个工作负载,您现在都有一个计划。具体而言,您知道将迁移什么(整个工作负载还是其中的一部分),将在何处迁移(IaaS、PaaS或SaaS),您将如何迁移(rehost、replatform、rebuild等),您将如何衡量成功,您将遵循什么模板,需要多长时间,以及将在哪些里程碑上进行沟通。为了将计划变为现实,我们建议您建立一个着陆区(landing zone),迁移到该区域,并验证已迁移的任务。着陆区(Landing Zone)首先,您需要构建所谓的着陆区(landing zone)- 所有工作负载将驻留的目标环境。这个活动根据您当前的配置可以具有不同的复杂性,但至少您将需要:定义目标项目及相关组织(例如,Google Cloud组织层次结构)设置新的身份管理解决方案或与传统或第三方解决方案集成(例如,Azure Active Directory或Okta)配置授权(例如,AWS IAM)和审计系统(例如,Azure安全日志记录和审计)定义并设置网络拓扑及相关配置一旦着陆区准备好,就是开始迁移的时候了。迁移通常建议将迁移分为多个阶段或迭代,除非您要迁移的工作负载数量非常小。这将使您能够在一次迁移一部分工作负载的情况下,积累经验和信心,处理挑战和错误。对于每个工作负载,您可能需要考虑以下事项:模式和数据迁移: 根据用例,您可能需要翻译数据模型或仅传输数据。查询转译: 在某些情况下,您可能需要将查询从源系统翻译到目标系统。如果目标系统不支持所有扩展,您可能需要重构查询。您可以利用诸如Datametica Raven或生成式人工智能等工具来减少手动工作的量。数据流水线迁移: 数据流水线是准备数据进行分析的数据工作负载的核心部分。我们将在“模式、流水线和数据迁移”中看到处理此类迁移的可能方法。业务应用程序迁移: 一旦迁移了数据,您需要迁移使用户能够与数据交互的应用程序。性能调优: 如果迁移的工作负载性能不如预期,您必须进行故障排除和修复。也许目标解决方案没有正确配置,您定义的数据模型不允许利用目标平台的所有功能,或者在转译过程中存在问题。使用基础设施即代码工具如Ansible或Terraform是至关重要的,因为它们可以自动化尽可能多的部署基础设施管理,加快每个迭代的测试和执行。验证一旦工作负载完成迁移,您需要仔细检查是否一切都已成功完成。验证工作负载的性能、运行成本、访问数据的时间等是否都符合您确定的关键绩效指标(KPI)。验证您获得的所有结果是否符合期望(例如,查询结果是否与传统环境中的相同)。一旦确保结果符合您的需求,就可以转移到第二个用例,然后是第三个,直到您将所有内容都迁移完毕。如果可能的话,尽量并行处理后续迭代,以加快整个过程。在每个工作负载结束时,记录可能遇到的问题、完成所有活动所需的时间以及相关的经验教训,以改进后续工作负载的过程是一个不错的主意。优化框架的最后一步是优化。在这里,您不会专注于每个单独迁移组件的性能。相反,您将把新系统作为一个整体来考虑,并确定引入潜在新用例以使其更加灵活和强大。您应该反思迁移所获得的成果(例如,无限的可扩展性、增强的安全性、增加的可见性等)以及作为下一步潜在的操作(例如,扩展数据收集的边界,与供应商建立更好的协同关系,开始将数据变现等)。您可以从“准备和发现”阶段收集的信息开始,了解自己在理想旅程中的位置,并考虑额外的下一步。这是一个永无止境的故事,因为创新,如商业一样,永远不会停歇,它将帮助组织在理解和利用其数据方面变得越来越好。现在您对如何利用四步迁移框架来处理迁移有了更好的理解,让我们深入了解如何估算解决方案的总体成本。估算解决方案的总体成本你刚刚看到了一个通用的迁移框架,可以帮助组织定义现代化数据平台所需执行的一系列活动。CTO、CEO或CFO可能会问的第一个问题是:“我们需要为此预算多少总成本?”在本节中,我们将审查组织通常如何应对这一挑战以及它们如何构建工作结构,以从供应商和第三方供应商那里获取报价。始终牢记,这不仅涉及技术成本,总是需要考虑人员和流程成本。现有基础设施的审计正如您所见,一切都始于对现有环境的评估。如果您对当前的基础设施状况没有清晰的了解,那么在正确评估下一代现代数据平台的定价方面肯定会面临挑战。这项工作可以通过以下三种方式之一进行:由内部IT/基础设施团队手动进行: 许多组织维护配置管理数据库(CMDB),它可以是一个文件或包含组织内使用的所有关键硬件和软件组件信息的标准数据库。这是组织内当前运行情况的一种快照,涵盖了甚至是组件之间的关系。CMDB可以更好地了解所有应用程序的运行成本,并帮助关闭不必要或多余的资源。由内部IT/基础设施团队自动进行: 目标与前一点中描述的完全相同,但旨在利用能够以自动方式收集信息的软件(与硬件相关的数据、在服务器上运行的应用程序、系统之间的关系等)。这些类型的工具(例如 StratoZone、Cloudamize、CloudPhysics 等)通常会生成与最常见的云超大规模供应商(例如 AWS、Google Cloud 和 Azure)相关的建议,例如机器的大小以及优化选项(例如每天应该运行多少小时才能执行其任务)。利用第三方参与者: 咨询公司和云供应商拥有经验丰富的人员和自动化工具,可以执行生成CMDB和前两个选项中描述的详细报告的所有活动。如果您的组织通常将IT项目外包给咨询公司,我们建议选择这种方式。接收信息/提案和报虽然这可能是您进行的唯一一次迁移,因此您必须边学边做,但咨询公司专门从事此类工作,通常更擅长处理迁移项目。当然,要验证分配给您的团队是否具有必要的经验。一些服务提供商甚至可能会执行评估并提供成本估算,如果他们看到未来的机会。确定在现代化过程中合作的最佳合作伙伴或供应商可能是一项艰巨的任务。有许多变量需要考虑(知识、能力、成本、经验),如果不以严格的方式执行,可能会变得非常复杂。这就是为什么组织通常利用三种类型的问卷调查从潜在的服务提供商那里收集信息的原因:信息请求(RFI): 用于收集有关供应商/潜在合作伙伴解决方案和服务的详细信息的问卷。它具有教育目的。提案请求(RFP): 用于收集关于供应商/合作伙伴将如何利用其产品和服务解决特定组织问题的详细信息的问卷(在这种情况下,是现代数据平台的实施)。它用于比较结果。报价请求(RFQ): 用于根据特定要求收集有关不同供应商/潜在合作伙伴定价的详细信息的问卷。它用于定量和标准化定价,以便未来进行比较。您的组织可能有关于如何执行此操作的政策和模板。与您的法务或采购部门进行沟通。否则,要求供应商向您展示他们通常使用的工具。一旦您收到所有供应商/潜在合作伙伴的回复,您应该获得选择最佳路径前进的所有信息。在某些情况下,特别是在要解决的问题可能非常模糊(例如,实时分析可能在一天内甚至一天内有多个峰值),即使对于供应商/潜在合作伙伴来说,提供有关成本的清晰详细信息也是具有挑战性的。这就是为什么有时供应商会要求进行概念验证(PoC)或最小可行产品(MVP),以更好地了解解决方案在实际用例场景中的工作方式并促使最终定价的定义。概念验证(PoC)/最小可行产品(MVP)新数据平台的设计和开发可能具有挑战性,因为大多数组织希望利用数据平台迁移的机会,不仅仅是进行简单的搬迁,而是要添加在旧世界中不可用的新功能和能力。由于这对他们来说是新的,组织(因此也包括供应商)可能无法完全理解最终行为,尤其是平台将产生的最终成本。为了解决这一挑战,组织通常要求精选的供应商或潜在合作伙伴在分析了RFP响应之后,实施最终解决方案的初始模拟(或具有有限功能的实际工作解决方案)作为第一步。这个模拟允许利益相关者体验最终解决方案的行为,以便他们可以确定是否需要任何范围的更改。模拟还使成本估算变得更加容易,尽管重要的是要注意,我们总是在谈论估算,而不是具体的定价。在采用云模型时,尤其是如果您想利用弹性,几乎不可能有清晰和最终定义的定价。弹性是云的主要优势之一,只有在生产中才能体验到。有三种方法来处理模拟的想法:概念验证(Proof of Concept,PoC): 构建解决方案的一小部分,以验证可行性、可集成性、可用性和潜在弱点。这有助于估算最终价格。目标不是触及平台的每个功能,而是验证可能需要重新设计的事物。例如,在处理流水线时,通过随机变化要处理的数据量,创建一个PoC是一个良好的实践。这将让您看到系统如何扩展,并为您提供更好的数据,以估算最终生产成本。最小可行产品(Minimum Viable Product,MVP): MVP的目标是开发一个具有非常明确定义的范围的产品,所有功能都已实施并且像一个真正的、完整的产品一样在生产环境中部署(例如,在新的DWH上实施的数据集市,连接到新的业务智能工具,以解决一个非常具体的用例)。MVP的主要优势是能够迅速从实际用户那里获得反馈,这有助于团队改进产品并产生更好的估算。混合方法: 最初,团队将以更广泛的范围开发一个通用的PoC,但深度有限(例如,端到端的数据流水线,以收集训练图像分类ML算法所需的数据),然后,基于第一轮结果和成本评估,焦点将转向开发MVP,可以看作是实施完整解决方案的第一步。现在您了解了如何估算成本,让我们深入研究迁移的第一部分——设置安全性和数据治理。建立安全性和数据治理即使数据的所有权和控制权移交给业务部门,安全性和治理仍然是大多数组织中的中心关注点。这是因为在角色定义、数据安全和活动日志记录方面需要保持一致性。在缺乏这种一致性的情况下,要遵守《被遗忘权》等法规是非常困难的,根据这些法规,客户可以要求删除与他们有关的所有记录。在本节中,我们将讨论这种中心化数据治理框架中需要具备哪些能力。然后,我们将讨论中心团队需要维护的工件以及它们在数据生命周期中如何结合在一起。框架安全性和治理试图解决的三个风险因素是:未经授权的数据访问 在将数据存储在公共云基础设施中时,有必要防止对敏感数据的未经授权访问,无论是公司机密信息还是法律保护的个人身份信息(PII)。法规合规 诸如《通用数据保护条例》(GDPR)和法律实体标识符(LEI)等法律限制了数据分析的地点、类型和方法。可见性 了解组织中存在哪些类型的数据,目前谁在使用这些数据,以及他们如何使用可能是供应组织数据的人所需的。这需要及时的数据和一个功能齐全的目录。鉴于这些风险因素,有必要建立一个全面的数据治理框架,涵盖数据的整个生命周期:数据摄取、编目、存储、保留、共享、归档、备份、恢复、防丢失和删除。这样的框架需要具备以下能力:数据血缘 组织需要能够识别数据资产并记录应用于创建每个数据资产的转换。数据分类 我们需要能够对敏感数据进行概要和分类,以确定每个数据资产或其部分需要应用哪些治理政策和程序。数据目录 我们需要维护一个包含结构元数据、血缘和分类的数据目录,并允许搜索和发现。数据质量管理 需要有一个过程来记录、监测和报告数据质量,以便为分析提供可信赖的数据。访问管理 这通常将与云 IAM 一起工作,以定义角色、指定访问权限并管理访问密钥。审计 组织和其他组织的授权个人(如监管机构)需要能够监控、审计和跟踪法律或行业规定所要求的细粒度活动。数据保护 需要能够加密数据、对其进行掩码或永久删除。要使数据治理实现运营化,您将需要建立一个允许进行这些活动的框架。云工具(如Google Cloud上的Dataplex和Azure上的Purview)提供了统一的数据治理解决方案,以管理数据资产,无论数据位于何处(即单一云、混合云或多云)。Collibra和Informatica是云不可知的解决方案,提供记录血缘、进行数据分类等功能。 根据我们的经验,这些工具中的任何一个都可以使用,但数据治理的艰苦工作并不在于工具本身,而是在于其运营化。建立一个运营模型——数据治理的流程和程序,以及一个负责确保业务团队遵守这些流程的委员会是非常重要的。该委员会还需要负责制定分类法和本体论,以确保组织内部的一致性。理想情况下,您的组织应参与并与行业标准机构保持一致。最好的组织还会定期进行持续的教育和培训活动,以确保遵守数据治理实践。既然我们已经讨论了集中化数据治理框架中需要具备哪些能力,让我们列举中央团队需要维护的工件。Artifacts为了向组织提供上述功能,中央数据治理团队需要维护以下工件:企业词典(Enterprise Dictionary): 这可以是一个简单的纸质文档,也可以是一个自动化(并执行)某些政策的工具。企业词典是组织使用的信息类型的存储库。例如,与各种医疗程序相关的代码或有关任何财务交易必须收集的必要信息都是企业词典的一部分。中央团队可以提供验证服务,以确保满足这些条件。许多读者熟悉的一个简单示例是由美国邮政服务提供的地址验证和标准化 API。企业经常使用这些 API 来确保组织内任何数据库中存储的地址都是标准的。数据类别(Data Classes): 企业词典中的各种信息类型可以分组为数据类别,并可以以一致的方式定义与每个数据类别相关的政策。例如,与客户地址相关的数据政策可能是邮政编码对一类员工可见,但对于仅在处理该客户的工单时主动参与的客户支持人员可见的更详细信息可能是可见的。政策手册(Policy Book): 政策手册列出了组织中使用的数据类别,每个数据类别的处理方式,数据的保留期限,可以存储数据的位置,需要控制对数据的访问方式等。用例政策(Use Case Policies): 通常,围绕数据类别的政策取决于用例。一个简单的例子是,客户的地址可能由履行客户订单的发货部门使用,但不会被销售部门使用。用例可能更加微妙:例如,客户的地址可能用于确定特定商店驾驶距离内的客户数量,但不用于确定特定客户是否在驾驶距离内的特定商店。数据目录(Data Catalog): 这是一个管理与数据相关的结构元数据、血缘、数据质量等的工具。数据目录充当高效的搜索和发现工具。除了上述与数据相关的工件外,中央组织还需要维护单一身份验证 (SSO) 能力,以在整个组织中提供唯一的身份验证机制。由于许多自动化服务和 API 通过密钥访问,并且这些密钥不应以明文形式存储,因此密钥管理服务通常也是中央团队的额外职责。作为现代化过程的一部分,重要的是要启动这些工件并将其放置在适当的位置,以便随着数据转移到云中,它成为强大数据治理框架的一部分。不要将数据治理推迟到云迁移之后,因为这样做的组织往往会迅速失去对其数据的控制。现在,让我们看看框架的能力和工件如何在数据的生命周期内相互关联。数据的生命周期内的治理数据治理涉及整合人员、流程和技术,贯穿数据的整个生命周期。数据的生命周期包括以下阶段:数据创建: 这是创建/捕获数据的阶段。在此阶段,应确保同时捕获元数据。例如,在捕获图像时,同时记录照片的时间和位置是很重要的。同样,在捕获点击流时,需要注意用户的会话ID、他们所在的页面、页面的布局(如果是根据用户个性化的),等等。数据处理: 在捕获数据后,通常需要对其进行清理、丰富,并将其加载到数据仓库中。作为数据血缘的一部分,捕获这些步骤是重要的。除了血缘关系,还需要记录数据的质量属性。数据存储: 通常将数据和元数据存储在持久存储中,如对象存储(S3、GCS等),数据库(Postgres、Aurora、AlloyDB等),文档数据库(DynamoDB、Spanner、Cosmos DB等),或数据仓库(BigQuery、Redshift、Snowflake等)。在这一阶段,需要确定行和列的安全性要求,以及是否需要在保存之前对任何字段进行加密。在数据治理团队的思考中,数据保护是至关重要的。数据目录: 需要将持久化的数据输入到企业数据目录中,并启用发现 API 以进行搜索。必须记录数据及其用法的详细信息。数据归档: 可以从生产环境中归档较旧的数据。如果是这样,请记得更新目录。必须注意是否法律要求进行此类归档。理想情况下,应根据适用于整个数据类别的策略自动执行归档方法。数据销毁: 可以删除已经超过法定保留期的所有数据。这也需要包含在企业的策略手册中。对于这些阶段,您必须制定数据治理政策。执行这些阶段的人员需要具备一定的访问权限。同一人在数据生命周期的不同阶段可能会有不同的职责和关注点,因此以“角色”而非“职责”来思考会更有帮助:法务: 确保数据使用符合合同要求和政府/行业法规。数据监管员: 数据的所有者,为特定数据项设置政策的人。数据管理者: 为数据类别制定政策,并确定特定数据项属于哪个类别的人。隐私专员: 确保用例不会泄露个人身份信息。数据用户: 通常是使用数据进行业务决策的数据分析师或数据科学家。既然我们已经了解了可能的迁移、安全和治理框架,让我们深入研究如何开始执行迁移。架构、流水线和数据迁移在本节中,我们将更详细地探讨可用于架构和流水线迁移的模式以及在数据传输过程中可能面临的挑战。架构迁移在将旧有应用程序迁移到新的目标系统时,你可能需要演进你的模式,以利用目标系统提供的所有功能。首先将模型按原样迁移到目标系统,连接上游(数据源和传输数据的流水线)和下游(用于处理、查询和可视化数据的脚本、过程和业务应用程序)流程,然后利用目标环境的处理引擎执行所有更改,是最佳实践。这种方法有助于确保您的解决方案在新环境中正常工作,最小化停机风险,并允许您在第二阶段进行更改。在这里,通常可以应用外观模式——一种设计方法,通过一组视图向下游流程公开隐藏底层表的视图,以隐藏最终所需更改的复杂性。然后,这些视图可以描述一个新的模式,有助于利用临时目标系统功能,而不干扰由此抽象层“保护”的上游和下游流程。如果无法采用这种方法,数据必须在进入新系统之前进行转换和转换。这些活动通常由迁移范围内的数据转换流水线执行。流水线迁移在将传统系统迁移到云中时,有两种不同的策略可以采用:卸载工作负载: 在这种情况下,保留供给源系统的上游数据流水线,并将数据的增量副本放入目标系统。最后,更新下游流程以从目标系统读取数据。然后,您可以继续卸载下一个工作负载,直到完成。完成后,可以开始完全迁移数据流水线。完全迁移工作负载: 在这种情况下,必须将所有内容都迁移到新系统(与数据流水线一起),然后淘汰相应的传统表。供给工作负载的数据需要进行迁移。它可以来自各种数据源,并且可能需要特定的转换或连接操作以使其可用。一般来说,有四种不同的数据流水线模式:ETL(抽取-转换-加载): 所有转换活动,以及数据收集和数据摄入,将由一个特定的系统执行,该系统具有适当的基础设施和适当的编程语言(该工具通常可以使用标准编程语言进行编程)。ELT(抽取-加载-转换): 与ETL类似,但有一个区别,所有转换将由数据将被摄入的处理引擎执行(正如我们在前面的章节中看到的,这是处理现代云解决方案时的首选方法)。提取和加载(EL): 这是最简单的情况,数据已经准备好,不需要进一步的转换。变更数据捕获(CDC): 这是一种用于跟踪源系统中数据更改并在目标系统中反映这些更改的模式。它通常与ETL解决方案一起工作,因为它在对下游流程进行任何更改之前存储原始记录。正如您在前一节中看到的,您可以为不同的工作负载迁移识别不同的方法。相同的方法论可以应用于数据流水线:退役(Retire): 数据流水线解决方案不再使用,因为它与旧用例相关或已被新解决方案取代。保留(Retain): 数据流水线解决方案仍然留在传统系统中,因为它可能很快就可以退役,因此启动迁移项目不是经济上可行的。也可能存在一些法规要求,禁止在公司边界之外移动数据。重托(Rehost): 数据流水线解决方案被提升并移到云环境中,利用IaaS范式。在这种情况下,除了在连接级别进行一些修改之外,您不会引入任何大的修改,其中可能需要设置专用网络配置(通常是虚拟专用网络或VPN)以启用云环境和本地环境之间的通信。如果上游流程在公司边界之外(例如,第三方提供商、其他云环境等),可能不需要VPN,因为可以使用其他技术(例如经过身份验证的REST API)以安全的方式建立通信。在继续之前,有必要向云供应商验证底层系统中是否存在任何技术限制,以防止解决方案的正确执行,并仔细检查可能的许可证限制。重平台(Replatform): 在此情况下,数据流水线解决方案的一部分在迁移之前进行了转换,以便利用云的功能,例如PaaS数据库或容器化技术。在“重托”描述中突出显示的连接方面的考虑仍然有效。重构(Refactor): 流水线解决方案将迁移到一个或多个云完全托管的PaaS解决方案(例如,Amazon EMR、Azure HDInsight、Google Cloud Dataproc、Databricks)。在采用此方法时,最好采用与整个迁移相同的迭代方法: 准备并发现要迁移的作业,可能按复杂性组织它们。 计划和评估可能要迁移的MVP。 执行迁移并根据定义的KPI评估结果。 对所有其他作业进行迭代,直到结束。 在“重托”部分突出显示的连接方面的注意事项仍然有效。替代(Replace): 流水线解决方案将被第三方现成或SaaS解决方案(例如,Fivetran、Xplenty、Informatica等)完全替换。在“重托”部分突出显示的连接方面的注意事项仍然有效。重建(Rebuild): 流水线解决方案将使用云完全托管的解决方案(例如,AWS Glue、Azure Data Factory、Google Cloud Dataflow)进行完全重建。在“重托”部分突出显示的连接方面的注意事项仍然有效。在迁移阶段,特别是与目标系统集成时,您可能会发现您的数据流水线解决方案与确定的目标云解决方案并不完全兼容。您可能需要一个连接器(通常称为接收器),它能够在数据流水线解决方案(例如ETL系统)与目标环境之间进行通信。如果该解决方案的接收器不存在,您可能能够生成一个文件作为过程的输出,然后在以下步骤中摄入数据。虽然这种方法会引入额外的复杂性,但在紧急情况下(等待供应商提供连接器时)是一种可行的临时解决方案。数据迁移现在您已经准备好新的模式和流水线,您可以开始迁移所有数据了。您应该着重考虑如何处理数据传输。您可能希望将所有本地数据迁移到云中,甚至是陈旧的老磁带(也许将来某天会有人要求这些数据)。您可能会面临一个事实,即在周末通过单个FTP连接完成任务可能是不够的。计划数据迁移需要计划。您需要确定并牵涉到: 技术负责人 能够提供执行迁移所需资源访问权限的人员(例如存储、IT、网络等)。批准者 能够为您提供所需批准以获取数据访问权限并启动迁移的人员(例如数据所有者、法律顾问、安全管理员等)。交付 迁移团队。如果可用,可以是组织内部的人员,或者是属于第三方系统集成商的人员。然后,您需要收集尽可能多的信息,以充分了解您需要做什么,以及以什么顺序进行(例如,也许您的迁移团队需要获准访问包含要迁移的数据的特定网络存储区域),以及可能会遇到的阻碍。以下是一些问题(不是详尽无遗的),在继续之前您应该能够回答这些问题:您需要迁移哪些数据集?基础数据在组织中的位置在哪里?您被允许迁移哪些数据集?是否有任何特定的法规要求您必须遵守?数据将落地在哪里(例如对象存储与DWH存储)?目的地区域是什么(例如欧洲、中东和非洲、英国、美国等)?是否需要在传输之前执行任何转换?您想要应用哪些数据访问策略?这是否是一次性的传输,还是您需要定期迁移数据?数据传输的资源有哪些?分配的预算是多少?您是否有足够的带宽来完成传输,以及是否足够的时间?您是否需要利用脱机解决方案(例如Amazon Snowball、Azure Data Box、Google Transfer Appliance)?完成整个数据迁移需要多长时间?一旦了解了正在迁移的数据的属性,您需要考虑影响迁移的可靠性和性能的两个关键因素:容量和网络。区域性容量和连接到云的网络在处理云数据迁移时,通常需要仔细考虑两个元素:区域性容量和连接到云的网络质量。区域性容量:云环境并非无限可扩展。事实上,硬件需要由云提供商在区域位置购买、准备和配置。一旦确定了目标架构和处理数据平台所需的资源,你还应该向选定的超大规模提供商提交一个区域容量计划,以确保数据平台将拥有满足平台使用和未来增长需求的所有所需硬件。通常,他们希望了解迁移数据的数量,以及在云中生成的数据量,您需要用来处理数据的计算量,以及与其他系统进行交互的次数。所有这些组成部分将作为输入提供给超大规模,以确保底层基础设施将从第一天开始准备好为所有工作负载提供服务。在出现缺货的情况下(如果您的用例涉及GPU,则这种情况很常见),您可能需要在其他地区选择相同的服务(如果没有合规性/技术影响),或者利用其他计算类型服务(例如,IaaS与PaaS相比)。连接到云的网络:即使网络现在被视为大宗商品,在每个云基础设施中仍起着至关重要的作用:如果网络速度慢或无法访问,组织的某些部分可能会完全与其业务数据断开连接(即使在利用本地环境时也是如此)。在设计云平台时,你需要首先考虑以下一些问题:我的组织将如何连接到云?我将利用哪个合作伙伴来建立连接?我是利用标准互联网连接(可能在其上使用VPN),还是想要支付额外的专用连接费用以确保更好的可靠性?所有这些主题通常都在RFI/RFP问卷中讨论,但它们也应该是与你选择设计和实施平台的供应商/合作伙伴进行的最初的研讨会之一的一部分。连接到云的主要方式有三种:公共互联网连接: 利用公共互联网网络。在这种情况下,组织通常在公共互联网协议之上利用VPN来保护其数据,并确保适当的可靠性水平。性能与组织能否靠近所选云超大规模的最近点有关。合作伙伴互联: 这是组织可能用于其生产工作负载的典型连接之一,特别是当他们需要具有高吞吐量的性能保证时。这种连接是组织与选定合作伙伴之间的连接,然后该合作伙伴负责与选定的超大规模建立连接。通过利用电信服务提供商的普遍性,组织可以建立高性能的连接,而价格又适中。直接互联: 这是可能的最佳连接,其中组织直接(物理上)与云提供商的网络连接。 (当双方都在同一物理位置拥有路由器时,这是可能的。)可靠性、吞吐量和一般性能是最佳的,并且定价可以直接与所选的超大规模进行讨论。有关如何配置这三种连接选项的更多详细信息,请参阅Azure、AWS和Google Cloud的文档。通常在PoC/MVP阶段,选择公共互联网连接选项,因为设置速度更快。在生产中,合作伙伴互联是最常见的,特别是当组织希望利用多云方法时。转移选项选择将数据传输到云端的方式时,请考虑以下因素:成本 考虑数据传输可能涉及的以下潜在成本:网络 在进行数据传输之前,您可能需要提升网络连接性。也许您的带宽不足以支持迁移,您需要与供应商协商以增加额外的线路。云服务提供商 向云服务提供商上传数据通常是免费的,但如果您不仅从本地环境导出数据,还从另一个超级规模云提供商导出数据,可能会收取出口费用(通常每导出1GB收费),还可能收取读取费用。产品 您可能需要购买或租赁存储设备以加速数据传输。人员 执行迁移的团队。时间 了解您需要传输的数据量和您可用的带宽是重要的。一旦了解了这些,您将能够确定传输数据所需的时间。例如,如果您需要传输200TB的数据,而您只有10Gbps的带宽可用,您将需要大约两天的时间来完成传输。这假定带宽完全可用于数据传输,这可能并非总是如此。如果在分析过程中发现需要更多带宽,您可能需要与互联网服务提供商(ISP)合作,请求增加带宽或确定一天中带宽可用的时间。这也可能是与云供应商合作实施直连的正确时机。这可以防止您的数据通过公共互联网传输,并且可以为大规模数据传输提供更一致的吞吐量(例如,AWS Direct Connect,Azure ExpressRoute,Google Cloud Direct Interconnect)。离线与在线传输 在某些情况下,在线传输可能不可行,因为它会花费太长时间。在这种情况下,选择使用离线处理并利用存储硬件。云供应商提供这种服务(例如,Amazon Snowball数据传输,Azure Data Box,Google Transfer Appliance),特别适用于数百TB到PB级别的数据传输。您可以从云供应商那里订购一个物理设备,必须连接到您的网络。然后,您将复制数据,数据将默认加密,然后请求发货到最近可用的供应商设施。一旦交付,数据将被复制到云中的适当服务(例如,AWS S3,Azure Blob Storage,Google Cloud Storage),并且可以随时使用。可用工具 一旦清理了所有网络动态,您应该决定如何处理数据上传。根据您可能要定位的系统(例如,blob存储,DWH解决方案,数据库,第三方应用程序等),通常您可以选择以下选项:命令行工具 工具(例如,AWS CLI,Azure Cloud Shell或Google Cloud SDK)允许您与云提供商的所有服务进行交互。您可以自动化和编排上传数据到所需目的地所需的所有流程。根据最终的工具,您可能需要在上传数据到最终目的地之前通过中间系统(例如,首先通过blob存储再将数据传入DWH) ,但由于各种工具的灵活性,您应该能够轻松实施工作流程,例如利用bash或PowerShell脚本。REST API 这将允许您将服务与您可能想要开发的任何应用程序集成,例如,您已经实施和利用的内部迁移工具或您可能想要为特定目的开发的全新应用程序。物理解决方案 如前述离线与在线传输的描述中所讨论的,用于离线迁移的工具。第三方商业现成解决方案 这些解决方案可能提供更多功能,如网络节流,自定义高级数据传输协议,高级编排和管理功能以及数据完整性检查。迁移步骤您的迁移将包括六个阶段(参见图4-3):上游过程被修改以供应当前的遗留数据解决方案,以满足目标环境的需求。下游过程从遗留环境中读取的方式被修改为从目标环境中读取。历史数据以批量方式迁移到目标环境。在这一阶段,上游过程也被迁移到写入目标环境。下游过程现在连接到目标环境。利用CDC(Change Data Capture)管道,在遗留环境完全被废弃之前,可以在遗留和目标环境之间保持数据同步。下游过程变得完全操作,利用目标环境。有一些最终检查你应该执行,以确保不会遇到瓶颈或数据传输问题:执行功能测试。 您需要执行一个测试来验证整个数据传输迁移是否正常工作。理想情况下,在执行MVP期间执行此测试,选择大量数据,充分利用可能在整个迁移过程中使用的所有工具。这一步的目标主要是验证您可以正确操作数据传输,同时发现潜在的可能导致项目停滞的问题,例如无法使用工具(例如,您的工作人员未经培训,或者您没有系统集成商的充分支持)或网络问题(例如,网络路由)。执行性能测试。 您需要验证当前基础架构是否能够处理大规模迁移。为此,您应该选择一个数据的大样本(通常在3%到5%之间)进行迁移,以确认您的迁移基础设施和解决方案是否正确根据迁移需求进行扩展,并且不会出现特定的瓶颈(例如,源存储系统速度慢)。执行数据完整性检查。 在数据迁移过程中可能遇到的最关键问题之一是,由于错误而删除了迁移到目标系统的数据,或者数据损坏且无法使用。有一些方法可以保护数据免受这种风险:在目标位置启用版本控制和备份,以减轻意外删除的影响。在删除源数据之前验证您的数据。如果您使用标准工具执行迁移(例如,命令行界面或REST API),则必须自己管理所有这些活动。但是,如果您采用第三方应用程序,如Signiant Media Shuttle或IBM Aspera on Cloud,很可能已经默认实施了多种检查。 (我们建议在选择解决方案之前仔细阅读应用程序说明中的可用功能。)总结在本章中,您已经看到了数据现代化旅程的实际方法,并审查了一个通用的技术迁移框架,可应用于从传统环境到现代云架构的任何迁移。主要要点如下:着重于现代化数据工作流,而不仅仅是升级单个工具。选择适合特定工作的正确工具将帮助您降低成本,充分发挥工具的潜力,并提高效率。可以在四个步骤中实施可能的数据迁移框架:准备和发现、评估和规划、执行和优化。准备和发现是一个关键的步骤,您在其中专注于定义迁移的范围,然后收集与已确定要迁移的各种工作负载/用例相关的所有信息。评估和规划是一个步骤,您在其中定义和计划完成已确定目标所需的所有活动(例如,对要迁移的工作负载进行分类和聚类,定义关键绩效指标,定义可能的MVP,以及定义与相关里程碑有关的迁移计划)。执行是一个步骤,在该步骤中,您迭代执行迁移,每次迭代都改善整个过程。优化是一个步骤,在该步骤中,您将整个新系统视为一个整体,并确定引入的可能新用例,使其更加灵活和强大。了解当前情况以及最终解决方案的总成本是一个复杂的步骤,可能涉及多个参与者。组织通常利用RFI、RFP和RFQ从供应商/潜在合作伙伴那里获取更多信息。治理和安全问题仍然是大多数组织的集中关注。这是因为在角色定义、数据安全和活动记录方面需要一致性。迁移框架必须与一个非常明确定义的治理框架相一致,这对数据的整个生命周期至关重要。在迁移数据架构时,可能有助于利用类似外观模式的模式,使目标解决方案功能的采用变得更容易。当使用云解决方案时,连接性和保障区域容量是至关重要的。网络配置、带宽可用性、成本、人员、工具、性能和数据完整性是必须为每个工作负载澄清的主要要点。在前面的四章中,您已经了解了为什么要构建数据平台,达到目标的策略,如何提升员工技能,以及如何进行迁移。在接下来的几章中,我们将深入探讨架构细节,首先从如何构建现代数据湖开始。
0
0
0
浏览量2033
攻城狮无远

《流式Data Mesh》第一章:Data Mesh介绍

第一章:Data Mesh介绍年轻人认为在某个时候,数据架构是容易的,然后数据的规模、速度和多样性增长了,我们需要新的、更复杂的架构。实际上,数据问题一直都是组织问题,因此从未被真正解决过。 ——Gwen(Chen)Shapira,《Kafka: The Definitive Guide(O'Reilly)》如果你在一家不断发展的公司工作,你会意识到公司增长与数据入口规模之间存在着正相关关系。这可能是由于现有应用程序的增加使用或新添加的应用程序和功能所导致的。数据工程师的任务是在保持服务级别协议(SLA)的同时,组织、优化、处理、管理和提供这些不断增长的数据给消费者。很可能,这些SLA是在没有数据工程师的参与下向消费者保证的。当你开始处理如此大量的数据时,你会首先学到的是,当数据处理开始逼近这些SLA所做出的保证时,更多的关注点会放在保持SLA内,而数据治理等方面则会被边缘化。这反过来会导致对所提供数据的不信任,最终也会对分析产生不信任——而这些分析可以用来改进运营应用程序以产生更多收入或防止收入损失。如果将这个问题在企业的所有业务线上复制,你会发现很多不满的数据工程师试图在数据湖和数据处理集群的能力范围内加快数据流水线的速度。这正是我经常发现自己处于的位置。那么,什么是数据网格(data mesh)?在“数据网格”中,“网格”一词来自于“服务网格”(service mesh)一词,它是一种在平台级别而非应用程序层面上添加可观测性、安全性、发现和可靠性的手段。服务网格通常作为一组可扩展的网络代理与应用程序代码一起部署(有时称为旁路模式)。这些代理处理微服务之间的通信,并作为引入服务网格特性的点。微服务架构是流数据网格架构的核心,并引入了一种根本性的变化,通过创建松耦合、更小、易于维护、敏捷和独立可扩展的服务,将单块应用程序分解为部署在任何单块架构容量之外的服务。在图1-1中,你可以看到将单块应用程序分解以创建更可扩展的微服务架构的过程,而不会失去应用程序的业务目的。数据网格(data mesh)旨在实现微服务对单块应用程序所取得的目标。在图1-2中,数据网格试图创建一种松耦合、更小、易于维护、敏捷和独立可扩展的数据产品,超越任何单块数据湖架构的能力范围。Zhamak Dehghani(本书中简称为ZD)是数据网格模式的先驱者。如果你对ZD及她的数据网格博客不熟悉,我强烈建议你阅读她的博客以及她非常受欢迎的书籍《Data Mesh》(O'Reilly)。在本书中,我将简要介绍数据网格架构模式的基本概念,帮助你对构成数据网格的支柱有一个基本的理解,以便在整本书中能够更好地参照这些概念。在本章中,我们将先介绍数据网格的基础知识,然后在第二章中介绍流数据网格。这将有助于打下更好的基础,以便更好地理解流数据的相关概念。接下来,我们将讨论其他与数据网格相似的架构,以帮助区分它们。这些其他架构在设计数据网格时往往会让数据架构师感到困惑,因此在将数据网格引入流数据之前,最好先搞清楚这些概念。数据分割ZD的博客讨论了数据分割(data divide),在图1-3中有所示,以描述数据在企业中的流动。这个基础概念将有助于理解数据如何驱动业务决策以及相关的单块问题。简单概括一下,操作数据平面包含支持业务应用程序的数据存储。通过提取、转换和加载(ETL)过程,将操作数据复制到分析数据平面,因为您不希望在操作数据存储上执行分析查询,这会占用生成业务收入所需的计算和存储资源。分析数据平面包含数据湖/数据仓库/数据湖仓库,用于获取洞察力。这些洞察力会反馈到操作数据平面,以进行改进和业务增长。在操作数据平面上,借助Kubernetes的帮助,应用程序已从笨重的单块应用程序发展为敏捷和可扩展的微服务,它们互相通信,形成了一个服务网格。然而,在分析数据平面上的情况并非如此。数据网格的目标就是这样:将单块的分析数据平面分解为一种去中心化的解决方案,以实现敏捷、可扩展和易于管理的数据。在本书中,我们将始终提到操作数据平面和分析数据平面,因此在我们开始构建流数据网格示例时,早期建立这种理解非常重要。数据网格支柱数据网格架构的基础由表1-1中定义的支柱支持。我们将在以下部分快速总结它们,涵盖每个支柱的重要概念,以便在后面的章节中专注于实现流数据网格。请注意,这里提到的表1-1是书中的一个表格,我无法直接提供其中的具体内容。数据所有权和数据作为产品构成了数据网格支柱的核心。自助式数据平台和联邦计算数据治理是支持前两个支柱的存在。我们将简要讨论这四个支柱,并在接下来的每一章节中专门探讨其中的一个支柱,从第三章开始。数据所有权正如之前提到的,数据网格的主要支柱是将数据分散,使其所有权归还给最初生成数据的团队(或至少是那些最了解和关心数据的团队)。该团队中的数据工程师将被分配到一个特定的领域,其中他们是数据专家。一些领域的示例包括分析、库存和应用程序。他们很可能以前是在单块数据湖中进行读写操作的团队。领域负责从真实数据源捕获数据。每个领域会对数据进行转换、丰富,并最终将数据提供给其消费者。有三种类型的领域:仅生产者领域:只产生数据,并不从其他领域消费数据。仅消费者领域:只从其他领域消费数据。生产者和消费者领域:分别向其他领域生产和消费数据。数据即产品由于数据现在属于一个领域,我们需要一种方法在领域之间提供数据。由于这些数据需要可消费和可用,因此需要将其视为任何其他产品,以便消费者能够获得良好的数据体验。从现在开始,我们将称任何被提供给其他领域的数据为数据产品。确定数据产品的“良好体验”是一个需要在数据网格的各个领域之间达成一致的任务。达成共识的定义将有助于在网格中参与的各个领域之间提供明确定义的期望。表1-2列出了一些思考的想法,这些想法将有助于为数据产品消费者创造“良好体验”,并在领域中构建数据产品时提供帮助。联邦计算数据治理由于领域用于创建数据产品,并且在多个领域之间共享数据产品最终构建了一个数据网格,因此我们需要确保提供的数据遵循一些准则。数据治理涉及创建和遵守一组全局规则、标准和政策,应用于所有数据产品及其接口,以确保协作和互操作的数据网格社区。这些准则必须在参与数据网格领域之间达成共识。在考虑为数据网格设置数据治理时,以下是一些需要考虑的事项:授权、身份验证、数据和元数据复制方法、模式、数据序列化以及令牌化/加密。自助式数据平台由于遵循这些支柱需要一套高度专业化的技能,因此必须创建一套服务来构建数据网格及其数据产品。这些工具需要与更广泛的工程师可访问的技能兼容。在构建数据网格时,有必要使领域中的现有工程师能够执行所需的任务。领域必须从其操作存储中捕获数据,对数据进行转换(联接或丰富、聚合、平衡),并将其数据产品发布到数据网格中。自助服务是使数据网格易于采用且易于使用的“简化按钮”。简而言之,自助服务使领域工程师能够承担数据工程师在企业各个业务线中负责的许多任务。数据网格不仅分解了单块数据湖,还将数据工程师的单块角色分解为领域工程师可以执行的简单任务。数据网格图示图1-4显示了三个不同的领域:数据科学、移动应用和库存。数据科学领域消费来自应用领域的数据,应用领域拥有来自移动应用的数据。库存领域消费数据科学领域的数据,用于库存物流,例如减少距离或将供应移动到更有购买倾向的位置。最后,应用领域可能会消费新训练模型的引用,将其加载到移动应用程序中,以创建更新的个性化体验。数据治理在数据产品的生产者和消费者之间创建访问控制,并提供诸如模式定义和血统等元数据。在某些情况下,主数据以及参考数据可能与实施相关。数据治理还允许我们为这些资源创建适当的访问控制。连接领域之间的边缘在它们之间复制数据。它们建立了领域之间的连接,创建了"网格"。图1-4是一个数据网格的高级图表,我们将在接下来的章节中深入探讨。在后面的章节中,我们还将讨论如何通过组建一个遵循ZD分散式数据平台愿景精神的数据团队来识别和构建领域。再次强调,图1-4是一个非流式数据网格的高级视图。这样的实施并不意味着流式处理是发布数据供消费使用的唯一解决方案。其他替代方案可以在领域之间和领域内传输数据。第2章专门介绍了流式数据网格及其优势。其他类似的架构模式前一部分以ZD的愿景为基础,以非常高的层次总结了数据网格架构。许多数据架构师喜欢指出现有的数据架构与数据网格具有相似的特征。这些相似之处可能足以让架构师将他们的现有实现标记为符合和满足数据网格的支柱。这些架构师可能完全正确或部分正确。其中一些数据架构包括数据织物、数据网关、数据即服务、数据民主化和数据虚拟化。数据编织(Data Fabric)数据编织是一种与数据网格非常相似的模式,两者都提供了涵盖数据治理和自助服务的解决方案:发现、访问、安全性、集成、转换和血缘(图1-5)。 在撰写本文时,关于数据网格和数据编织之间的区别尚不清楚。简单来说,数据编织是一种元驱动的方式,用于连接不同的数据集和相关工具,提供一致的数据体验,并以自助服务的方式提供数据。虽然数据网格和数据织物都致力于解决许多相同的问题,即能够在一个单一的组合数据环境中处理数据,但方法是不同的。数据织物使用户能够在分布式数据之上创建一个单一的虚拟层,而数据网格进一步赋予了分布式的数据生产者以管理和发布数据的权力。数据织物通过在数据织物内部的API中应用数据集成,实现了低至无代码的数据虚拟化体验。然而,数据网格允许数据工程师为进一步接口编写代码的API。 数据织物是一种跨多种技术和平台提供数据访问的架构方法,基于技术解决方案。一个关键的区别是,数据网格不仅仅是技术:它是一个涉及人员和流程的模式。与数据织物拥有整个数据平台的所有权不同,数据网格允许数据生产者专注于数据生产,数据消费者专注于数据消费,并允许混合团队使用其他数据产品、融合其他数据创建更有趣的数据产品,并在此过程中考虑一些数据治理问题。在数据网格中,数据是分散的,而在数据织物中,允许数据的集中化。而像数据湖这样的数据集中化方式会带来与之相关的巨大问题。数据网格试图通过将数据域分解为更小、更敏捷的群组,将微服务的方法应用于数据。 值得庆幸的是,支持数据织物的工具也可以支持数据网格。显而易见,数据网格将需要支持域工程中数据产品的自助服务,并提供构建和提供数据产品的基础设施。在图1-6中,我们可以看到数据网格具有数据织物的所有组件,但以网格的形式实施。简而言之,数据织物是数据网格的子集。数据网关和数据服务数据网关类似于API网关,但是用于服务数据: 数据网关的作用类似于API网关,但侧重于数据方面。数据网关提供抽象、安全性、扩展性、联邦性和基于契约的开发功能。 ——Bilgin Ibryam在2020年5月的InfoQ文章《云原生时代的数据网关》中提到同样,数据即服务(DaaS)是一种模式,它从其原始来源提供数据,通过遵循开放标准进行完全管理,并由软件即服务(SaaS)提供。这两种架构模式都提供数据,但DaaS更注重从云端提供数据。可以说,DaaS在云端实现了数据网关,而以前可能只能在本地环境中实现。图1-7显示了一个示例。 将数据网关和DaaS的概念结合起来,更容易将数据标识为来自原始来源,特别是如果数据在源头上是不可变的。将源自本地数据中心的数据复制到云端的SaaS中将是一项要求。除了由SaaS提供支持之外,数据网格满足了DaaS的所有要求。在数据网格中,SaaS是一种选择,但目前还没有SaaS数据网格提供商可以使实施数据网格变得容易。数据民主化(Data Democratization)数据民主化是将数字信息对普通非技术用户的信息系统变得可访问的过程,而无需依赖IT的参与:Data democratization means that everybody has access to data and there are no gatekeepers that create a bottleneck at the gateway to the data. It requires that we accompany the access with an easy way for people to understand the data so that they can use it to expedite decision-making and uncover opportunities for an organization. The goal is to have anybody use data at any time to make decisions with no barriers to access or understanding. ———Bernard Marr, “What Is Data Democratization? A Super Simple Explanation and the Key Pros and Cons,” Forbes, July 2017数据网格通过其数据产品、自助服务和低代码方法来共享和创建数据,满足了这一定义。对数据的简单访问在保持企业数据驱动至关重要。快速获取数据以创建分析洞察将使企业能够更快地响应运营变化,从而节省高昂的成本。数据虚拟化数据虚拟化是一种特殊的数据集成技术,它可以在不将任何数据移动到新位置的情况下,提供对实时数据的访问,跨多个来源和非常大的数据量。许多人认为数据虚拟化是实现数据网格的解决方案,因为它可以满足数据网格架构的所有支柱,特别是不需要将任何数据移动到新位置的理念,这需要使用单独的ETL过程来复制数据。当我们开始讨论流数据网格时,我们需要了解数据虚拟化和数据复制之间的区别,流数据网格采用的是后者的方法。如前所述,数据虚拟化不会将数据移动到新位置,因此在执行查询时不会多次复制数据,与数据复制不同。这在数据分布广泛时可能是有益的。然而,如果数据存在于多个全球区域,跨长距离执行查询将显著影响查询性能。使用ETL将数据从操作层复制到分析层的目的不仅是防止在操作数据存储上执行特定查询(这将影响支持业务的操作应用程序),还将数据更接近执行分析的工具。因此,仍然需要ETL,并且数据复制是不可避免的:Data virtualization comes from a world-view where data storage is expensive and networking is cheap (i.e., on premise). The problem it solved is making the data accessible no matter where it is and without having to copy it. Copies in databases are bad because of the difficulty in guaranteeing consistency. Data replication is based on the view that copies aren’t bad (because the data has been mutated). Which turns out to be more useful when it’s the network that’s expensive (the cloud). Mic Hussey, principal solutions engineer at Confluent考虑使用混合复制和虚拟化的方式。随着数据开始存储在不同的全球区域,将数据复制到一个区域,然后在该区域内实施数据虚拟化可能会更好。专注于实施ZD的数据网格定义中,并不要求使用流式处理。在数据网格中,可以使用批处理或流式处理API来提供数据产品。ZD还指出,数据网格应仅用于分析用例。我们将超越分析,将数据网格应用于提供DaaS和数据织物解决方案的架构。第二章将重点介绍流式处理在数据网格中的一些优势。将数据网格的基本原理应用于流式处理环境将需要我们做出实现选择,以便通过示例构建流式数据网格。本书中所做的实现选择并不一定是流式数据网格的要求,而是为了在遵循ZD的数据网格支柱的同时,帮助将流式解决方案整合在一起的选择。流式实现所需的两个关键技术包括: (1)流式技术,例如Apache Kafka、Redpanda或Amazon Kinesis; (2)以异步资源形式公开数据的方式,使用AsyncAPI等技术。随着本书的进展,我们将重点介绍使用Kafka和AsyncAPI的实现。Kafka本书使用Apache Kafka作为流式数据平台的实现。本书不要求您了解Apache Kafka,但将涵盖使流式数据网格正常运行所需的重要功能。此外,Kafka可以用类似的流式平台如Apache Pulsar或Redpanda替代,它们都遵循Apache Kafka生产者和消费者框架。值得注意的是,那些能够将数据保存在提交日志中的流式平台将最好地实现本书中描述的流式数据网格模式。提交日志在第二章中有详细介绍。AsyncAPIAsyncAPI是一个开源项目,简化了异步事件驱动架构(EDA)。AsyncAPI是一个框架,允许实现者生成代码或配置,无论使用哪种语言或工具,以产生或消费流式数据。它允许您以简单、描述性和互操作的方式描述AsyncAPI配置中的任何流式数据。它将成为我们构建流式数据网格的基础组件之一。仅仅使用AsyncAPI无法完全定义流式数据网格中的数据产品。然而,由于AsyncAPI是可扩展的,我们将扩展它以包含先前定义的数据网格支柱。我们将在后续章节中详细介绍AsyncAPI的细节。有了数据网格及其定义的支柱,让我们更深入地了解如何将本章讨论的支柱和概念应用于流式数据,从而创建一个流式数据网格。
0
0
0
浏览量2012
攻城狮无远

第四章:生成数据观察结果

正如第三章所解释的,数据可观察性结合了技术和人员的作用,从数据角度收集系统状态的信息以及对该状态的期望。然后,它利用这些信息来使系统更具适应性或更加弹性。本章将解释如何应用数据可观察性实践。我将从“数据源的数据可观察性”开始,这是一种引入收集策略到日常数据工作的方法,并向您展示如何最大程度地减少对效率的影响。然后,本章将详细说明如何实现订阅软件交付生命周期的期望,例如持续集成和持续部署(CI/CD)。与任何新兴的实践和技术一样,为了增加数据可观察性的采用率,您需要降低参与的门槛;这样,人们就没有太多理由反对这种变化。然而,人员也是解决方案的一部分,因为他们在该过程中的参与对于确定他们的期望并对规则进行编码至关重要。为此,您将学习减少生成观察结果所需的工作量的几种方法,并了解如何在开发生命周期的适当阶段引入它们。在数据源头第二章解释了帮助观察者的信息的来源和类型。但是,如何从这些来源生成和收集信息呢? 这始于数据源头的数据可观察性。术语“源头”指的是负责读取、转换和写入数据的应用程序。正如第3章所解释的,这些应用程序可能是问题的根本原因,也可能是解决问题的手段。此外,与数据本身不同,这些应用程序在工程师和组织的控制之下。因此,数据源头的数据可观察性方法依赖于应用程序能够生成遵循第2章中介绍的模型的数据观察,换句话说,这些应用程序被制作成数据可观察的。 纯数据和分析观察,如数据源、模式、血统和指标,与读取、转换和写入活动相关。本节中描述的策略通过解释运行这些活动时要考虑什么来解决这些活动。在数据源头生成数据观察在源头生成数据观察始于生成额外的信息,捕捉数据的特定活动行为:读取、转换和写入。例如,开发人员可以添加包含生成有关其应用程序执行的可见性所需信息的日志行。指南使用第2章中介绍的通道,即日志、指标、跟踪或血统,来传达可以集中在日志系统中的观察结果。在下一节中,您将学习如何创建JSON格式的数据观察,这些观察可以收集(发布)到本地文件、本地服务、远程(网络)服务或类似的目标。例如,根据第2章中的数据可观察核心模型,Postgres表的数据源和模式实体将如示例4-1所示。{ "id": "f1813697-339f-5576-a7ce-6eff6eb63249", "name": "gold.crm.customer", "location": "main-pg:5432/gold/crm/table", "format": "postgres" } { "id": "f1813697-339f-5576-a7ce-6eff6eb63249", "data_source_ref": {"by_id": "e21ce0a8-a01e-5225-8a30-5dd809c0952e"}, "fields": [ { "name": "lastname", "type": "string", "nullable": true }, { "name": "id", "type": "int", "nullable": false } ] }能够在一个集中的平台上以相同的模型收集数据观察结果,对于在规模上生成数据可观察性的价值至关重要,比如跨应用程序、团队和部门。这就是为什么使用数据可观察核心模型对于轻松汇总数据观察结果,尤其是沿着血统线的汇总,非常重要。让我们看看如何使用Python生成数据观察结果,使用低级API,这将用于介绍更高级的抽象(在接下来的章节中介绍)。Python中的低级API使用低级API的策略需要大量的时间和参与,因为您需要明确地创建每个观察结果。但是,这种策略也为您提供了最大的灵活性,因为它不涉及任何高级抽象。 另一方面,在这个级别支持数据可观察性,特别是在探索和维护期间,需要开发人员保持一致,并始终考虑他们可能在生产环境中想要观察的内容(例如,任何高级开发人员都应该为日志和检查生成与业务逻辑一样多的行)。在开发过程中,开发人员必须通过生成相关的观察结果来为应用程序的逻辑或行为修改创建可见性。此类观察的示例包括与新表的连接、新文件的创建或带有新字段的结构更改。 在接下来的几节中,您将通过一个完整的示例,了解使用Python编写的数据应用程序,这些应用程序在处理数据时生成数据观察,并完成以下操作:了解没有数据可观察性能力的应用程序。添加生成数据观察的指令以及它们的目的。深入了解使用这种策略的利弊。数据管道的描述在本章的其余部分,我将使用一种用Python编写的数据管道,我们将使其具有数据可观测性。GitHub上的管道代码允许您运行本章中的示例。它使用pandas库来处理CSV文件,由两个应用程序组成,即入库和报告,如图4-1所示。两个应用程序(入库和报告)都使用Python和pandas,并从“每日股票价格”数据源中共享数据,以创建两个下游报告(BuzzFeed股票和AppTech)。入库应用程序读取股市团队每月提供的每日股票价格的CSV文件。团队按年份和月份对文件进行了分区,然后将价格合并到存储为单独文件的月度视图中,如示例4-2中的入库所示。 import pandas as pd AppTech = pd.read_csv( "data/AppTech.csv", parse_dates=["Date"], dtype={"Symbol": "category"}, ) Buzzfeed = pd.read_csv( "data/Buzzfeed.csv", parse_dates=["Date"], dtype={"Symbol": "category"}, ) monthly_assets = pd.concat([AppTech, Buzzfeed]) \ .astype({"Symbol": "category"}) monthly_assets.to_csv( "data/monthly_assets.csv", index=False )在运行了第一个脚本并且文件可用之后,可以运行其余的流水线脚本来生成BuzzFeed和AppTech股票报告。这个示例只有一个脚本,即报告的Python文件,如示例4-3所示。import pandas as pd all_assets = pd.read_csv("data/monthly_assets.csv", parse_dates=['Date']) apptech = all_assets[all_assets['Symbol'] == 'APCX'] buzzfeed = all_assets[all_assets['Symbol'] == 'BZFD'] buzzfeed['Intraday_Delta'] = buzzfeed['Adj Close'] - buzzfeed['Open'] apptech['Intraday_Delta'] = apptech['Adj Close'] - apptech['Open'] kept_values = ['Open', 'Adj Close', 'Intraday_Delta'] buzzfeed[kept_values].to_csv("data/report_buzzfeed.csv", index=False) apptech[kept_values].to_csv("data/report_appTech.csv", index=False)为了正确执行流水线,尊重报告和摄取应用程序之间的依赖关系非常重要。换句话说,必须在运行报告应用程序之前成功运行摄取应用程序。否则,流水线将失败。实现这一目标的方法是使用一个编排器,在报告之前运行摄取应用程序,比如Dagster和Airflow。事实上,编排器是另一个需要配置的应用程序,需要在数据级别明确硬编码应用程序之间的依赖关系。但是,应用程序本身仍然不了解它们的下游依赖关系。在编排器中硬编码依赖关系的反面是,这是一个需要维护的新资产(例如,明确依赖关系的准确性)。此外,在创建流水线时会施加额外的约束,因为团队必须保持独立,因此不能简单地更新现有的流水线以满足他们自己的需求。因此,必须在更高的级别创建一个扩展,通过添加一个新的分离的DAG来实现,这可能会断开明确的依赖关系。回到我们的流水线,让我们讨论应用程序之间的功能依赖关系;也就是说,在运行报告之前,必须成功运行摄取。但成功是什么意思呢?数据流水线的状态定义确定数据流水线的执行是否成功,我将分析相反的问题:摄取过程中可能发生哪些失败?明确的失败会导致应用程序崩溃。如果您使用编排器,这种类型的故障很容易处理,因为它是一个标志,用于不触发下一个应用程序,例如我们的示例中的报告应用程序。另一方面,静默失败是指应用程序在没有错误代码或日志的情况下完成。因为它没有按预期运行,所以您必须考虑第二章介绍的期望的概念。摄取应用程序的观察者可能会遇到以下明确的失败:文件未找到错误:如果文件夹中的任何数据文件(例如Buzzfeed.csv)不可用,因为其名称已更改为小写,或者在运行摄取应用程序之前未创建文件,则会发生此类错误。类型错误(TypeError):如果某些值不能强制转换以匹配read_csv函数提供的类型,例如,当符号应该是类别时,出现此类错误。固定名称错误:当代码中明确用于访问值的任何字段(例如示例4-3中的列名Date和此案例中的Symbol)不存在或名称已更改时,会发生此类错误。文件系统错误:如果文件不可读取或文件夹对于运行应用程序的用户来说不可写,则会发生此类错误。内存错误:当文件增大到分配给应用程序的内存不再足够时,会发生此类错误。系统错误:如果磁盘没有足够的空间来写入汇总结果,则会触发此类错误。但从工程师观察的角度来看,以下示例表明了静默失败:日期列无法解析为日期,因为其格式错误、模式更改或时区不一致。在这种情况下,该列不再是日期时间,而是一个对象。日期列包含值,但不是当前月份的值。所有值都是过去或未来的日期。日期列包含未来的值,因为生成器可能稍后运行并生成与其正在处理的月份进行比较的未来信息。这种情况可能会导致后续产生重复项,或者相同日期的不一致值,并且可能会导致某些聚合失败。报告应用程序也可能将摄取视为失败,因为它在报告开始时未写入月度汇总文件,使得在按照给定间隔运行报告工具时不可用。用于在算术中筛选的任何字段不可用或其名称已更改。由于这些故障中的任何一个都可能发生,因此必须在它们发生时具备可见性,并且更好的做法是在摄取应用程序中及早预防它们的传播(请参阅“快速失败和安全失败”)。已经将明确的失败制作成了可见性,作为开发实践,以明确捕获这些错误(在Python中使用try...except)。但是,为了使观察者能够识别和发现静默失败,他们需要应用程序生成适当的观察结果。数据流水线的数据观察在本节中,我将概述数据流水线必须生成的数据观察结果。为此,让我们快速查看图4-2,该图显示了低级API如何实现第2章中提出的模型。有趣的是,它们具有相似的结构,甚至一些实体(标记为)也相同;在接下来的段落中,我将逐个详细介绍每个部分,以突出这些事实。在此图中,您会注意到用大写字母A、B、C和D标记的实体,它们位于圆圈内。 "A" 数据源突出显示了由摄取应用程序生成的观察结果,这些观察结果涉及其生成的数据,以及由报告应用程序读取数据时生成的观察结果,因此明确了隐式依赖关系。 实际上,这两个应用程序都生成了多个类似的观察结果,这些观察结果代表将它们联系在一起的所有依赖关系。在图4-2中,还突出显示了以下类似的观察结果:“B” 实体观察检索数据的服务器。“C” 实体观察执行命令的用户。“D” 实体观察由摄取生成的数据的模式,由报告应用程序读取。 让我们深入探讨需要添加到应用程序代码中以生成图4-2中显示的观察结果的内容。由于代码是用Python编写的,我们将使用logging模块来打印编码为JSON的观察结果。生成上下文数据观察在这一部分,我将介绍生成关于摄取应用程序执行上下文的观察所需的代码,如图4-3所示(请注意,报告可以重用相同的代码)。将示例4-4中的代码插入到文件开头,以生成摄取应用程序的观察。app_user = getpass.getuser() repo = git.Repo(os.getcwd(), search_parent_directories=True) code_repo = repo.remote().url commit = repo.head.commit code_version = commit.hexsha code_author = commit.author.name application_name = os.path.basename(os.path.realpath(__file__)) application_start_time = datetime.datetime.now().isoformat() 示例4-5中的额外指令创建了用于观察的变量,但目前还没有使用它们。如前所述,要记录信息,我们使用与示例4-5中编码的信息模型的JSON表示。application_observations = { "name": application_name, "code": { "repo": code_repo, "version": code_version, "author": code_author }, "execution": { "start": application_start_time, "user": app_user } }此代码创建了一个包含到目前为止创建的所有观察结果的JSON。但是,本节是关于使用数据可观察性的低级API。随着我们的继续,我们将遇到类似的模式,这为我们提供了创建函数的机会,以简化代码并在未来的数据收集和报告应用程序中共享它们。为了创建API,我们创建一个模型,模仿JSON中的观察核心模型,将每个实体转换为一个类,并将关系转换为引用(请参见示例4-6)。 class Application: name: str def __init__(self, name: str, repository: ApplicationRepository) -> None: self.name = name self.repository = repository def to_json(self): return {"name": self.name, "repository": self.repository.to_json()} class ApplicationRepository: location: str def __init__(self, location: str) -> None: self.location = location def to_json(self): return {"location": self.location} app_repo = ApplicationRepository(code_repo) app = Application(application_name, app_repo) 这意味着应用程序实体必须具有一个Application类,具有一个能够保存文件名的属性name,该属性可以将文件名存储为一个application_name变量,并引用一个ApplicationRepository实例。这个ApplicationRepository实体将被编码为一个ApplicationRepository类,具有属性location,设置为git远程位置。这个结构将有助于构建模型并生成更容易重用且能够导致标准化的JSON表示。 将概念编码成API类的一个附加好处是,它们有责任提供助手来提取相关的观察结果,就像示例4-7中所示。location: str # [...] @staticmethod def fetch_git_location(): import git code_repo = git.Repo(os.getcwd(), search_parent_directories=True).remote().url return code_repo class Application: name: str # [...] @staticmethod def fetch_file_name(): import os application_name = os.path.basename(os.path.realpath(__file__)) return application_name app_repo = ApplicationRepository(ApplicationRepository.fetch_git_location()) app = Application(Application.fetch_file_name(), app_repo) 这种策略可能是实现该模型的一种直接方式。然而,我们更喜欢另一种方法,它减弱了实体之间的联系。在示例4-8中,所有信息都将记录在一个JSON中,实体分散在信息树中,其中Application是根实体。这种编码方式迫使我们在记录根实体之前创建所有观察结果,而根实体就是Application实例。Application构造函数将变成类似示例4-8的内容。class Application: name: str def __init__(self, name: str, version: ApplicationVersion, repo: ApplicationRepository, execution: ApplicationExecution, server: Server, author: User) -> None: pass为了避免这种复杂性和约束,更好的方法是颠倒实体之间的依赖关系。不再让Application包含其ApplicationVersion或ApplicationRepository,而是创建单独的Application,然后在ApplicationVersion和ApplicationRepository内部添加对它的弱引用。示例4-9展示了这种模型的外观。class ApplicationRepository: location: str application: Application id: str def __init__(self, location: str, application: Application) -> None: self.location = location self.application = application id_content = ",".join([self.location, self.application.id]) self.id = md5(content.encode("utf-8")).hexdigest() def to_json(self): return { "id": self.id, "location": self.location, "application": self.application.id } @staticmethod def fetch_git_location(): import git code_repo = git.Repo(os.getcwd(), search_parent_directories=True).remote().url return code_repo 有了这个模型,我们可以单独记录每个观察结果——logging.info的两个调用——从而减少需要保存的信息量。因为我们需要重新构建实体之间的关系,所以引入了id变量,以减少需要记录的信息和需要链接的观察结果数量。使用这些日志,id可以通过它们的id来重建模型中的链接,例如ApplicationRepository和Application之间的依赖关系,因为它们已经被记录下来了。在这个示例中,应用程序在本地生成了id,导致了一个设计上的问题,使其在多次执行中不一致。为了解决这个问题,我们必须定义一个功能性的id,可以在多次执行、部署和应用程序之间标识实体。这个概念在建模中称为主键。您可以将主键用作散列算法的输入,例如使用hashlib以确定性方式生成id,在本例中使用md5。示例4-9说明了如何使用主键来一致生成id,例如通过使用md5。在本章的后续部分,我们将使用这种策略来生成实体。生成与数据相关的观察结果现在让我们讨论主要的数据可观测性组件的观察结果:数据源、模式和数据指标。图4-4显示了我们必须记录的观察结果。要生成的观察结果与所读取和写入的数据源相关。然后,处理转换(谱系)的问题。在摄入代码中,许多源使用pandas read_csv函数读取。数据源的观察主要是提供给read_csv函数作为参数的文件路径。另一个观察是数据源格式,由于read_csv没有使用任何特定的解析器属性,如sep,因此数据是“真正的”CSV。此外,read_csv没有提供关于标头的任何信息。因此,第一个非空行应该是列名,这将有助于创建关于摄入应用程序访问的数据的观察。列的类型是可以观察的另一项信息。 pandas推断出大多数数值类型。但是,字符串、类别和日期保留为对象。 read_csv函数提供了两个提示:Date是日期信息,Symbol是分类数据,或者在这种情况下是字符串。通过这些值,pandas创建了一个DataFrame,一种我们可以使用来捕获到目前为止列出的信息的表格。此外,DataFrame还允许我们计算关于值本身的附加内在信息,例如描述性统计信息。我们使用describe函数来计算这些基本统计信息,包括数字值和其他类型(如分类或日期值)的统计信息,使用参数:include='all'。最后,摄入应用程序使用to_csv函数将月度资产CSV文件写入到保存要写入的值的内存中的DataFrame中。此函数提供的信息几乎与read_csv函数相同。因此,我们可以得出文件路径、列和类型。让我们看看如何在可重用的API中建模这些观察结果。第一个类是DataSource,相对来说比较容易建模,如示例4-10所示。我们主要想知道格式以及应用程序正在读取或写入的数据源的位置。因此,它将模拟到文件的路径。class DataSource: location: str format: str id: str def __init__(self, location: str, format: str = None) -> None: self.location = location self.format = format id_content = ",".join([self.location, self.application.id]) self.id = md5(content.encode("utf-8")).hexdigest() def to_json(self): return {"id": self.id, "location": self.location, "format": self.format} 此外,id是基于提供的路径生成的,该路径是相对路径(以./开头),应扩展为使用绝对路径。此外,如果我们使用数据库表,例如除read_csv之外的其他方法,我们需要添加其他辅助方法来处理连接字符串。接下来,示例4-11可以对应用程序在数据源中操作的模式进行建模。class Schema: fields: list[tuple[str, str]] data_source: DataSource id: str def __init__(self, fields: list[tuple[str, str]], data_source: DataSource) -> None: self.fields = fields self.data_source = data_source linearized_fields = ",".join(list(map(lambda x: x[0] + "-" + x[1], sorted(self.fields)))) id_content = ",".join([linearized_fields, self.data_source.id]) self.id = hashlib.md5(id_content.encode("utf-8")).hexdigest() def to_json(self): from functools import reduce jfields = reduce(lambda x, y: dict(**x, **y), map(lambda f: {f[0]: f[1]}, self.fields)) return {"id": self.id, "fields": jfields, "data_source": self.data_source.id} @staticmethod def extract_fields_from_dataframe(df: pd.DataFrame): fs = list(zip(df.columns.values.tolist(), map(lambda x: str(x), df.dtypes.values.tolist()))) return fs Schema类具有fields属性,用于模拟CSV文件的列。在这里,我们可以从pandas DataFrame的元数据中提取它:columns和dtypes。我们选择将字段表示为一对(字段名称,字段类型)的列表。这部分的最后一个类是DataMetrics,它将模拟摄取应用程序读取和写入的文件的指标。但是,这是该类的不完整版本,因为它只编码了与Schema的关系。必须扩展它以确保它提供了特定用途的数据指标的可见性。这一目标需要一个后续的lineage(参见示例4-15)。当前的类看起来像示例4-12。class DataMetrics: schema: Schema metrics: list[tuple[str, float]] id: str def __init__(self, metrics: list[tuple[str, float]], schema: Schema) -> None: self.metrics = metrics self.schema = schema self.id = hashlib.md5(",".join([self.schema.id]).encode("utf-8")).hexdigest() def to_json(self): from functools import reduce jfields = reduce(lambda x, y: dict(**x, **y), map(lambda f: {f[0]: f[1]}, self.metrics)) return {"id": self.id, "metrics": jfields, "schema": self.schema.id} @staticmethod def extract_metrics_from_dataframe(df: pd.DataFrame): d = df.describe(include='all', datetime_is_numeric=True) import math import numbers metrics = {} filterf = lambda x: isinstance(x[1], numbers.Number) and not math.isnan(x[1]) mapperf = lambda x: (field + "." + x[0], x[1]) for field in d.columns[1:]: msd = dict(filter(filterf, map(mapperf, d[field].to_dict().items()))) metrics.update(msd) # metrics looks like: # {"Symbol.count": 20, "Symbol.unique": 1, "Symbol.freq": 20, # "Open.count": 20.0, "Open.mean": 3.315075, "Open.min": 1.68, # "Open.25%": 1.88425"Open.75%": 2.37, "Open.max": 14.725, # "Open.std": 3.7648500643766463, ...} return list(metrics.items()) 这里选择了这种简化形式的指标,仅表示数字描述统计信息,以一对(指标名称,数值)的列表形式。这些观察结果将直接从应用程序在包含数据值的DataFrame上调用describe函数的结果转换而来。现在我们已经将所有实体定义为类,我们可以更新我们的代码(见示例4-13)以确保它适当地生成这些观察结果。app = Application(Application.fetch_file_name()) app_repo = ApplicationRepository(ApplicationRepository.fetch_git_location(), app) AppTech = pd.read_csv( "data/AppTech.csv", parse_dates=["Date"], dtype={"Symbol": "category"}, ) AppTech_DS = DataSource("data/AppTech", "csv") AppTech_SC = Schema(Schema.extract_fields_from_dataframe(AppTech), AppTech_DS) AppTech_M = DataMetrics(DataMetrics.extract_metrics_from_dataframe(AppTech), AppTech_SC) Buzzfeed = pd.read_csv( "data/Buzzfeed.csv", parse_dates=["Date"], dtype={"Symbol": "category"}, ) Buzzfeed_DS = DataSource("data/Buzzfeed", "csv") Buzzfeed_SC = Schema(Schema.extract_fields_from_dataframe(Buzzfeed), Buzzfeed_DS) Buzzfeed_M = DataMetrics(DataMetrics.extract_metrics_from_dataframe(Buzzfeed), Buzzfeed_SC) monthly_assets = pd.concat([AppTech, Buzzfeed]) \ .astype({"Symbol": "category"}) monthly_assets.to_csv( "data/monthly_assets.csv", index=False ) monthly_assets_DS = DataSource("data/monthly_assets", "csv") monthly_assets_SC = Schema(Schema.extract_fields_from_dataframe(monthly_assets), monthly_assets_DS) monthly_assets_M = DataMetrics( DataMetrics.extract_metrics_from_dataframe(monthly_assets), monthly_assets_SC) 现在我们有了一个生成观察结果所需的代码,用于观察运行摄取应用程序时的数据行为。然而,大部分代码几乎是相同的。这个示例中的函数可以用来消除大部分的噪音。def observations_for_df(df_name: str, df_format: str, df: pd.DataFrame) -> None: ds = DataSource(df_name, df_format) sc = Schema(Schema.extract_fields_from_dataframe(df), ds) ms = DataMetrics(DataMetrics.extract_metrics_from_dataframe(df), sc)在这一部分,我们处理的是文件,但如果你需要处理表格,使用SQL来读取、转换和写入数据呢?如果直接使用SQL执行这些操作,它包含与Python代码相同的操作。也就是说,它读取表格,进行转换,最终写入它们;例如,插入子查询。然而,SQL并不提供在中间生成信息的多种能力,比如元数据提取或度量。SQL的另一个应用程序(应该由数据库服务器负责)运行SQL。请记住,SQL包含了大量信息,你的应用程序可以利用它。它包括表格、列、类型和执行的转换的名称;因此,解析查询会生成类似的观察结果,如示例4-14所示。实际上,我建议对SQL查询使用这种提取策略,因为SQL查询很可能是多次迭代和实验的结果,直到它完全按照预期运行(实际上,与任何代码一样)。因此,SQL查询实现了每次迭代中的所有假设,例如包括以下内容:IS NOT NULL 表示列可以为null,并且过滤掉它们是可以接受的(假设它们的数量不会影响最终逻辑)。cast("Amount" as "INT64") 表示金额始终可以转换为整数的假设。在添加了生成数据和应用程序观察结果的摄取功能后,我们可以着手处理另一个领域,这将提供更多的信息——即在部署应用程序时生成有关分析部分的观察结果。生成与数据血统相关的数据观察结果为了生成关于管道中应用程序之间在数据级别的相互作用以及列级别的观察结果,我将在本节中介绍如何生成血统实体,如图4-5所示。正如在第2章中讨论的那样,连接血统(数据流和转换,在本例中)的模型部分将数据可观察性领域的主要组件的观察结果与应用程序组件连接在一起。它位于所有三个领域的交汇处,即分析可观察性领域。为了编码这一部分,我们必须生成关于使用哪些数据源(输入)来生成其他数据源(输出)的观察结果。对于摄取来说,股票CSV文件有助于生成月度资产CSV文件。再深入一层,摄取将来自所有列的所有值连接起来,然后将结果写入文件。在字段级别的血统是直接的,这意味着输入和输出列名相同,并且输出仅从每个输入数据源的此列中获取其值。示例4-15展示了如何在报告应用程序中包含血统(OutputDataLineage)的生成,使用一个专门的类来定义输出与其输入的依赖关系。class OutputDataLineage: schema: Schema input_schemas: list[tuple[Schema, dict]] id: str def __init__(self, schema: Schema, input_schemas_mapping: list[tuple[Schema, dict]]) -> None: self.schema = schema self.input_schemas_mapping = input_schemas_mapping self.id = hashlib.md5(",".join([self.schema.id]).encode("utf-8") \ + self.linearize().encode("utf-8")).hexdigest() def to_json(self): return {"id": self.id, "schema": self.schema.id, "input_schemas_mapping": self.input_schemas_mapping} 示例4-16展示了OutputDataLineage的一部分静态函数,该函数将在字段级别将每个输入与输出进行映射,以以下方式连接它们:output_field -> 每个输入的输入字段列表。generate_direct_mapping助手使用了一个过于简单的启发式方法来绑定数据源,当字段名匹配时映射输出数据源。 这种策略在大多数实际用例中都会失败,特别是在需要更谨慎的跟踪以管理所有连接的聚合情况下。 通过使用下一章讨论的策略之一,您可以避免出现这种情况。@staticmethod def generate_direct_mapping(output_schema: Schema, input_schemas: list[Schema]): input_schemas_mapping = [] output_schema_field_names = [f[0] for f in output_schema.fields] for schema in input_schemas: mapping = {} for field in schema.fields: if field[0] in output_schema_field_names: mapping[field[0]] = [field[0]] if len(mapping): input_schemas_mapping.append((schema, mapping)) return input_schemas_mapping define linearize(self): [...] return linearized 然后,在示例4-17中,我定义了与上下文(与应用程序相关)观测相关联的谱系执行ApplicationExecution。class DataLineageExecution: lineage: OutputDataLineage application_execution: ApplicationExecution start_time: str id: str def __init__(self, lineage: OutputDataLineage, application_execution: ApplicationExecution) -> None: self.lineage = lineage self.application_execution = application_execution self.start_time = datetime.datetime.now().isoformat() self.id = hashlib.md5( ",".join([self.lineage.id, self.application_execution.id, self.start_time]).encode("utf-8")).hexdigest() def to_json(self): return {"id": self.id, "lineage": self.lineage.id, "application_execution": self.application_execution.id, "start_time": self.start_time}另外,第二章介绍的数据可观性核心模型将数据度量实体与谱系的执行相连接,以便在数据被使用或修改时提供可见性。因此,我们通过添加lineage_execution属性来调整DataMetrics模型,以表示这种关联,如示例4-18所示。class DataMetrics: schema: Schema lineage_execution: DataLineageExecution metrics: list[tuple[str, float]] id: str def __init__(self, metrics: list[tuple[str, float]], schema: Schema, lineage_execution: DataLineageExecution) -> None: self.metrics = metrics self.schema = schema self.lineage_execution = lineage_execution id_content = ",".join([self.schema.id, self.lineage_execution.id] self.id = hashlib.md5(id_content).encode("utf-8")).hexdigest() def to_json(self): from functools import reduce jfields = reduce(lambda x, y: dict(**x, **y), map(lambda f: {f[0]: f[1]}, self.metrics)) return {"id": self.id, "metrics": jfields, "schema": self.schema.id, "lineage_execution": self.lineage_execution.id} @staticmethod def extract_metrics_from_df(df: pd.DataFrame): d = df.describe(include='all', datetime_is_numeric=True) import math import numbers metrics = {} filterf = lambda x: isinstance(x[1], numbers.Number) and not math.isnan(x[1]) mapperf = lambda x: (field + "." + x[0], x[1]) for field in d.columns[1:]: msd = dict(filter(filterf, map(mapperf, d[field].to_dict().items()))) metrics.update(msd) return list(metrics.items()) 现在所有的组件都准备好生成图4-5中显示的观测数据了。最终的数据摄取脚本可以在GitHub仓库中查看。总结:可观察数据流水线在继续分析观察如何帮助解决本节开头介绍的明显和悄悄失败之前,我们将迄今为止所做的工作重新用于报告应用程序的数据可观察性。请参见示例 4-19。import ApplicationRepository.fetch_git_location import ApplicationVersion.fetch_git_version app = Application(Application.fetch_file_name()) app_repo = ApplicationRepository(fetch_git_location(), app) git_user = User(ApplicationVersion.fetch_git_author()) app_version = ApplicationVersion(fetch_git_version(), git_user, app_repo) current_user = User("Emanuele Lucchini") app_exe = ApplicationExecution(app_version, current_user) all_assets = pd.read_csv("data/monthly_assets.csv", parse_dates=['Date']) apptech = all_assets[all_assets['Symbol'] == 'APCX'] buzzfeed = all_assets[all_assets['Symbol'] == 'BZFD'] buzzfeed['Intraday_Delta'] = buzzfeed['Adj Close'] - buzzfeed['Open'] apptech['Intraday_Delta'] = apptech['Adj Close'] - apptech['Open'] kept_values = ['Open', 'Adj Close', 'Intraday_Delta'] buzzfeed[kept_values].to_csv("data/report_buzzfeed.csv", index=False) apptech[kept_values].to_csv("data/report_appTech.csv", index=False) all_assets_ds = DataSource("data/monthly_assets.csv", "csv") all_assets_sc = Schema(Schema.extract_fields_from_dataframe(all_assets), all_assets_ds) buzzfeed_ds = DataSource("data/report_buzzfeed.csv", "csv") buzzfeed_sc = Schema(Schema.extract_fields_from_dataframe(buzzfeed), buzzfeed_ds) apptech_ds = DataSource("data/report_appTech.csv", "csv") apptech_sc = Schema(Schema.extract_fields_from_dataframe(apptech), apptech_ds) # First lineage lineage_buzzfeed = OutputDataLineage(buzzfeed_sc, OutputDataLineage.generate_direct_mapping (buzzfeed_sc, [all_assets_sc])) lineage_buzzfeed_exe = DataLineageExecution(lineage_buzzfeed, app_exe) all_assets_ms_1 = DataMetrics(DataMetrics.extract_metrics_from_df(all_assets), all_assets_sc, lineage_buzzfeed_exe) buzzfeed_ms = DataMetrics(DataMetrics.extract_metrics_from_df(buzzfeed), buzzfeed_sc, lineage_buzzfeed_exe) # Second lineage lineage_apptech = OutputDataLineage(apptech_sc, OutputDataLineage.generate_direct_mapping (apptech_sc, [all_assets_sc])) lineage_apptech_exe = DataLineageExecution(lineage_apptech, app_exe) all_assets_ms_2 = DataMetrics(DataMetrics.extract_metrics_from_df(all_assets), all_assets_sc, lineage_apptech_exe) apptech_ms = DataMetrics(DataMetrics.extract_metrics_from_df(apptech), apptech_sc, lineage_apptech_exe) 通过以这种方式添加观察,我们使修改与我们在摄取应用程序中所做的类似。这种方法使我们能够建立习惯和抽象,比如一个框架,可以减少所需的更改数量 - 几乎是开发中的一种隐式规律。在示例 4-19 中,请注意为输入生成的观察已移至末尾。我们为示例的简单性而做出了这种实现选择。一个好处是附加计算在末尾完成,而不会影响业务流程。一个不足之处在于,如果中间发生了故障,将不会发送关于数据源及其架构的观察。当然,通过对代码进行一些调整,可以避免这种情况。另外,对于这个低级API的介绍,我们必须添加一些模板代码以生成正确的信息,这可能听起来像是噪音,与业务代码成比例。但是,请记住,该脚本最初没有日志。一般来说,日志是零散添加的,以生成有关脚本自身行为的一些信息,这就是我们所做的,但是应用于数据。还要记住以下几点: 我们按原样重用了 OutputDataLineage.generate_direct_mapping 助手来创建输出和输入之间的系谱映射。但是,它不起作用,因为我们从 monthly_assets.csv 文件中聚合了 Adj Close 和 Open 到新的 Intraday_Delta 列中。因为字段名称不相同,"直接" 启发法不会捕捉到这种依赖关系。 关于报告每次都再次报告 monthly_assets 的相同观察结果显示了警告消息。我们之所以这样做,是因为我们对每个输出编码了系谱。现在我们有两个系谱,每个都产生报告的 report_buzzfeed.csv 和 report_AppTech.csv 文件。因为输出重用与输入(经过筛选)相同的数据,所以我们必须报告每个输出的输入是什么样的,以避免它们看起来像是重复的。作为替代方案,我们可以重用观察结果或调整模型以解决此重复情况。您可以考虑以下替代选项:如果我们改变策略,每次访问数据时读取数据,而不是将其加载到内存中,那么如果在两次写操作之间更改了数据,则观察结果将不再相同。如果一个输出存在问题,我们更喜欢将输入的观察与此系谱同步。当考虑到每个写操作可能需要数小时而不是数秒时,出现这种情况的概率会增加。报告应用程序在每次运行时生成所有输出,但以后可以进行重构以更改此操作并进行参数化。例如,只生成一个输出,例如 BuzzFeed。因此,每个报告数据集都由独立运行生成。在这种情况下,观察结果已适当地表示这一点,因此我们无需调整逻辑。换句话说,将给定输入的观察结果发送与创建输出的次数一样经常,以表示现实,而不是尝试优化以减少可能看起来像是重复的数据。让我们解决第一个问题,确保系谱能够表示数据源之间的真实连接。为了以一种简化的方式做到这一点,我们将更新示例 4-19 中引入的助手函数,其中现在包括一个参数,用于提供每个输入的非直接映射。稍后的章节将介绍处理此常见用例的更简单、高效、易于维护和准确的策略,例如使用猴子补丁。@staticmethod def generate_direct_mapping(output_schema: Schema, input_schemas: list[tuple[Schema, dict]]): input_schemas_mapping = [] output_schema_field_names = [f[0] for f in output_schema.fields] for (schema, extra_mapping) in input_schemas: mapping = {} for field in schema.fields: if field[0] in output_schema_field_names: mapping[field[0]] = [field[0]] mapping.update(extra_mapping) if len(mapping): input_schemas_mapping.append((schema, mapping)) return input_schemas_mapping 示例 4-21 展示了报告应用程序的最终观察部分。# First lineage intraday_delta_mapping = {"Intraday_Delta": ['Adj Close', 'Open']} a = (all_assets_sc, intraday_delta_mapping) lineage_buzzfeed = OutputDataLineage(buzzfeed_sc, OutputDataLineage.generate_direct_mapping( buzzfeed_sc, [(all_assets_sc, intraday_delta_mapping)])) lineage_buzzfeed_exe = DataLineageExecution(lineage_buzzfeed, app_exe) all_assets_ms_1 = DataMetrics(DataMetrics.extract_metrics_from_df(all_assets), all_assets_sc, lineage_buzzfeed_exe) buzzfeed_ms = DataMetrics(DataMetrics.extract_metrics_from_df(buzzfeed), buzzfeed_sc, lineage_buzzfeed_exe) # Second lineage lineage_apptech = OutputDataLineage(apptech_sc, OutputDataLineage.generate_direct_mapping( apptech_sc, [(all_assets_sc, intraday_delta_mapping)])) lineage_apptech_exe = DataLineageExecution(lineage_apptech, app_exe) all_assets_ms_2 = DataMetrics(DataMetrics.extract_metrics_from_df(all_assets), all_assets_sc, lineage_apptech_exe) apptech_ms = DataMetrics(DataMetrics.extract_metrics_from_df(apptech), apptech_sc, lineage_apptech_exe) 利用数据观察来解决数据管道的故障问题现在,我们可以部署、运行和监视我们的管道,使用它们每次运行时生成的观察结果。低级别的API需要相当多的额外工作和坚持才能实现。然而,我们对结果感到满意。在遇到生产问题时,花在这些任务上的每一分钟都会使我们受益百倍,以避免收入损失—遵循总质量成本法则,1-10-100—当问题发生时。让我们从本节提到的可能出现的问题开始,首先是摄取应用程序的故障:找不到输入文件 DataSource 观察结果每次运行都会发送。因此,当文件缺失时,它们将不会被发送。即使对于没有关于应用程序逻辑的任何了解的人来说,目前使用的文件已经缺失是很明显的。读取时出现类型错误 Schema 观察结果被发送,并包含与其类型相关联的字段名称。因此,观察者可以清楚地知道所期望的类型,而无需查看应用程序或之前月份的文件。由于缺少字段而导致的错误 与“读取时出现类型错误”相同的观察结果有助于观察者迅速识别出当前运行中缺少的字段。文件系统错误 pandas库通常会抛出异常,异常通常提供导致错误的路径。对于观察者来说,用于标识问题的其余信息是使用的服务器。与DataSource相关的服务器观察结果可以立即提供关于与该路径相关的服务器的可见性。内存错误 这个问题通常发生在数据突然增加的情况下。虽然有许多情况可以考虑,但它们大多数是根据DataMetrics观察结果直观地由观察者处理的,其中包含了行数、字段数或DataSource数量的模式。但是,可能需要在脚本结束之前更早地发送观察结果,例如在以下两种情况下: 一个输入文件的大小大于以前。该文件很容易被检测到,因为它没有DataMetrics可用。 输出已经大大增加,因为所有文件都已经增加。可以通过缺少输出的DataMetrics来检测大小差异。此外,输入的DataMetrics显示了行数的增加。文件系统空间错误 这些错误最有可能发生在输出时进行写入,考虑到我们在此处处理数据可观测性情况。因此,与“内存错误”相同的信息会立即向观察者提供可见性,说明为什么可用空间不再足够以及哪些文件无法写入。日期无法解析为日期 在这种情况下,模式观察结果的日期字段类型将从日期更改为其他类型,例如字符串或对象。日期列不包含当前年/月的值 DataMetrics观察结果包括时间戳的最小值和最大值,从而立即提供了可用数据的执行时间与当前数据之间的差异。例如,假设时间戳的最大值是数据源读取的两天前,那么如果可接受的周期只有一天,那么数据可以被认为过时了。日期列包含未来的值 这是容易的,因为与先前因为当前月份的值缺失而失败的情况相同的观察结果将为您提供此可见性。符号类别更改 如果我们仅考虑数值型DataMetrics,我们可以通过增加输出文件中的类别数来快速识别此情况。其中一个或一些文件将不再一致,因为它们将引用不同的类别。接下来,我们必须考虑报告应用程序可能会将摄取应用程序视为失败的情况,以及在适用的情况下,观察者可以使用的报告应用程序。这些情况包括:月度汇总文件不可用 摄取应用程序没有发送DataSource观察结果或DataMetrics观察结果。聚合使用了缺失的字段,如 Close 摄取应用程序发送的月度数据的模式缺少这些字段。读写访问、大小和空间方面的错误 对于报告观察者,与摄取观察者相同的解决方案适用。没有跨团队或团队成员之间的信息偏见。APCX 和 ENFA 符号 摄取观察者报告的类别数已经发生了变化,为某些情况提供了线索。然而,我们可以扩展DataMetrics以报告非数值观察结果,并报告类别。Adj Close 或 Open 缺少值导致异常值 DataMetrics "null数"可以覆盖这种情况,因为当null数大于零时,Intraday_Delta的计算将返回NAs。月度资产中的日期错误 与日期故障相关的摄取观察者使用的解决方案也适用于此处。例如,应用程序可以使用与当前报告月份的最小值和最大值相比来检查日期列的值。现在,我们已经准备好处理各种数据是问题来源的情况。没有这些知识,这些情况将需要长时间的高压会议来理解它们,以及消耗几个小时甚至几天的调试工作,可能会变成几个月,因为我们无法访问生产中的数据。到目前为止讨论的问题是我们知道可能会发生的问题。然而,许多其他问题可能会在整个管道中传播,我们对此知之甚少—未知的未知。例如,由于在CSV导出过程中引入了数据错误,某只股票的一个月前的值是不正确的。这个问题至少会发生在你的一个应用程序和其他类似的应用程序中。由于这些未知的未知因素,数据观察不能仅限于仅覆盖预定义的情况,而是必须尽可能地报告尽可能多的观察结果—可能在计算资源和时间上有一些约束—以生成关于预期或未满足的情况的可见性。在这个示例中,月度股票价值的分布将有助于以后与其他月份进行比较,并且它们可能提供关于这些值是否相等或相似的提示。使用低级别的日志记录具有完全灵活性,可以生成任何您可以生成为可见的内容。例如,自定义度量标准和关键绩效指标(KPI)。所有作业并不相同;每家公司、项目和应用程序都有其自己的具体细节。您可能会控制特定的度量标准,无论它们与消耗的数据还是生成的数据有关。例如,对于一个表,这样的度量标准可以是项目数乘以每单位成本减去从Web服务获得的金额,count(items) * cost_per_unit。结果必须始终大于零。这可以很容易地添加到源代码中,但必须由工程师添加,因为这构成了与业务逻辑(和列的语义)相关的特定度量标准。自定义观察的另一个原因是关键绩效指标(KPI)—这些是利益相关者请求的对底层业务重要的数字。KPI通常定期报告或根据需要计算,并在随机或固定的时间间隔内使用。然而,它们的含义非常强烈,利益相关者对其寄予了很高的期望,几乎没有时间等待修正。因此,您还必须创建有关KPI随时间如何发展的可见性,因为如果利益相关者对其产生疑虑,他们提问其正确性的时刻一开始,时间就开始流逝。为了增加响应性,您通常必须了解KPI如何发展,识别它们在发生之前如何变化,以及根据其历史值和血统了解它们变化的原因。正如您在报告应用程序更新过程中所预见的,定义API—模型、编码和函数—不是每个应用程序的任务,而是必须在应用程序之间进行标准化和重用。标准化减少了每个应用程序的工作量。更重要的是,它使观察结果成为均匀的,独立于应用程序,以简化观察者在匹配参与其分析的其他应用程序的行为时的工作。标准化还有助于跨应用程序重用实体,例如资产月度DataSource,这是摄取应用程序的输出和报告应用程序的输入。通过观察结果的均匀表示,可以通过跨应用程序重用实体来巩固整个管道的状态。支持数据可观测性的架构的一部分必须包括创建一个外部系统,以系统地聚合观察结果,以构建全局视图。通过拥有依赖于这个聚合视图并对其采取行动的观察者,系统可以发展到执行一些当前由观察者执行的操作,这就是机器学习发挥作用的地方。总结本章对数据可观察性在数据源方面的全面探讨及其在提高数据质量和运营卓越方面的重要性。我们深入探讨了在数据应用程序代码中生成数据观察结果的概念,强调了在应用程序的各个组件中合并观察结果生成代码的重要性。这些组件包括应用程序本身、正在使用的数据、它们之间的关系以及它们包含的内容。此外,我们讨论了在低级别创建数据可观察性Python API的概念,该API为开发人员提供了将数据观察功能无缝集成到其应用程序中的强大工具集。通过这个API,从业者可以生成数据观察结果,跟踪数据流动,并确保其数据的可靠性和准确性。为了加强这些概念,我们提供了一个完全可工作的示例,展示了将一个非数据可观察性的Python数据管道转化为稳健的、以数据可观察性为驱动的管道。通过利用专用的数据可观察性Python API,我们演示了如何生成、捕获和利用数据观察结果,以增强可见性、识别问题并推动持续改进。随着我们继续前进,本章探讨的原则和策略成为将数据可观察性纳入数据应用程序的基础。通过采用这些实践,组织可以确保其数据管道稳健、可靠,并能够以高度信任的方式提供有价值的见解。尽管低级别日志记录具有超高度可自定义性和灵活性,但其采用可能会受到所需的初始工作的阻碍。这个论点也适用于测试的采用。因此,在这个层面上简化使用复杂性至关重要。此外,我们需要探索在促进团队和个人之间广泛采用数据可观察性的同时,补充低级别日志记录的替代方法。接下来的章节将深入探讨这个主题,从探讨基于事件的系统开始。
0
0
0
浏览量647
攻城狮无远

《Delta Lake Up & Running》第八章:流数据的操作

Spark Structured Streaming首次在Apache Spark 2.0中引入。Structured Streaming的主要目标是在Spark上构建准实时流应用程序。Structured Streaming替代了一个名为DStreams(离散流)的较老、较低级别的API,该API基于旧的Spark RDD模型。自那时以来,Structured Streaming已经添加了许多优化和连接器,包括与Delta Lake的集成。Delta Lake通过其两个主要运算符readStream和writeStream与Spark Structured Streaming集成。Delta表可用作流式数据的源和接收端。Delta Lake克服了通常与流系统相关的许多限制,包括:合并由低延迟摄取产生的小文件在多个流(或并发批处理作业)上保持“仅一次”处理利用Delta事务日志,在使用文件作为源流时高效地发现哪些文件是新文件我们将从快速回顾Spark Structured Streaming开始本章,然后初步概述Delta Lake流处理及其独特的功能。接下来,我们将步入一个小的“Hello Streaming World!”Delta Lake流处理示例。虽然范围有限,但这个示例将提供一个在非常简单的环境中了解Delta Lake流处理编程模型细节的机会。数据的增量处理已经成为一种流行的ETL模型。AvailableNow流触发模式使开发人员能够构建增量管道,而无需维护自己的状态变量,从而实现了更简单、更健壮的管道。您可以在Delta表上启用Change Data Feed(CDF)。客户端可以使用SQL查询消耗这个CDF feed,或者他们可以将这些变更流式传输到他们的应用程序中,从而实现创建审计试验、流式分析、合规性分析等用例。流处理概览尽管本章专注于Delta Lake流处理模型,但在深入研究Delta Lake Structured Streaming的独特功能之前,让我们简要回顾一下Spark Structured Streaming的基础知识。Spark结构化流处理Spark结构化流处理是建立在Apache Spark之上的近实时流处理引擎。它实现了可伸缩、容错和低延迟处理连续数据流的功能。Spark Structured Streaming提供了一个高级API,使您能够构建端到端的流应用程序,可以从各种来源读取和写入数据,例如Kafka、Azure Event Hubs、Amazon S3、Google Cloud Platform的Pub/Sub、Hadoop分布式文件系统等等。Structured Streaming的核心思想是,它允许您将数据流视为一个无边界的类似表的结构,可以使用类似SQL的操作进行查询和操作,从而轻松分析和操作数据。Spark结构化流处理的众多优点之一是其易用性和简单性。API建立在熟悉的Spark SQL语法之上,因此您可以利用对SQL和DataFrame操作的现有知识,构建流应用程序,而无需学习一套新的复杂API。此外,结构化流处理通过利用Spark的处理引擎提供了容错性和可靠性,该引擎可以从故障中恢复,并确保每个数据点仅处理一次。这种容错性使其非常适合构建需要低延迟和高吞吐量数据处理的关键任务应用程序。Delta Lake与结构化流处理当您在结构化流处理中使用Delta Lake时,您既获得了Delta Lake的事务保证,又拥有了Apache Spark结构化流处理的强大编程模型。通过Delta Lake,您现在可以将Delta表用作流式数据的源和接收端,实现了一种连续处理模型,通过流式方式处理您的数据,涵盖了数据湖中的原始数据、青铜层、银层和金属层,消除了批处理作业的需要,从而简化了解决方案架构。在本章的后面部分,我们将介绍这种连续处理架构的一个示例。在第7章中,我们讨论了模式强制执行和模式演变。流入Delta Lake的流数据提供了模式强制执行,确保传入的数据流符合预定义的模式,防止数据异常进入数据湖。然而,当业务需求变化引入对捕获附加信息的需要时,您可以利用Delta Lake的模式演变能力,允许模式随时间变化。流处理示例我们将通过回顾一个非常简单的“Hello Streaming World!”示例来开始本节,以说明从Delta表进行流处理的基础知识。Hello Streaming World在本节中,我们将创建一个简单的Delta表流处理场景,并设置一个流查询,该查询:从源Delta表中读取所有更改到一个流DataFrame。在Delta Lake表的情况下,“读取更改”等同于“读取事务日志条目”,因为它们包含表的所有更改的详细信息。对流DataFrame执行一些简单的处理。将流DataFrame写入目标Delta表。从源读取流并将流写入目标的组合通常被称为流查询,如图8-1所示。一旦我们启动了流查询,我们将对源表执行一些小的批量更新,允许数据通过流查询流向目标。在执行查询期间,我们将查询查询过程日志,并研究检查点文件的内容,这些文件维护了我们流查询的状态。这个简单的示例将使您在转向更复杂的示例之前充分了解Delta Lake流处理模型的基础知识。首先,执行第81章的“章节初始化”笔记本以创建所需的Delta表。然后,打开“01 - 简单流处理”笔记本。在这里,我们有一个包含10条黄色出租车数据记录的Delta表,所有数据都包含在一个单独的Parquet文件中:%sh ls -al /dbfs/mnt/datalake/book/chapter08/LimitedRecords.delta drwxrwxrwx 2 root root 4096 Apr 11 19:40 _delta_log -rwxrwxrwx 1 root root 6198 Apr 12 00:04 part-00000-....snappy.parquet %sql SELECT * from delta.`/mnt/datalake/book/chapter08/LimitedRecords.delta` 输出(仅显示相关部分):+------+--------+----------------------------+----------------------------+ |RideId|VendorId| PickupTime | DropTime | +------+--------+----------------------------+----------------------------+ |1 | 1 |2022-03-01T00:00:00.000+0000|2022-03-01T00:15:34.000+0000| |2 | 1 |2022-03-01T00:00:00.000+0000|2022-03-01T00:10:56.000+0000| |3 | 1 |2022-03-01T00:00:00.000+0000|2022-03-01T00:11:20.000+0000| |4 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:20:01.000+0000| |5 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:00:00.000+0000| |6 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:00:00.000+0000| |7 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:00:00.000+0000| |8 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:00:00.000+0000| |9 | 2 |2022-03-01T00:00:00.000+0000|2022-03-01T00:00:00.000+0000| |10 | 2 |2022-03-01T00:00:01.000+0000|2022-03-01T00:11:15.000+0000| +------+--------+----------------------------+----------------------------+ 创建流查询首先,我们将创建我们的第一个简单流查询。我们从源表中读取一个流,如下所示:# Start streaming from our source "LimitedRecords" table # Notice that instead of a "read", we now use a "readStream", # for the rest our statement is just like any other spark Delta read stream_df = \ spark \ .readStream \ .format("delta") \ .load("/mnt/datalake/book/chapter08/LimitedRecords.delta") readStream 与任何其他标准的 Delta 表读取操作一样,只是多了一个 Stream 后缀。我们得到了一个流 DataFrame,命名为 stream_df。接下来,我们对DataFrame执行一些操作。我们添加一个时间戳,以便知道我们何时从源表中读取每条记录。我们也不需要源 DataFrame 中的所有列,因此我们选择需要的列:# Add a "RecordStreamTime" column with the timestamp at which we read the # record from stream stream_df = stream_df.withColumn("RecordStreamTime", current_timestamp()) # This is the list of columns that we want from our streaming # DataFrame select_columns = [ 'RideId', 'VendorId', 'PickupTime', 'DropTime', 'PickupLocationId', 'DropLocationId', 'PassengerCount', 'TripDistance', 'TotalAmount', 'RecordStreamTime' ] # Select the columns we need. Note that we can manipulate our stream # just like any other DataStream, although some operations like # count() are NOT supported, since this is an unbounded DataFrame stream_df = stream_df.select(select_columns) 最后,我们将 DataFrame 写入一个输出表:# Define the output location and the checkpoint location target_location = "/mnt/datalake/book/chapter08/StreamingTarget" target_checkpoint_location = f"{target_location}/_checkpoint" # Write the stream to the output location, maintain # state in the checkpoint location streamQuery = \ stream_df \ .writeStream \ .format("delta") \ .option("checkpointLocation", target_checkpoint_location) \ .start(target_location) 首先,我们定义一个目标或输出位置,我们希望将流写入该位置。在选项中,我们定义了一个检查点文件的位置。这个检查点文件将维护流查询的元数据和状态。检查点文件是为了确保容错性并在发生故障时启用查询的恢复。它将维护许多其他信息,包括已处理的流源的事务日志条目,以便识别尚未处理的新条目。最后,我们使用目标位置调用 start 方法。请注意,我们对输出和检查点文件使用相同的基目录。我们只是在检查点子目录后附加了下划线(_checkpoint)。由于我们没有指定触发器,流查询将继续运行,因此它将执行、检查新记录、处理它们,然后立即检查下一组记录。在接下来的章节中,您将看到可以使用触发器更改此行为。查询过程日志当我们启动流查询时,我们会看到流正在初始化,并显示查询进度日志(QPL)。 QPL是由每个微批次生成的JSON日志,提供了有关微批次执行的详细信息。它用于在笔记本单元格中显示一个小型流仪表板。该仪表板提供有关流应用程序性能、吞吐量和延迟的各种指标、统计数据和见解。当您展开流显示时,会看到一个带有两个选项卡的仪表板(图8-2)。第一个选项卡包含仪表板,其中以图形方式显示了QPL的一些关键指标。原始指标显示在原始数据选项卡中。 这里显示了查询过程日志的一部分原始数据:{ "id" : "c5eaca75-cf4d-410f-b34c-9a2128ee1944", "runId" : "508283a5-9758-4cf9-8ab5-3ee71a465b67", "name" : null, "timestamp" : "2023-05-30T16:31:48.500Z", "batchId" : 1, "numInputRows" : 0, "inputRowsPerSecond" : 0.0, "processedRowsPerSecond" : 0.0, "durationMs" : { "latestOffset" : 14, "triggerExecution" : 15 }, QPL中的一个关键指标是流唯一标识,即日志中的第一个条目。这个ID唯一标识了流,并映射回检查点目录,稍后将看到。流唯一标识也显示在流仪表板标题上方。 查询日志还包含 batchId,这是微批次的ID。对于每个流,此ID将从零开始,并为每个处理的微批次递增一次。numInputRows 字段表示当前微批次中摄取的行数。 QPL中下一组重要的指标是 Delta 源和接收器指标: sources 中的 startOffset 和 endOffset 指示每个批次的起始和结束位置。这些包括以下子字段: reservoirVersion 是当前微批次操作的 Delta 表的版本。 index 用于跟踪从哪个部分文件开始处理。 如果 reservoirVersion 设置为当前流开始时 Delta 表的版本,则 isStartingVersion 布尔字段设置为 true。 sink 字段包含流接收器的位置。 当我们查看第1个微批次的源和接收器指标时,我们看到以下内容:"sources" : [ { "description" : "DeltaSource[dbfs:/mnt/.../LimitedRecords.delta]", "startOffset" : { "sourceVersion" : 1, "reservoirId" : "6c25c8cd-88c1-4b74-9c96-a61c1727c3a2", "reservoirVersion" : 0, "index" : 0, "isStartingVersion" : true }, "endOffset" : { "sourceVersion" : 1, "reservoirId" : "6c25c8cd-88c1-4b74-9c96-a61c1727c3a2", "reservoirVersion" : 0, "index" : 0, "isStartingVersion" : true }, "latestOffset" : null, "numInputRows" : 0, "inputRowsPerSecond" : 0.0, "processedRowsPerSecond" : 0.0, "metrics" : { "numBytesOutstanding" : "0", "numFilesOutstanding" : "0" } } ], "sink" : { "description" : "DeltaSink[/mnt/datalake/book/chapter08/StreamingTarget]", "numOutputRows" : -1 } 注意 numInputRows 为 0。这可能看起来有点令人惊讶,因为我们知道我们的源表中有10行。然而,当我们启动 .writeStream 时,流查询开始运行,并立即作为批次0的一部分处理了前10行。我们还可以看到我们的 batchId 目前为1,因为 batchId 从0开始,第一个批次已经被处理。我们还可以看到 reservoirVersion 仍然是0,因为此批次尚未运行,没有处理新记录。所以,我们仍然处于源表的版本0。我们还看到 index 是0,这意味着我们正在处理第一个数据文件,并且确实在起始版本。我们可以通过显示源表的版本来验证这一点:%sql DESCRIBE HISTORY delta.`/mnt/datalake/book/chapter08/LimitedRecords.delta` 输出(仅显示相关部分):+-------+----------------------------+ |version| timestamp| +-------+----------------------------+ |0 |2023-05-30T16:25:23.000+0000| +-------+----------------------------+ 在这里,您可以看到我们当前确实是在版本0。我们还可以通过查询我们的输出流表来验证这一点:%sql SELECT count(*) FROM delta.`/mnt/datalake/book/chapter08/StreamingTarget` 我们可以看到我们确实有10行:+--------+ |count(1)| +--------+ | 10 | +--------+ 因为我们用 .start 启动了 writeStream,并且没有指示查询应该运行多久,它一直在运行。当 writeStream 完成时,它执行下一个 readStream,依此类推。然而,由于源表中没有生成新行,实际上什么也没发生,我们的输出记录计数仍然为10。batchId 不会改变,直到它从流中提取行,因此保持为1。 接下来,我们执行以下SQL语句,在源表中插入10条新记录:%sql -- Use this query to insert 10 random records from the -- allYellowTaxis table into the limitedYellowTaxis table INSERT INTO taxidb.limitedYellowTaxis SELECT * FROM taxidb.allYellowTaxis ORDER BY rand() LIMIT 10 如果我们取消注释并运行此查询,batchId 现在设置为1,并且我们看到了10行新数据:{ "id" : "c5eaca75-cf4d-410f-b34c-9a2128ee1944", "runId" : "508283a5-9758-4cf9-8ab5-3ee71a465b67", "name" : null, "timestamp" : "2023-05-30T16:46:08.500Z", "batchId" : 1, "numInputRows" : 10, "inputRowsPerSecond" : 20.04008016032064, 一旦处理了这个 batchId,batchId 2 的记录计数将恢复为0,因为没有来自流的新行。请记住,流查询将一直运行下去,寻找源表中的新事务条目,并将相应的行写入流目标。通常,Spark结构化流处理作为基于微批次的流服务在运行。它将从源中读取一批记录,处理这些记录并将它们写入目标,然后立即开始下一批,寻找新的记录(或在Delta Lake的情况下,寻找新的事务条目)。在一个实际应用中,这种对源表进行批量更新的模型并不经济。源表只会定期更新,但由于我们的流查询一直在运行,我们必须保持其集群一直运行,这会增加成本。在本章后面,我们将修改流查询以更好地适应我们的用例,但首先,让我们简要了解一下检查点文件。检查点文件前面,我们看到检查点文件将维护我们流查询的元数据和状态。检查点文件在 _checkpoint 子目录中:%sh ls -al /dbfs/mnt/datalake/book/chapter08/StreamingTarget/_checkpoint/ drwxrwxrwx 2 root root 4096 May 1 23:37 __tmp_path_dir drwxrwxrwx 2 root root 4096 May 1 23:37 commits -rwxrwxrwx 1 root root 45 May 2 15:48 metadata drwxrwxrwx 2 root root 4096 May 1 23:37 offsets 我们有一个文件(metadata)和两个目录(offsets和commits)。让我们看看每一个。metadata文件只是以JSON格式包含流标识符:%sh head /dbfs/mnt/datalake/book/chapter08/StreamingTarget/_checkpoint/metadata {"id":"c5eaca75-cf4d-410f-b34c-9a2128ee1944"} 当我们查看offsets目录时,您会看到两个文件,每个文件对应一个batchId:%sh ls -al /dbfs/mnt/datalake/book/chapter08/StreamingTarget/_checkpoint/offsets -rwxrwxrwx 1 root root 769 May 30 16:28 0 -rwxrwxrwx 1 root root 771 May 30 16:46 1 当我们查看文件0的内容时,我们看到以下内容:v1 { "batchWatermarkMs": 0, "batchTimestampMs": 1685464087937, "conf": { "spark.sql.streaming.stateStore.providerClass": "org.apache.spark.sql.execution.streaming .state.HDFSBackedStateStoreProvider", ….. } } { "sourceVersion": 1, "reservoirId": "6c25c8cd-88c1-4b74-9c96-a61c1727c3a2", "reservoirVersion": 0, "index": 0, "isStartingVersion": true } 第一部分包含Spark流处理的配置变量。第二部分包含我们之前在QPL中看到的相同的reservoirVersion、index和isStartingVersion。在这里记录的是批次执行前的状态,所以我们的版本是零,文件索引是零,isStartingVersion变量指示我们处于起始版本。当我们查看文件1时,我们看到以下内容:v1 { "batchWatermarkMs": 0, "batchTimestampMs": 1685465168696, "conf": { "spark.sql.streaming.stateStore.providerClass": "org.apache.spark.sql.execution.streaming.state. HDFSBackedStateStoreProvider", … } } { "sourceVersion": 1, "reservoirId": "6c25c8cd-88c1-4b74-9c96-a61c1727c3a2", "reservoirVersion": 2, "index": -1, "isStartingVersion": false } 在这个批次中,额外的10条记录被处理,将要被处理的下一个可能的版本是2,这在reservoirVersion中反映出来。另外,请注意,索引设置为-1,这表示对于当前版本没有其他文件要处理。commits文件夹包含每个微批次一个文件。在我们的情况下,我们将有两个commits,每个批次一个:drwxrwxrw -rwxrwxrwx 1 root root 29 Jun 9 15:55 0 -rwxrwxrwx 1 root root 29 Jun 9 16:15 1 每个文件代表了微批次的成功完成。它只包含一个水印:%sh head /dbfs/mnt/datalake/book/chapter08/StreamingTarget/_checkpoint/commits/0 这产生了:v1 {"nextBatchWatermarkMs":0} 在这一部分,我们初次了解了Delta流处理。我们看了一个简单的例子,其中Delta表既是流查询的源表,也是流查询的目标表。在接下来的章节中,我们将看看如何在增量处理模型中利用Delta流处理。AvailableNow 流处理Spark Structured Streaming 提供了多种触发器模式。AvailableNow 触发器选项以增量批次的形式消耗所有可用的记录,可以使用选项(例如 maxBytesPerTrigger)配置批次大小。 首先,我们需要在“02 - 简单流处理”笔记本中取消当前正在运行的流查询,方法是导航到流仪表板并单击取消链接。然后,我们可以确认取消并停止流查询。 由于源表只会定期更新,我们不希望流查询持续运行。相反,我们希望启动查询,提取新的事务条目,处理相应的记录,写入接收器,然后停止。以下触发器将允许我们执行此操作。如果我们在流查询中添加代码 .trigger(availableNow=True),则查询将运行一次,然后停止,如笔记本“02 - AvailableNow Streaming”中所示:# Write the stream to the output location, maintain # state in the checkpoint location streamQuery = \ stream_df \ .writeStream \ .format("delta") \ .option("checkpointLocation", target_checkpoint_location) \ .trigger(availableNow=True) \ .start(target_location) 当我们运行此笔记本时,流查询将运行直到找不到新记录,但由于源表中没有添加新记录,因此找不到记录,也不会将记录写入目标表。我们可以通过查看 writeStream 的原始数据来验证这一点:{ "id" : "c5eaca75-cf4d-410f-b34c-9a2128ee1944", … "numInputRows" : 0, 如果现在在笔记本中在 writeStream 下方运行下面的SQL查询,我们将向源表添加10条记录。然后,如果我们重新运行流查询,我们将再次看到这10行新数据:{ "id" : "c5eaca75-cf4d-410f-b34c-9a2128ee1944", "runId" : "36a31550-c2c1-48b0-9a6f-ce112572f59d", "name" : null, "timestamp" : "2023-05-30T17:48:12.079Z", "batchId" : 2, "numInputRows" : 10, "sources" : [ { "description" : "DeltaSource[dbfs:/mnt/.../LimitedRecords.delta]", "startOffset" : { "sourceVersion" : 1, "reservoirId" : "6c25c8cd-88c1-4b74-9c96-a61c1727c3a2", "reservoirVersion" : 3, "index" : -1, "isStartingVersion" : false 在输出中,我们还看到了 sources 部分,其中包含 reservoirVersion 变量,目前设置为 3。请记住,reservoirVersion 表示在这种情况下的下一个可能的版本 ID。如果我们对表进行 DESCRIBE HISTORY,我们可以看到我们的版本是 2,所以下一个版本将是 3:%sql describe history delta.`/mnt/datalake/book/chapter08/LimitedRecords.delta` 输出(仅显示 version 列):+-------+ |version| +-------+ | 2 | | 1 | | 0 | +-------+ 在下一个查询中,我们向源表中添加了20条记录。然后,如果我们重新运行我们的流查询并查看原始数据,我们会看到这20条新记录,同时也会看到 startOffset 的 reservoirVersion 现在设置为 3:{ "id" : "d89a5c02-052b-436c-a372-2445fb8d88d6", .. "numInputRows" : 20, ... "sources" : [ { "description" : "DeltaSource[dbfs:/mnt/.../LimitedRecords.delta]", "startOffset" : { "sourceVersion" : 1, "reservoirId" : "31611029-07d1-4bcc-8ee3-cad0d4fa8bc4", "reservoirVersion" : 3, ... }, 这种 AvailableNow 模型意味着我们现在可以按照 “02 - AvailableNow Streaming” 笔记本中所示的方式运行流查询,只需一天一次,或每小时一次,或者任何用例需要的时间间隔。由于检查点文件中保存的状态,Delta Lake 将始终捕获自上次运行以来发生在源表中的所有更改。除了 AvailableNow 触发器之外,还有一个 RunOnce 触发器,其行为非常相似。两者都将处理所有可用的数据。但是,RunOnce 触发器将在单个批次中处理所有记录,而 AvailableNow 触发器将在适当时处理数据的多个批次,通常可以更好地实现可伸缩性。更新源记录接下来,让我们看看当我们运行如下更新语句时会发生什么:%sql -- Update query to demonstrate streaming update -- behavior UPDATE taxidb.limitedyellowtaxis SET PickupLocationId = 100 WHERE VendorId = 2 当我们查看相应事务日志条目的 commitInfo 操作时,我们看到以下内容:"commitInfo": { … "operation": "UPDATE", .. }, "notebook": { "notebookId": "3478336043398159" }, … "operationMetrics": { … "numCopiedRows": "23", … "numUpdatedRows": "27", … }, … } 我们可以看到有23行被复制到新的数据文件,而27行被更新,总共是50行。 因此,如果我们再次运行我们的查询,我们应该在我们的批次中看到确切的50行。当我们再次运行“02 - AvailableNow Streaming”笔记本时,我们会看到50行:{ ... "batchId" : 4, "numInputRows" : 50, 如果我们返回并再次运行流查询,我们会注意到以下错误:Stream stopped... com.databricks.sql.transaction.tahoe.DeltaUnsupportedOperationException: Detected a data update (for example part-00000-....snappy.parquet) in the source table at version 3. This is currently not supported. If you'd like to ignore updates, set the option 'ignoreChanges' to 'true'. If you would like the data update to be reflected, please restart this query with a fresh checkpoint directory. The source table can be found at path dbfs:/mnt/.../LimitedRecords.delta. 在这里,Delta Lake 告诉我们流中的数据更新当前不受支持。如果我们知道我们只想要新的记录,而不是更改,我们可以在 readStream 中添加 .option("ignoreChanges", "True") 选项:# Start streaming from our source "LimitedRecords" table # Notice that instead of a "read", we now use a "readStream", # for the rest our statement is just like any other spark Delta read # Uncomment the ignoreChanges option when you want to receive only # new records, and no updated records stream_df = \ spark \ .readStream \ .option("ignoreChanges", True) \ .format("delta") \ .load("/mnt/datalake/book/chapter08/LimitedRecords.delta") 如果我们现在重新运行流查询,它将成功。但是,当我们查看原始数据时,仍然会看到所有50个输入行,这看起来是错误的:{ "id" : "d89a5c02-052b-436c-a372-2445fb8d88d6", "runId" : "b1304246-4083-4275-8637-1f99768b8e03", "name" : null, "timestamp" : "2023-04-13T17:28:31.380Z", "batchId" : 3, "numInputRows" : 50, "inputRowsPerSecond" : 0.0 这种行为是正常的。ignoreChanges 选项仍然会将 Delta 表中的所有重写文件发送到流中。这通常是所有更改记录的超集。然而,实际上只有插入的记录会被处理。StreamingQuery 类让我们看一下 streamQuery 变量的类型:# Let's take a look at the type # of the streamQuery variable print(type(streamQuery)) 输出:<class 'pyspark.sql.streaming.query.StreamingQuery'> 我们可以看到类型是 StreamingQuery。如果我们调用 streamQuery 的 status 属性,我们会得到以下结果:# Print out the status of the last StreamingQuery print(streamQuery.status) 输出:{'message': 'Stopped', 'isDataAvailable': False, 'isTriggerActive': False} 查询当前已停止,没有可用的数据。没有触发器处于活动状态。另一个有趣的属性是 recentProgress,它将打印出笔记本中流输出的原始数据部分相同的输出。例如,如果我们想要查看输入行数,我们可以打印以下内容:print(streamQuery.recentProgress[0]["numInputRows"]) 输出:50这个对象还有一些有趣的方法。例如,如果我们想要等待流终止,我们可以使用 awaitTermination() 方法。重新处理源记录的全部或部分由于我们一直在处理源表的许多批次,检查点文件系统地积累了所有这些更改。如果我们删除检查点文件并重新运行流查询,它将从源表的开头开始,并引入所有记录:%sh # Uncomment this line if you want to reset the checkpoint rm -r /dbfs/mnt/datalake/book/chapter08/StreamingTarget/_checkpoint 流查询的输出:{ ... "numInputRows" : 50, ,.. "stateOperators" : [ ], "sources" : [ { "description" : "DeltaSource[dbfs:/mnt/.../LimitedRecords.delta]", "startOffset" : null, "endOffset" : { ... "reservoirVersion" : 5, ... }, "latestOffset" : null, "numInputRows" : 50, 我们读取了源表中的所有行。我们从偏移量 null 开始,结束于 reservoirVersion 5。 我们还可以只流式传输部分更改。为此,我们可以在再次清除检查点后在 readStream 中指定 startingVersion:stream_df = \ spark \ .readStream \ .option("ignoreChanges", True) \ .option("startingVersion", 3) \ .format("delta") \ .load("/mnt/datalake/book/chapter08/LimitedRecords.delta") 当我们查看原始数据时,我们得到以下结果:{ ... "batchId" : 0, "numInputRows" : 70, "inputRowAnd" : 0.0, ... "stateOperators" : [ ], "sources" : [ { ... "startOffset" : null, "endOffset" : { "sourceVersion" : 1, "reservoirId" : "32c71d93-ca81-4d6e-9928-c1a095183016", "reservoirVersion" : 6, "index" : -1, "isStartingVersion" : false }, 我们得到了70行。这是不正确的,因为我们从版本3开始。让我们看一下表8-1,总结了我们迄今为止使用过的版本和操作。这验证了流查询的输入行总数。通过将其与 DESCRIBE HISTORY 命令结合使用,设置 startingVersion 会为我们提供许多选项。我们可以查看历史记录,然后决定从哪个时间点加载数据。从变更数据源读取流在第6章,您了解到Delta Lake如何记录通过CDF写入表中的所有数据的“更改事件”。这些更改可以传递给下游消费者。这些下游消费者可以使用具有 .readStream() 的流查询读取在CDF中捕获和传输的更改事件。要在启用了CDF的表中读取CDF的更改,请将 readChangeFeed 选项设置为 true。将 readChangeFeed 设置为 true 与 .readStream() 结合使用将允许我们有效地从源表中流式传输更改到下游目标表。我们还可以使用 startingVersion 或 startingTimestamp 来指定Delta表流源的起始点,而无需处理整个表:# Read CDF stream with readChangeFeed since version 5 spark.readStream \ .format("delta") \ .option("readChangeFeed", "true") \ .option("startingVersion", 5) \ .table(" <delta_table_name> ") # Read CDF stream since starting timestamp 2023-01-01 00:00:00 spark.readStream \ .format("delta") \ .option("readChangeFeed", "true") \ .option("startingTimestamp", "2023-01-01 00:00:00") \ .table(" <delta_table_name> ") 使用 .option("readChangeFeed", "true") 将返回带有CDF架构的表更改,该架构提供了 _change_type 、 _commit_timestamp 和 _commit_version ,这是 readStream 将要使用的。以下是CDF数据的示例(这是来自第6章的内容):+----------+----------------+------------+------------------+-----------------+ | VendorId | PassengerCount | FareAmount | _change_type | _commit_version | +----------+----------------+------------+------------------+-----------------+ | 1 | 1000 | 2000 | update_preimage | 2 | +----------+----------------+------------+------------------+-----------------+ | 1 | 1000 | 2500 | update_postimage | 2 | +----------+----------------+------------+------------------+-----------------+ | 3 | 7000 | 10000 | delete | 3 | +----------+----------------+------------+------------------+-----------------+ | 4 | 500 | 1000 | insert | 4 | +----------+----------------+------------+------------------+-----------------+ 先前读取变更 feed 的代码片段指定了 startingVersion 或 startingTimestamp。重要的是要注意这些方法是可选的,如果没有提供,流将在流式处理时获取表的最新快照,将其作为插入,未来的更改作为变更数据。读取变更数据时,我们可以指定其他选项,特别是围绕数据更改和速率限制的选项(每个微批次处理多少数据)。表 8-2 突出显示了在使用 Delta 表作为流源时在流查询中使用的其他重要选项。速率限制选项对于更好地控制整体资源管理和利用率非常有用。例如,当有大量新的数据文件或大量要处理的数据时,我们可能希望避免潜在的过载处理资源(例如,我们的集群)。控制速率限制可以通过控制微批次大小来实现更平衡的处理体验。如果我们想要有效地控制速率限制,同时又要忽略删除以避免干扰现有的流查询,我们可以在流查询中指定这些选项:# Read CDF stream with readChangeFeed and don’t specify the # starting timestamp or version. Specify rate limits and ignore deletes. spark.readStream \ .format("delta") \ .option("maxFilesPerTrigger", 50) \ .option("maxBytesPerTrigger", "10MB") \ .option("ignoreDeletes", "true") \ .option("readChangeFeed", "true") \ .table("delta_table_name") 在此示例中,我们设置了速率限制选项,忽略了删除,并省略了起始时间戳和版本选项。这将读取表的最新版本(因为未指定版本或时间戳),并更好地控制微批次的大小和处理资源,以减少对流查询的潜在中断。总结Delta Lake的一个关键特性是将批处理和流处理数据统一到单个表中。本章深入探讨了Delta Lake如何与Structured Streaming完全集成,以及Delta表如何支持连续数据流的可伸缩、容错和低延迟处理。通过readStream和writeStream与Structured Streaming集成,Delta表可以用作流处理的源和目标,并利用流处理的DataFrame。本章的示例演示了如何将更改读取到这些流处理的DataFrame中,以及如何执行简单的处理将流写入目标。然后,我们探讨了检查点文件和元数据、查询过程日志以及流处理类,以更好地了解流处理是如何在幕后跟踪信息的。最后,您学习了如何使用readStream利用CDF传输和读取流查询中的逐行更改。在将批处理和流处理数据统一到单个Delta表后,第9章将深入探讨如何安全地与其他组织共享这些数据。
0
0
0
浏览量868
攻城狮无远

第六章:使用企业数据仓库进行创新

第3章中,您了解到在云数据平台的核心组件中选择数据湖还是数据仓库取决于您的组织是以工程/科学为先(选择数据湖)还是以分析为先(选择数据仓库)。在第5章中,我们专注于数据湖作为数据平台设计的中心元素的概念。在本章中,您将学习如何使用现代数据仓库作为中心元素来解决成本、民主化、灵活性和治理等相同的问题。我们将首先快速回顾构建数据平台时要解决的问题,并讨论使云数据仓库成为一个吸引人的解决方案的技术趋势。然后,我们将深入探讨现代数据仓库架构的特点,以及如何有效地为数据分析师和数据科学家提供支持。现代数据平台每当您开始进行大型技术项目时,首先应该问自己您想要实现什么业务目标,您当前的技术挑战是什么,以及您想要利用哪些技术趋势。在本节中,我们将重点帮助您了解在构建现代数据平台时如何回答这些问题,以及企业数据仓库方法如何引导您的数据平台设计焦点。许多这些概念在先前的章节中已经涉及到,但将它们在这里重新构架将有助于您将现代数据仓库的设计与架构解决的问题联系起来。组织目标在我们的客户访谈中,CTOs 反复提到以下组织目标的重要性:无隔阂 数据必须在整个企业范围内得以激活,因为业务的一个部分需要访问其他部门创建的数据。例如,确定明年产品设计的产品经理可能需要访问由零售运营团队创建和管理的交易数据。民主化 数据平台必须支持领域专家和其他非技术用户,他们可以在不经过技术中介的情况下访问数据,但应能够依赖数据的质量和一致性。可发现性 数据平台必须支持数据工程师和其他技术用户,他们需要在不同处理阶段访问数据。例如,如果我们有一个数据集,其中已经调整了原始的传入交易数据,数据科学家需要能够获取已调整的数据集。如果他们无法发现它,他们将重新构建一个调整例程。因此,应该能够发现所有这些“中间”数据集,以便在整个组织中不重复处理步骤。管理 数据集应该由了解它们的团队控制。例如,财务数据应由财务部门控制,而不是由 IT 部门控制。单一来源 数据应该在原地读取。最小化数据的复制和提取。安全性和合规性 IT 应该作为数据的经纪人,确保只有具有适当权限的人可以访问它。必须实施法规要求的所有合规性检查(例如 GDPR、CCPA、格拉姆-利奇-布莱利法案)。确保实施将数据分类为敏感/受限数据与开放或行业特定数据的解决方案。易用性 使报告更容易,因为有数百名分析师正在创建支持各种功能的报告。数据科学 使数据科学团队更具生产力,因为这些角色往往既昂贵又难以招聘。灵活性 使决策者更快地获得见解。虽然这些目标在组织之间的相对顺序有所不同,但所有这些目标在我们接触的每个组织中以某种方式出现。因此,现代数据平台应使CTOs能够实现这些目标。技术挑战在实现这些目标时,CTO们使用组织内已部署的技术时面临哪些障碍呢?CTO们通常提到以下技术挑战:规模和规模他们的组织收集的数据量随着时间的推移急剧增加,并预计将继续增加。他们目前的系统无法扩展,并无法保持在业务的速度和成本限制内,从而导致妥协,例如对传入数据进行抽样或严重优先考虑新数据项目。复杂的数据和用例越来越多的收集到的数据是非结构化数据,如图像、视频和自然语言文本。他们目前用于管理结构化和非结构化数据的系统没有交集。然而,需要在推荐等用例中将结构化数据(例如产品目录详情)和非结构化数据(例如目录图像、用户评论)一起使用。集成随着时间的推移,我们看到技术领域涌现了许多新的数据源和数据接收端,组织希望并应该利用这些资源。例如,他们希望在Salesforce中管理销售信息,在Adobe中管理广告活动,在Google Analytics中管理网页流量。有必要同时分析和决策所有这些数据。实时性许多新数据是实时收集的,能够在数据到达时处理并做出决策具有竞争优势。然而,组织没有一个无缝支持流式处理的数据平台。这当然只是传统大数据挑战的一个更具细化的版本:数据的规模、多样性(数据和系统)、速度。技术趋势和工具为实现组织的业务和技术目标,云架构师可以利用前几章描述的趋势和工具。为了方便起见,我们在这里对它们进行了总结:计算和存储的分离 公共云提供商允许您将数据存储在Blob存储中,并从临时计算资源中访问它。这些计算资源包括Google BigQuery、AWS Redshift、Snowflake、Amazon EMR、Google Dataproc、Cloud Dataflow或Databricks等自定义构建或调整的软件,它们利用了计算与数据处理的分离,并在多个工作节点上分布数据处理。它们涵盖结构化和非结构化数据。多租户 云计算资源被设计为允许多个租户。因此,不需要为组织中的每个部门创建独立的集群或存储阵列。因此,两个不同的部门可以将其数据存储在单个DWH中,并从它们各自支付的计算资源中访问彼此的数据,每个团队都可以在共享的通用数据集上启动自己的分析。同样,组织可以使用其计算资源访问来自多个组织的数据并对连接的数据集进行分析。与传统的Hadoop集群不同,不必在与数据共同运行计算工作负载。身份验证和授权的分离 云IAM解决方案可以确保中央IT团队在数据所有者控制访问的同时确保身份的安全性。事实上,通过提供对组的访问,可以允许访问组织来管理成员资格,而数据所有者仅管理提供何种访问的业务逻辑。分析中心 无服务器DWH(以及正如我们在上一章中看到的数据湖)使架构师能够打破组织边界之外的数据孤立。虽然数据所有者支付存储费用,但数据使用者支付查询费用。此外,虽然数据所有者决定了哪些组具有访问权限,但组的成员资格可以由数据使用者管理。因此,合作伙伴和供应商可以共享数据,而不必担心查询成本或组成员资格。跨云语义层 诸如Informatica或Looker之类的工具可以创建一个跨超大规模云(例如AWS、GCP或Azure)、多云数据平台(例如Snowflake或Databricks)和本地环境的语义层。语义层可以实时重写查询,以提供一致和连贯的数据字典。(请注意,语义层将在本章后面更详细地讨论。)一致的管理界面 数据fabric解决方案在公共云上提供一致的管理体验,无论数据存储在DWH中还是以Parquet文件的数据湖格式存储在Blob存储中。跨云控制面板 诸如BigQuery Omni之类的工具提供了一个一致的控制面板和查询层,无论您的组织使用哪个超大规模云(AWS、GCP或Azure)来存储数据。如果关心确保可以使用相同的工具,而不管特定数据集存储在哪个超大规模云中,这些工具是有用的。这是与GCP控制面板的增加依赖性的权衡。多云平台 Snowflake、Confluent和Databricks提供了在任何超大规模云上运行相同软件的能力。然而,与前一个项目不同,不同云上的运行时仍然是不同的。如果关心确保可以从一个超大规模云转移到另一个超大规模云,这些工具是有用的。这是对单一来源软件供应商增加依赖性的权衡。数据湖和DWH的融合 联邦和外部查询使得可以在DWH上运行Spark作业以及在数据湖上运行SQL查询。我们将在下一章对这个主题进行扩展。内建ML 像AWS Redshift和Google BigQuery这样的企业DWH提供了在不移动数据出DWH的情况下进行ML训练和运行ML的能力。Spark具有ML库(Spark MLlib),并且ML框架(如TensorFlow)在Hadoop系统中得到支持。因此,ML可以在数据平台上进行,而无需提取数据到单独的ML平台。流式摄取 诸如Kafka、AWS Kinesis、Azure Event Hub和Google Cloud Pub/Sub之类的工具支持实时将数据着陆到超大规模云的数据平台。诸如AWS Lambda、Azure Functions、Google Cloud Run和Google Cloud Dataflow之类的工具还支持在数据到达时对数据进行转换,以进行质量控制、聚合或语义上的纠正,然后将数据写出。流式分析 DWH支持流式SQL,因此只要数据实时着陆在DWH中,查询就会反映最新的数据。变更数据捕获 诸如Qlik、AWS Database Migration Service和Google Datastream之类的工具提供了捕获操作关系数据库(例如在AWS关系数据库服务[RDS]上运行的Postgres或在Google Cloud SQL上运行的MySQL)更改并实时流式传输到DWH的能力。嵌入式分析 可以使用Power BI等现代可视化工具将分析嵌入到最终用户使用的工具(手机或网站)中,而无需让最终用户操作仪表板。枢纽和辐条体系结构提供了实现CTO期望目标并利用上述技术能力的经过验证的方法。枢纽和辐条架构在设计围绕数据仓库的现代云数据平台时,枢纽和辐条是理想的架构。在这种架构中,数据仓库充当一个集线器,收集所有用于业务分析的数据。辐条是自定义的应用程序、仪表板、ML 模型、推荐系统等,通过标准接口(即 API)与数据仓库进行交互。工具如 Sigma Computing、SourceTable 和 Connected Sheets 甚至提供了一种电子表格界面,模拟了在数据仓库顶部运行的 Excel。所有这些辐条都可以直接从数据仓库访问数据,而无需复制。我们建议这种方法适用于没有遗留技术需要适应的初创公司,希望进行全面改建以实现全面转型的组织,甚至大型企业,因为它具有可伸缩性、灵活性和弹性。它是可伸缩的,因为新的(现代的)数据仓库可以轻松集成新的数据源和用例到现有的基础设施,而无需重新配置整个系统。它是灵活的,因为您可以定制整体架构以满足企业的特定需求(例如,启用流处理)。而且它是弹性的,因为它可以承受比其他架构更多的故障。 现代的云原生企业数据仓库形成了枢纽和辐条架构的中枢,而辐条是数据提供者和数据消费者,如图 6-1 所示。请在阅读以下段落时参考图中的组件。面向分析的数据和 ML 能力涉及将原始数据加载到数据仓库(企业数据仓库)中,然后根据需要进行转换以支持各种需求。由于计算和存储的分离,数据仅有一个唯一的副本。不同的计算工作负载,如查询(查询 API)、报告/交互式仪表板(商业和智能报告工具)和 ML(数据科学和 ML 工具),都可以在数据仓库中的这些数据上运行。由于可以利用 SQL 进行所有转换,因此可以使用视图和物化视图进行详细说明,从而无需进行 ETL 流水线。这些视图可以调用外部函数,从而允许使用 ML API 和模型对数据进行增强。在某些情况下,甚至可以仅使用 SQL 语法训练 ML 模型,并且可以通过简单的 SQL 命令调度复杂的批处理流水线。现代数据仓库甚至支持直接摄取(加载 API)流式数据(流式 API),因此只有在需要对数据执行低延迟、窗口聚合分析时,才必须利用流水线。始终记住评估解决方案的最终成本并将其与您将获得的收益进行比较。有时一种策略(批处理)可能比另一种策略(流处理)更好,反之亦然。枢纽和辐条架构的关键思想是尽可能高效地将所有数据引入企业数据仓库。当数据来自 SaaS 软件(如 Salesforce)时,可以通过计划的自动导出机制加载数据。而当数据来自操作性数据库(如 Postgres)时,可以通过 CDC 工具实时接收数据。同时,实时数据源预计在发生新事件时将新数据发布到数据仓库。某些数据源是联合的(联合数据集),这意味着您将动态查询并将其视为数据仓库的一部分。现在,所有企业数据都在逻辑上成为数据仓库的一部分,数据科学和报告工具可以对数据进行操作(存储 API/查询 API)。如果您没有需要迁移的现有 ETL 流水线或需要支持的现有终端用户工具选择,那么枢纽和辐条是简单、强大、快速和经济有效的。Google Cloud 上这种架构的一个示例呈现在图 6-2 中。其他超大规模云提供了更多选项(例如 AWS 上的 Athena、Redshift、Snowflake);我们将在第 7 章中介绍这些变体。在其完全自动化的形式中,枢纽和辐条对于没有强大工程技能的企业以及进行影子 IT 的小部门来说是一个很好的选择,因为在数据摄取方面几乎没有代码需要维护。此外,您只需了解 SQL 就可以完成数据科学和报告活动。值得注意的是,支持面向分析的数据仓库的这些功能都是最近的发展。计算和存储的分离随着公共云的出现而产生:此前,大数据范式由 MapReduce 主导,该范式需要将数据分片到本地存储。分析工作负载需要构建业务特定的数据集市。由于性能限制,需要在将数据加载到这些数据集市之前进行转换(即 ETL)。存储过程是数据仓库,而不是外部函数,它们本身依赖于无状态微服务的自动缩放的发展。ML 部署需要捆绑和分发库,而不是无状态的 ML API。ML 工作流基于独立的训练代码,而不是由容器化组件组成的 ML 流水线。流处理涉及不同的代码库,而不是统一的批处理和流处理。现在您已经了解了枢纽和辐条架构的基本概念,让我们深入探讨其主要组成部分:摄取、商业智能、转换和 ML。数据摄取枢纽与辐条架构中的一个辐条(在图 6-1 中围绕企业数据仓库的方框)对应于将数据(或进行摄取)传送到数据仓库的各种方式。有三种摄取机制:预建连接器、实时数据捕获和联合查询。我们将在本节分别讨论每种机制。预建连接器将数据导入数据仓库可以变得非常简单,尤其是在利用流行的SaaS平台时,因为它们提供了自动导入数据的连接器,只需点击几下即可。每个云原生数据仓库(如Google BigQuery、AWS Redshift、Snowflake、Azure Synapse等)通常都支持SaaS软件,例如Salesforce、Google Marketing Platform和Marketo。如果您使用的软件不受您选择的数据仓库支持,可以查看软件供应商是否提供到您选择的数据仓库的连接器,例如Firebase(一个移动应用平台)可以直接将移动应用的崩溃报告导出到BigQuery进行分析(“crashlytics”)。您可以设置这些SaaS服务将数据推送到常见的数据仓库(例如Salesforce将自动将数据推送到Snowflake),或者使用BigQuery数据传输服务等服务将这些数据集导入数据仓库。这通常被称为Zero ETL——正如无服务器并不意味着没有服务器,只是服务器由其他人管理一样,Zero ETL意味着ETL过程由您的SaaS供应商或您的数据仓库供应商管理。 第三个选择是使用提供连接器的第三方供应商,例如Fivetran。他们的预建连接器提供了一种轻松集成来自营销、产品、销售、财务等应用程序的能力(见图6-3)。通过云提供商的传输服务、支持云连接器的软件供应商以及第三方连接器供应商,您可以购买(而不是构建)定期从SaaS系统导出数据并将其加载到企业数据仓库的能力。实时数据如果您希望您的数据仓库能够实时反映变化,您需要利用一组较小的工具,称为CDC工具。操作性数据库(Oracle、MySQL、Postgres)通常是其中的一部分,企业资源规划(ERP)工具如SAP也是支持的。确保这些工具使用DWH的Streaming API以近实时的方式加载数据。在Google Cloud上,Datastream是推荐的CDC工具,在AWS上则是Database Migration Service(DMS)。如果您有实时数据源,例如点击流数据或来自物联网设备的数据,请寻找一种能够使用DWH的Streaming API将事件实时发布的能力。由于Streaming API可以通过HTTPS访问,所以您只需要一种在事件发生时调用HTTPS服务的方式。如果您的物联网设备提供商不支持推送通知,那么请寻找一种将事件发布到消息队列(例如使用消息队列遥测传输或MQTT),并使用流处理器(在GCP上使用Dataflow,在AWS上使用Kinesis)将这些事件写入DWH的方法(参见图6-4)。联邦数据你甚至可能无需将数据加载到数据仓库中即可使用它。现代云数据仓库能够在标准格式(如Avro、CSV、Parquet和JSONL(逐行JavaScript对象表示法))的数据集上运行查询,而无需将数据移到数据仓库中。这被称为联邦查询,通常要求数据格式是自描述的,或者模式被预先指定。例如,让Google BigQuery对Avro文件执行联邦查询包括以下三个步骤:使用bq mkdef --source_format=AVRO gs://filename创建一个表定义文件,并在必要时编辑默认值。例如,您可能会更改Avro中的整数字段,使其被视为实数。使用生成的表定义文件使用bq mk --external_table_definition mydataset.mytablename创建一个BigQuery数据集。像正常的SQL一样查询数据集。请注意,数据仍然以Avro格式保存在Cloud Storage中。这就是这被称为联邦查询的原因。如果数据格式不是自描述的,则mkdef命令允许您指定模式。 甚至可以将这些步骤结合起来,采用按需读取的模式,以便模式定义仅在查询的持续时间内有效。例如,要使Azure Synapse查询Azure数据湖中的Parquet文件,您可以执行以下查询:SELECT ... FROM OPENROWSET( BULK 'https://....dfs.core.windows.net/mydata/*.parquet', FORMAT='PARQUET' ) AS Tbl在联邦查询的情况下,查询引擎是数据仓库。还可以使用外部查询引擎(如PostgreSQL关系数据库)执行查询。此类查询称为外部查询(见图6-5)。例如,要让Amazon Redshift查询Postgres数据库,请按照以下三个步骤操作:确保您的RDS PostgreSQL实例可以从Amazon Redshift集群接受连接。为此,您需要设置物理网络,并确保Redshift集群被分配了在PostgreSQL实例中授权的IAM角色。在Redshift中使用CREATE EXTERNAL SCHEMA FROM POSTGRES创建一个外部模式,传入数据库、模式、主机、IAM和密码。像正常一样查询模式。在所有这些情况中,关键要注意的是数据仍然保持在原地,并且从那里进行查询,而不是加载到数据仓库中。由于当数据保持原地时(无法进行分区、聚类等),进行联邦查询和外部查询的优化机会更有限,因此它们往往比本地查询慢一些。 鉴于联邦查询和外部查询较慢,为什么要使用它们?为什么不简单地将数据加载到数据仓库中并将数据仓库视为真实数据的来源?有一些情况下,避免移动数据可能是有利的:在某些情况下,数据仓库内的存储费用可能高于其外部存储。对于很少查询的数据,将数据保留在联邦数据源中可能更具成本效益。当需要最佳性能时,请使用数据仓库解决方案提供的本机存储系统。如果相反,灵活性更为重要,则尝试利用联邦数据源。如果关系数据库中的数据经常更新,将关系数据库视为黄金来源可能是有利的。从操作性数据库到数据仓库进行CDC可能会引入不可接受的延迟。数据可能是由工作负载(如Spark)创建或需要的。因此,可能需要维护Parquet文件。使用联邦/外部查询限制了数据的移动。当您已经拥有数据湖时,这是最常见的用例。数据可能属于与查询它的组织不同的组织。联邦查询很好地解决了这个问题。但是,假设您正在使用完全无服务器的数据仓库,比如Google BigQuery,它不是基于集群的。在这种情况下,即使是对本机存储,也可以直接提供对合作伙伴和供应商的访问。最后一种情况是我们建议使用完全无服务器的数据仓库的原因之一,它不要求您将数据移动到集群、创建数据提取或提供特定应用程序(而不是特定用户)访问数据。 现在您对数据摄入的可用选项有了更好的了解,让我们深入了解如何通过查看我们可以在BI方面开发的各种功能来使数据“说话”。商业智能数据分析师需要能够迅速从数据中获取洞见。他们用于此目的的工具需要是自助的,支持即席分析,并对使用的数据提供一定程度的信任(在中心枢轴结构中为业务和智能报告工具)。它需要提供几种功能:SQL分析、数据可视化、嵌入式分析和语义层,我们将在本节中详细介绍这些功能。SQL分析正如前面的部分所强调的,SQL是查询DWH时的主要语言。在组织内,SQL是数据分析师和业务分析师的通用语言。这些分析师通常会在DWH上执行即席查询,以帮助回答出现的问题(例如,“在罗马尼亚的最后一次热浪中卖出了多少升冰淇淋?”)。但在许多情况下,这些问题变得常规化,用户会利用诸如Power BI或Looker之类的即席工具将其操作化为报告的形式。这些报告工具,通常称为BI工具,旨在通过连接到DWH(图6-1中的中心枢轴结构中的业务和智能报告工具)提供对组织整个数据资产的视图,以便分析师能够做出基于数据的决策。考虑到不太实际指望分析师在需要数据的时候收集、清理和加载某个数据片段,数据需要事先存在。这就是为什么带有中央企业DWH的中心枢轴模型如此受欢迎的原因。然而,您应确保BI工具已经适应云环境,并能够处理大量快速到达的数据。早期的BI工具要求将数据提取到在线分析处理(OLAP)立方体中以提高性能(见图6-6)。这根本行不通——它会导致过时的、导出的数据大量增加,或者需要支持每种可能用例的单独OLAP立方体的巨大维护负担。您希望BI工具能够透明地将查询委托到DWH并检索结果。这是充分利用DWH规模和其流式接口的及时性的最佳方法。企业DWH中的SQL引擎已升级到一定水平,能够处理多个并行查询或在内存中维护大量数据,这使得组织能够摆脱OLAP方法。可视化您的SQL查询将生成表格。然而,仅仅从原始表格中获得理解和见解可能是困难的。相反,您通常会将结果绘制成图形、图表或地图。可视化SQL查询结果通常是引发洞察力的原因。在探索性数据分析中,可视化是临时的。然而,可视化必须帮助使用常见的图表元素回答常见问题。这是Tableau、Looker、Power BI和Looker Studio等仪表板工具的职责。优秀的仪表板考虑受众,并讲述故事。它们既可以作为当前状态的高层概述,也可以作为进一步交互式分析的起点。它们使用适当形式的图表显示上下文相关的重要指标。嵌入式分析通过传统的仪表板工具进行可视化足以与少数内部人员共享分析结果。这些用户会欣赏仪表板提供的丰富控件和互动性。但如果您是手工艺市场或电信运营商,并且希望每位艺术家或每个摊位都能够查看其自己商店性能的近实时图表呢?当您为数千用户提供定制报告时,您不希望向最终用户提供一个功能丰富的仪表板界面,因为这可能难以支持和维护。相反,您需要一个轻量级的图形可视层,可以嵌入到用户已经在使用的工具中。通常将分析嵌入到艺术家用于上架商品或摊位运营用于订购新商品的网站或移动应用程序中。为商店性能提供一致的实时指标可以显著提高艺术家和运营商在您的市场上赚钱的能力。还可以更好地连接工作流程,例如,使用分析,卖家可能能够轻松调整经常缺货商品的价格。提供诸如预测产品需求之类的机器学习功能还可能为市场带来新的收入来源。语义层自助分析和一致性之间存在紧张关系。赋予公司内每位业务分析师快速创建仪表板的能力是重要的,而不必等待中央IT团队。同时,保持仪表板在计算方面的一致性至关重要(例如,运输成本在各个仪表板中必须以相同的方式计算)。尽管业务分析师能够自己构建复杂的仪表板很重要,但他们尽可能地重复使用现有的可视化也同样重要。提供一致性和重复使用的传统方法是在IT内部集中计算和基础能力。然而,在数据驱动的环境中,这种集中式的以IT为中心的解决方案通常过于脆弱且速度太慢,无法满足业务用户的需求。现代BI工具如Looker或MicroStrategy提供了一个语义层(参见第2章)来解决这种紧张关系。语义层是一种额外的层,允许您通过常用的业务术语自主访问数据;它通过表创建者采用的术语与业务用户采用的名称之间的解耦来工作。LookML是Looker称之为语义模型层的一种基于SQL的数据语言(见示例6-1)。它为数据管理者提供了创建可重复使用的维度或度量,业务分析师可以重复使用和扩展的能力。这些定义在数据字典中提供,方便进行发现和管理。dimension: overall_health_score { view_label: "Daily Account Health Scores" description: "Weighted average of Usage, User, Support and CSAT Scores" Type: number Sql: (${usage_score}*2+${user_score}*2+${support_score}+${csat_score})/6 ;; Value_format_name: decimal_1 }这些语义层本身就充当了BI数据源。例如,Tableau可以连接到Looker的语义层,而不是直接连接到DWH。尽管业务用户通常不会直接看到或与类似LookML的工具进行交互,但他们可以构建使用定义的维度和度量的仪表板。这有助于促进重复使用,使每个分析师都无需为他们使用的每个维度或度量从基本表列中定义。集中定义度量还可以减少人为错误的可能性,并提供可以更新定义的单一点。在文本文件中存在这样一个集中的定义允许轻松进行版本控制。您已经了解了如何通过BI工具的帮助深入挖掘数据,并通过语义层帮助业务用户自主管理数据。有时,这种方法还不足以满足需求,您需要在开始将数据摄取到DWH之前对数据进行准备。在接下来的部分,我们将重点关注这个主题。转换假设您将来自ERP系统的原始数据摄取到DWH。如果ERP是SAP,列名很可能是德语的,并反映了应用程序状态,而不是我们认为有用于持久化的数据。我们不希望强迫所有用户每次需要数据时都必须将数据转换为可用形式。那么转换应该在哪里进行呢?一种选择是在语义层中定义列的转换方式,该语义层是您BI工具的一部分,正如前一节所讨论的那样。但是,这将定义限制为维度和度量,而从非BI工具访问这些定义将会很困难。更好的方法是在SQL中定义转换并创建视图、表或材料化视图。在本节中,我们将查看在DWH内处理转换的常见选项。这是“中心辐射”架构的另一个优势:当在DWH中实施数据转换时,结果立即对所有需要它们的用例可用。第三种方法是利用超大规模云提供商上可用的应用程序开发平台。使用事件机制(Eventarc、EventBridge、Event Grid)在创建新表分区时启动一个无服务器函数(Lambda、Cloud Functions)。然后,该函数可以通过调用其API将转换后的数据“推送”到后端系统,从而启动业务操作(例如发货通知)。这被称为反向ETL,因为数据流的方向是远离DWH。使用视图的ELT与ETL不同,可以将数据按原样加载到DWH中,并在通过视图读取时即时对其进行转换(见示例6-2)。由于视图在将数据加载到DWH后即时进行转换(在加载之前不进行转换),因此这通常被称为提取-加载-转换(ELT),与典型的ETL工作流形成对比。CREATE VIEW view1 AS SELECT fis.CustomerKey, fis.ProductKey, fis.OrderDateKey, fis.SalesTerritoryKey, dst.SalesTerritoryRegion FROM FactInternetSales AS fis LEFT OUTER JOIN DimSalesTerritory AS dst ON (fis.SalesTerritoryKey=dst.SalesTerritoryKey); 与查询原始表不同,用户查询视图。创建视图的SQL可以选择特定列,对列应用掩码等操作,并联接多个表。因此,ELT为业务用户提供了对原始数据的一致、受控的视图。因为最终查询在进一步聚合或选择之前运行视图查询,所以所有查询都反映了基于DWH中存在的任何数据的最新信息。然而,就计算资源、时间和/或金钱而言,视图可能会变得相当昂贵。例如,考虑示例6-2中显示的连接销售订单表和销售领域的视图。在此视图中,正在查询业务的整个历史期间所有领域的所有销售订单。即使查询视图的分析师只关注特定年份或地区(在这种情况下,过滤掉不相关的数据然后执行联接是有意义的),这也是如此。预定查询如果表的更新频率较低,定期将数据提取到表中可能更为高效。例如,在Google BigQuery中,如示例6-3中所述,我们指定目标表、查询运行频率和查询本身。bq query ... --destination_table=Orders_elt.lifetime_value \ --schedule='every 24 hours' \ --replace=true \ 'SELECT customer_id, COUNT(*) AS num_purchases, SUM(total_amount) AS lifetime_value FROM Orders.all_orders GROUP BY customer_id'在示例中,原始表每24小时只被查询一次。虽然与目标表相关的存储成本会增加,但存储成本通常比计算成本便宜几个数量级。因此,创建目标表的成本是相当可控的。定期查询的主要优势在于分析师查询的是目标表而不是原始数据。因此,分析师的查询相对较便宜。 定期查询的缺点是返回给分析师的结果可能最多可以延迟24小时。通过更频繁地运行定期查询,可以减少查询的过时程度。当然,定期查询的频率越高,成本优势就越容易消失。定期查询的另一个缺点是,如果最终的目标表从未被查询,它们可能会是一种浪费。物化视图很明显,平衡过时数据和成本的最有效方法是在首次请求视图时将原始数据提取到目标表中。随后的查询可以很快,因为它们可以从目标表中检索数据而无需进行任何提取。与此同时,系统需要监视基础表,并在原始表更改时重新提取数据。您还可以通过使用新的原始数据行更新目标表而不是重新查询整个表来使系统更加高效。虽然您可以构建一个数据工程流水线来执行所有这些操作,但现代云DWH在开箱即用时就支持完全托管的物化视图。在这些系统中创建物化视图类似于创建实时视图(见示例 6-4),您可以像查询任何其他视图一样查询它。DWH会确保查询返回最新的数据。DWH供应商会对管理物化视图收取一些费用。create materialized view vulnerable_pipes (segment_id, installation_year, rated_pressure) as select segment_id, installation_year, rated_pressure from pipeline_segments where material = "cast iron" and installation_year < "1980"::date;然而,请注意一些DWH(例如Amazon Redshift,在撰写本文时)不会自动为您管理物化视图;您需要设置一个计划或触发器来刷新物化视图。从这个意义上说,他们所谓的物化视图实际上只是一个表提取。Google BigQuery、Snowflake、Databricks和Azure Synapse会透明地维护物化视图内容。随着数据被添加到底层表中,视图内容会自动更新。安全性和血统数据治理的最佳实践是确保组织从数据摄取到使用的所有数据转换都能被跟踪。一个需要考虑的重要方面与识别哪些资源(例如,整个数据集或表中的某些字段)需要被视为敏感并需要通过设计进行保护有关。不仅要防止访问公司内部应视为机密的信息,甚至要准备好根据一些在某些情况下变得越来越严格的政府法规正确地管理数据(例如,欧洲的GDPR,美国的《健康保险可移植性与责任法案》或HIPAA等)。需要注意的是,在谈论安全性和合规性时,焦点不应仅限于谁访问了什么(这可以通过精细的访问控制列表[ACL]策略管理方法和数据加密/伪装技术来解决);它还应该集中在:数据的起源在其被使用之前应用的不同转换当前物理数据位置(例如,德国的数据湖或英国的DWH)追踪这类元数据被称为数据血统,它有助于确保正在使用准确、完整和可信的数据来推动业务决策。查看数据血统在需要保证数据局部性的情况下也很有帮助,因为由于某些本地法规(例如,在德国的电信元数据中),如果您可以追踪数据在其生命周期中的位置,那么您可以实施自动化来防止那些不符合最低要求的人访问、使用和移动该信息。正如您所见,元数据在帮助公司组织其数据并管理其访问方面起着中心作用。它对于以准确性、完整性、一致性和新鲜度为标准评估所收集数据的质量也至关重要:低质量的数据可能导致错误的业务决策和潜在的安全问题。有几种技术和相关工具可以支持数据质量活动,但最常见的是:标准化:将来自不同来源的数据放入一致的格式中的过程(例如,修复大小写不一致性、字符更新、错误字段的值、数据格式等)重复数据删除:识别重复记录(利用相似度得分)并随后删除重复的值的过程匹配:查找数据集之间的相似性或关联以发现潜在的依赖关系的过程个人资料和监控:识别一系列数据值(例如,最小值、最大值、平均值),揭示可能需要进行数据修复或模式演变的异常和异常如果您使用云供应商的本机托管服务执行数据转换,工具通常会管理并携带元数据。因此,如果您使用例如Google Cloud上的Data Fusion、视图、物化视图等,Data Catalog将得到更新并保持血统。如果使用Dataflow构建转换管道,则应更新Data Catalog。同样,爬虫将自动更新AWS上的Data Catalog,但是如果实施自己的转换,则需要调用Glue API以添加到目录中。您已经了解了DWH(我们体系结构中的中心)如何推动数据转换,使其对您想要实现的所有用例都可用,同时保持您需要维护强大的所有加工的血统的元数据。现在让我们看看如何构建组织结构的结构以匹配中心和辐射架构。组织结构在许多组织中,业务分析师的数量通常远多于工程师,通常的比例是100:1。对于希望构建主要服务业务分析师的数据和机器学习平台的组织来说,Hub-and-Spoke架构是理想的选择。由于Hub-and-Spoke架构假定业务分析师能够编写自定义的SQL查询和构建仪表板,因此可能需要一些培训。在以分析师为先的系统中,中央数据工程团队负责(参见图6-7中的填充形状):将原始数据从各种来源加载到数据仓库(DWH)中。许多来源可以配置为直接发布到现代数据仓库。确保数据治理,包括语义层和个人身份信息(PII)的保护。处理涉及业务单元跨越的工作负载(例如激活),涉及业务单元间数据的工作负载(例如身份解析),或需要专业工程技能的工作负载(例如机器学习)。管理常见的工件存储库,如数据目录、源代码存储库、秘密存储、特征存储和模型注册表。业务单元负责(参见图6-7中的未填充形状):从业务特定来源加载数据到数据仓库。将原始数据转换为可用于下游分析的形式。向治理目录和工件注册表中填充业务特定的工件。为业务决策制定报告、自定义分析和仪表板。图6-7显示了Google Analytics、财务和Salesforce作为数据源的示例。在我们的示例中,Google Analytics的数据可能需要跨多个业务单元使用,因此由中央数据工程团队进行摄取。财务和Salesforce数据仅由特定的业务单元使用,因此由该业务单元进行摄取。每个业务单元管理自己的部署。中央团队的责任是将数据加载到业务团队使用的数据仓库中。这通常意味着中央团队使用的软件需要在不同的云环境中具有可移植性,并且由管道生成的数据格式是可移植的格式。因此,Apache Spark和Parquet通常是构建ETL管道的常见选择。如果两个业务单元选择使用相同的数据仓库技术,那么在这两个业务单元之间共享数据就变得更简单了,因此,在可能的情况下,我们建议在整个组织中使用相同的数据仓库技术(如BigQuery、Snowflake、Redshift等)。然而,在通过收购进行业务扩展的企业中,这并不总是可能的。根据我们的经验,最好使用单一的云技术作为您的数据仓库,以充分发挥其在所有部门中的能力,即使您采用多云战略。我们曾与一些利用两种甚至更多技术的组织合作过,但他们在维护所有系统之间的一致性上所付出的努力并不值得其中的好处。在本节中,您已经了解了如何通过实施Hub-and-Spoke架构来现代化您的数据平台,将您的数据仓库置于中心位置。您了解了多个"辐条"是如何围绕中央枢纽,即现代数据仓库,以实现批处理和流处理的任何用例,利用纯SQL语言,并符合数据治理要求。在接下来的章节中,我们将讨论数据仓库如何使数据科学家能够进行其活动。数据仓库(DWH)以支持数据科学家数据分析师通过对数据进行临时分析以创建报告,然后通过商业智能(BI)将报告操作化,以支持数据驱动的决策。数据科学家旨在利用统计学、机器学习和人工智能自动化和扩展数据驱动的决策。现代云数据仓库(DWH)对数据科学家和数据科学工具有哪些需求呢?正如您在图6-1中所见,他们需要以各种方式与DWH进行交互,以执行查询或仅仅获取对低级数据的访问权限。在本节中,我们将研究他们可以利用的最常见工具,以实现这一目标。正如您在前一章中所看到的,数据科学家需要进行实验,尝试不同形式的自动化,并了解自动化如何在历史数据的各个切片上运作。正如我们之前所见,数据科学家用于实验的主要开发工具是笔记本。因此,他们需要以高效的方式访问DWH中的数据,无论是通过DWH的查询界面进行的探索性数据分析,还是通过DWH的存储界面进行的操作化(请参阅图6-1)。确保您的云DWH支持这两种机制非常重要。让我们看看这些机制是如何工作的。查询接口在自动化决策之前,数据科学家需要进行大量的探索性数据分析和实验。这需要以交互方式进行。因此,需要一种快速的方式从笔记本中调用SQL查询。Jupyter魔法(例如图6-8中单元格中的%%bigquery行)提供了一种无需样板代码即可从笔记本中调用SQL查询的方法。结果以一种称为DataFrame的本机Python对象返回,可以使用pandas(一种数据分析函数库)进行操作。重要的是要注意,这是在不创建数据的内存提取的情况下完成的。云数据仓库以分布式方式执行SQL查询。笔记本服务器不需要运行在与数据仓库相同的计算基础设施上。利用数据仓库后端进行大规模数据处理以及笔记本前端的编程和可视化能力的结合对于数据科学家的高效工作是强大且必要的。存储API虽然笔记本与数据仓库连接的交互能力对于探索和实验非常重要,但这并不是自动化所需的。对于自动化而言,数据访问速度至关重要。机器学习框架应该能够绕过查询API,直接访问数据仓库的存储层。存储访问应支持从多个后台线程并行读取数据,因为通常情况下,在ML加速器(GPU或TPU)对前一批数据进行繁重计算的同时,会读取下一批数据。因此,与其使用查询API,不如使用机器学习框架支持的存储API,以高效且并行的方式从数据仓库中读取数据。在示例6-5中展示了使用Spark和TensorFlow从Google BigQuery中使用Storage API读取数据的方法。df = spark.read \ .format("bigquery") \ .load("bigquery-public-data.samples.shakespeare") def read_bigquery(): tensorflow_io_bigquery_client = BigQueryClient() read_session = tensorflow_io_bigquery_client.read_session( "projects/" + PROJECT_ID, "bigquery-public-data", "samples", "shakespeare", ..., requested_streams=2) dataset = read_session.parallel_read_rows() transformed_ds = dataset.map(transform_row) return transformed_ds 查询界面和存储API是数据科学家用来访问数据仓库进行分析的两种方法。现在有一个新的趋势需要考虑——在不必提取数据的情况下,直接在数据仓库中实现、训练和使用机器学习算法的能力。在接下来的部分,我们将看看它是如何工作的。在不移动您的数据的情况下进行机器学习一些现代的云数据仓库(例如BigQuery和Redshift,截至撰写本文时)还支持在数据仓库中对数据进行机器学习模型训练,而无需首先提取数据。它们通过在SQL中训练简单模型,并通过分别将任务委托给Vertex AI和SageMaker来训练更复杂的模型来实现这一点。让我们看看如何直接从您的数据仓库利用训练和服务(称为激活)您的机器学习算法。训练ML模型假设您想要训练一个机器学习模型,以预测用户在给定账户特征和账户费用的情况下是否会流失:可以利用历史数据来完成所有操作,如示例6-6所示。训练的模型类型可以非常复杂。CREATE MODEL customer_churn_auto_model FROM (SELECT state, account_length, area_code, total_charge/account_length AS average_daily_spend, cust_serv_calls/account_length AS average_daily_cases, churn FROM customer_activity WHERE record_date < '2020-01-01' ) TARGET churn FUNCTION ml_fn_customer_churn_auto IAM_ROLE 'arn:aws:iam::XXXXXXXXXXXX:role/Redshift-ML'SETTINGS ( S3_BUCKET 'your-bucket' );您可以使用访问者实际购买的历史数据来训练推荐系统,如示例6-7所示。CREATE OR REPLACE MODEL bqml_tutorial.my_implicit_mf_model OPTIONS (model_type='matrix_factorization', feedback_type='implicit', user_col='visitorId', item_col='contentId', rating_col='rating', l2_reg=30, num_factors=15) AS SELECT visitorId, contentId, 0.3 * (1 + (session_duration - 57937) / 57937) AS rating FROM bqml_tutorial.analytics_session_data示例6-8展示了一个只使用两个SQL语句编写的异常检测系统。CREATE OR REPLACE MODEL ch09eu.bicycle_daily_trips OPTIONS( model_type='arima_plus', TIME_SERIES_DATA_COL='num_trips', TIME_SERIES_TIMESTAMP_COL='start_date', DECOMPOSE_TIME_SERIES=TRUE ) AS ( SELECT EXTRACT(date from start_date) AS start_date, COUNT(*) AS num_trips FROM `bigquery-public-data.london_bicycles.cycle_hire` GROUP BY start_date ); SELECT * FROM ML.DETECT_ANOMALIES( MODEL ch09eu.bicycle_daily_trips, STRUCT (0.95 AS anomaly_prob_threshold)) ORDER BY anomaly_probability DESC能够仅使用SQL进行机器学习而无需设置数据移动,使得更广泛的组织能够进行预测性分析。这使机器学习变得更加民主化,因为借助典型的商业智能工具,数据分析师现在可以实施预测。这还显著提高了生产力——如果只需要两行SQL而不是需要熟练掌握TensorFlow或PyTorch的数据科学家进行为期六个月的项目,那么组织更有可能能够识别异常活动。ML训练和服务训练机器学习模型并不是现代数据仓库支持的唯一机器学习活动。它们还支持以下功能:将经过训练的机器学习模型导出为标准机器学习格式,以在其他地方进行部署。将机器学习训练纳入数据仓库作为更大机器学习工作流的一部分。调用机器学习模型作为外部函数。直接加载机器学习模型到数据仓库,以便以分布式方式高效调用机器学习预测。让我们一起看看这四种活动,它们为何重要,以及现代数据仓库如何支持它们。导出经过训练的机器学习模型在数据仓库中训练的机器学习模型可以通过ML.PREDICT在批处理模式下直接对历史数据进行调用。然而,对于需要实时结果的现代应用程序(例如,基于连接的用户,决定在页面中显示哪些广告的电子商务应用程序),这样的能力是不够的。有必要能够在线调用模型,即作为对单个输入或一小组输入的同步请求的一部分。在Redshift中,您可以通过将模型部署到SageMaker端点来实现这一点,在Google Cloud中则通过以SavedModel格式导出模型。然后,您可以将其部署到支持此标准机器学习格式的任何环境中。当然,支持Vertex AI端点,还支持SageMaker、Kubeflow Pipelines、iOS和Android手机以及Coral Edge TPUs。Vertex AI和SageMaker支持部署为微服务,并且通常用于基于服务器的应用程序,包括网站。部署到流水线支持流数据处理、自动交易和监控等用例。部署到iOS和Android支持移动电话,而部署到Edge TPUs则支持定制设备,例如汽车仪表板和信息亭。在机器学习流水线中使用您训练的模型很少有数据科学家的实验仅包括训练一个机器学习模型。通常,一个实验将包括一些数据准备、训练多个机器学习模型、进行测试和评估、选择最佳模型、创建模型的新版本、在小部分流量上设置A/B测试以及监控结果。只有其中的一些操作是在数据仓库中完成的。因此,在数据仓库中完成的步骤必须成为更大的机器学习工作流的一部分。机器学习实验及其工作流程被捕捉在机器学习流水线中。这些流水线包含许多容器化步骤,其中少数涉及数据仓库。因此,当数据准备、模型训练、模型评估等成为机器学习流水线的一部分时,它们必须作为容器可调用。流水线框架提供了方便的函数,以在云数据仓库上调用操作。例如,要从Kubeflow Pipelines中将BigQuery作为容器化操作调用,您可以使用BigQuery运算符。调用外部机器学习模型如今,机器学习是理解非结构化数据(如图像、视频和自然语言文本)的常用方法。在许多情况下,已经存在许多种类非结构化内容和用例的预训练机器学习模型,例如,已有预训练的模型可用于检测某些评论文本是否包含有害言论。您无需训练自己的有害言论检测模型。因此,如果您的数据集包含非结构化数据,能够对其进行机器学习模型的调用非常重要。例如,在Snowflake中,可以使用EXTERNAL FUNCTION调用Google Cloud的Translate API。通过通过网关创建API集成,然后配置Snowflake,可以按照示例6-9中演示的步骤完成这一操作。create or replace api integration external_api_integration api_provider = google_api_gateway google_audience = '<google_audience_claim>' api_allowed_prefixes = ('<your-google-cloud-api-gateway-base-url>') enabled = true; create or replace external function translate_en_french(input string) returns variant api_integration = external_api_integration as 'https://<your-google-cloud-api-gateway-base-url>/<path-suffix>’; 鉴于这一点,可以在SELECT语句中调用Translate API的某一列:SELECT msg, translate_en_french(msg) FROM ...加载预训练的机器学习模型使用外部函数调用机器学习模型可能效率较低,因为计算没有充分利用数据仓库的分布式性质。更好的方法是加载已训练的机器学习模型,并使数据仓库在其自己的计算环境中调用该模型。在BigQuery中,您可以首先加载TensorFlow模型,然后通过在SELECT语句中使用ML.PREDICT来调用它,如示例6-10中所示。CREATE OR REPLACE MODEL example_dataset.imported_tf_model OPTIONS (MODEL_TYPE='TENSORFLOW', MODEL_PATH='gs://cloud-training-demos/txtclass/export/exporter/1549825580/*') SELECT * FROM ML.PREDICT( MODEL tensorflow_sample.imported_tf_model, (SELECT title AS input FROM `bigquery-public-data.hacker_news.stories`)) 由于ML.PREDICT无需从数据仓库进行到已部署模型服务的外部调用,通常速度要快得多。它也更加安全,因为模型已经加载,不容易被篡改。总结在本章中,我们重点描述了数据仓库如何成为现代数据平台的核心,分析了如何利用中心枢-辐射体系结构实现数据分析、机器学习训练和激活。主要要点如下:现代数据平台需要支持更快地向决策者提供洞察。中心枢-辐射体系结构是对于没有需要适应的传统技术的组织而言的理想架构。所有数据都以尽可能自动化的方式落入企业数据仓库。可以使用预构建连接器摄取来自大量SaaS软件的数据。可以使用CDC工具使数据仓库反映在OLTP数据库或ERP系统中所做的更改。可以联合数据源,例如Blob存储,确保某些数据不需要移动到数据仓库。可以在支持SQL的关系数据库上运行外部查询。为了协助业务分析,投资于将操作推送到数据库而不是依赖于提取OLAP立方体的SQL工具。当你有数千到数百万的客户时,提供一个轻量级的图形可视化层更具可扩展性,可以嵌入到用户已经使用的工具中。建立一个语义层来捕获关键业绩指标(KPIs)、度量和维度,以促进一致性和重用。视图提供了一种使落入数据仓库的原始数据更易用的方式。将查询调度到表中以提取视图内容可能更具成本效益。物化视图则兼具两者的优点。随着数据的爆炸,架构变得更加复杂,访问和利用数据的用户数量增加,立法者正在引入更严格的规则来处理数据,数据治理和安全性变得更加重要。数据科学家需要能够从笔记本交互式地访问数据仓库,在没有样板代码的情况下运行查询,并直接从数据仓库的存储中批量访问数据。能够在不进行任何数据移动的情况下进行机器学习,并使得到的机器学习模型能够在许多环境中使用,有助于在业务的许多部分推动AI的使用。在组织层面,一些数据工程和治理功能是集中的,但不同的业务部门以不同的方式转换数据,仅在需要时进行。在下一章中,我们将讨论数据湖和数据仓库世界的融合,看看现代平台如何利用这两种范式的优势,为最终用户提供最大的灵活性和性能。
0
0
0
浏览量2023
攻城狮无远

第二章:创新数据的战略步骤

您的领导之所以为您建立数据平台提供资金,很可能是因为他们希望组织进行创新。他们希望组织能够发现经营的新领域,创造更好的业务运作方式,或为更多客户提供更高质量的产品。这种形式的创新通常通过更好地了解客户、产品或市场来实现。无论您的组织是想要减少用户流失、获取新用户、预测产品的维修周期,还是确定低成本替代品是否会受欢迎,任务始于数据收集和分析。数据是分析业务当前状态、识别缺陷或机会、实施改进现状的方式以及衡量这些变化影响的必需品。通常,必须将业务单元特定的数据与其他数据(来自整个组织以及来自供应商和市场的数据)一起进行分析。在构建数据平台时,重要的是要有意识地将最终的“为什么”(促进创新)牢记在心。在本章中,您将了解构建促进创新的平台时应采取的七个战略步骤,为什么这些步骤是必不可少的,以及如何使用现代云技术实现它们。将这些步骤视为构建金字塔的基石(如图2-1所示),其中每个步骤都作为随后步骤的基础。在本章中,我们将明晰说明所有步骤背后的概念,但将其实现的详细信息推迟到后续章节。例如,虽然我们将在本章描述打破信息孤岛的概念,但我们将在第5、6和7章中描述实现这一目标的分析中心或数据网格方法的架构。步骤1:战略规划为了使最后的六个步骤成功,首先需要制定一项战略计划,其中需要识别三个主要组成部分:目标 组织在充分利用数据方面的雄心是什么?重要的是要深入挖掘,确定超越成本节约的目标。具体而言,重要的是要确定将使用数据做出的决策以及可以通过哪些指标了解转变是否成功。利益相关方 在组织中,谁有权力赞助和推动更深层次的变革?确保将所有这些利益相关方聚集在一起——根据我们的经验,IT 项目往往资源不足,常常面临失败的风险,而业务驱动的项目则有更长期的高层支持和资金支持。业务项目还具有更高的投资回报率。变革管理流程 如何有效地将方法层层传递并传达给整个组织,以获取最终用户的支持?这种战略规划需要定期审视。目标是否仍然相同?是否有新的利益相关方需要了解情况?是否在队伍中酝酿着不满?让我们逐一看看这三个组成部分。战略目标在构建数据和AI/ML平台时,您应该明确自己的目标。很多时候,利益相关者会以当前平台的局限性为目标,例如,如果您当前最令人头痛的问题是报告工作负载导致级联宕机,目标可能会表述为“我们希望能够在三小时内为我们的三百万客户创建月度对账单”。然而,在构建平台的目标不应该被一个单一的用例狭隘地定义。相反,您希望从对您想要实现的战略目标的清晰理解开始。基于业务的战略方向来设计系统。对于新购买提供信息的期望周转时间是多少?客户数量的预期增长是多少?通过手机还是通过经纪人到达的客户比例会随时间而变化吗?IT团队的预期人数是多少?业务是否希望让现场人员做出更多决策?您是希望发送月度对账单,还是希望根据需求动态生成历史活动报告?我们计划通过这些报告赚取钱?我们是否会根据这些结果改变业务做法?报告需要提供什么样的支持来支持我们当前的业务?当前平台无法支持这些需求将自然从这些战略关注中产生,但这使您能够全面地构建系统的要求,而不是被单一用例和老旧思维所束缚。这还有助于您向非技术高管传达对系统的需求。如果可能的话,获取未来两三年内这些战略业务目标的数值估算,以帮助您做出具有成本效益的决策。例如,简单地说“查询应尽可能快地运行”可能很诱人,但这是建造一个过度工程化和昂贵系统的方法。相反,如果您知道在高峰时期会有1,500个并发查询,每个查询处理100GB的数据,需要在10秒内运行,您可以选择实现目标而不破坏银行的技术。这也可以反向运作。了解业务以60%的年同比速度获取客户,可能清楚地显示点击流数据集有望达到每天1TB的量级。这将阻止您做出短视的决策,需要撤销。这通常受到您可以预见业务增长的时间范围的限制。它还受到现实世界事件的影响。例如,2020年的COVID-19大流行颠覆了许多企业的计划,加速了转向数字和全渠道体验的步伐。有时,您构建的系统必须被废弃并重新构建,但通过全面考虑应急情况,可以最大限度地减少这种情况发生的频率。尽管细节可能有所不同,但我们发现大多数组织确定的战略目标最终需要他们的数据平台具备以下特点:降低运营数据和AI平台的成本。尤其是随着数据集大小的增长,线性增长的IT成本可能难以持续。加速新项目产生价值的时间,从而增加创新速度。对新想法进行实验不应该面临几个月的延迟,如采购机器、安装软件、获取数据集访问权限等。民主化从数据中获得的见解。使领域专家和现场人员能够直接与数据交互并获得见解。在决策中纳入预测分析,而不仅仅是描绘性分析。例如,不仅要测量上周使用的材料量,还要基于最近过去的使用量来预测下周所需的材料量。这些因素的相对优先级在企业之间会有所不同(这也是应该的)。许多初创公司和数字原生公司强调创新速度和灵活性的增长,而许多成熟企业强调成本而不是灵活性。例如,初创公司可能会使用PB级的DWH,即使其数据规模较小,因为它预计将看到10倍的年度增长。与此相反,更成熟的企业可能选择使用批处理,因为它比流处理更便宜。这些不同的起点影响可能的创新和增长类型 - PB级的DWH可能使初创公司能够基于每次交易的每次付款推荐,而更成熟的企业可能仅每天发送一次推荐电子邮件,而且只向进行大额订单的客户发送。确定利益相关者确立战略的基础是正确的需求收集。为了成功做到这一点,非常重要的是要确定组织内能够理解需求并在所有不同业务单元之间有效协作的正确人员,以降低选择错误方法和解决方案的风险。但是,谁是正确的人呢?我们是指来自业务方面的人(例如,首席执行官或首席财务官[CFO]),还是更好地依赖于IT团队(例如,首席信息官[CIO]、首席技术官[CTO]、首席数据官[CDO]等)?嗯,这实际上取决于组织。我们见过许多不同的方法,但一个共同的主题是,当直接由业务支持时,这种转型过程通常具有最高的成功率。为什么呢?很多时候,IT组织可能只被授权保持运营并在年复一年中降低成本。如果您的利益相关者仅在IT中,他们的激励与如果您的利益相关者包括需要开发新产品、触及更多客户或从根本上改变业务的BU中的人是非常不同的。通过使新数据平台的定义不仅仅是纯粹的IT活动,您可以提高转型的可见性,并确保新平台允许组织解决以前无法解决的许多业务问题(例如,实时分析)。即使在支持该倡议的公司领域内,也要确保所有参与其中的人都全力以赴是至关重要的。但是,这些人可能非常忙碌,没有足够的时间和专业知识来投入到这个转型项目中。因此,另一个关键问题是:公司是否有足够的内部人员(具有正确的技能和经验)来支持该倡议?或者您是否需要在公司外找到某人来引导项目朝着正确的方向发展?这不仅仅涉及技术知识,还涉及领导和管理,以确保数据平台的设计和实施成功,并且业务结果是积极的。变更管理一旦确定了目标和应该引导实现目标的人员,接下来必须为变更管理定义一个策略。组织可能拥有最雄心勃勃的目标,由最有影响力的人支持,但如果没有明确的任务来有效地将信息传达到组织内部,实施项目将变得非常困难。 在接受像以数据为驱动的变革这样的项目时,我们看到很多公司忽视了将变革的文化方面放在重要位置,将其视为一项纯粹的技术项目。业务用户和一般将利用数据平台的员工应准备好接受变革,而这只能通过适当的流程和正确的技能来实现。 如图2-2所示,变更管理是人、技术和流程之间的交集:人和技术 开发全面的培训计划是至关重要的,以使员工能够利用组织内为他们提供的新资源。这可以由公司自己(内部提供)或由合作伙伴(外部提供)完成。组织越强调提升员工技能,就越能成功地实现其整体业务目标。人和流程 这总是领导层的问题。公司内部的领导必须促使整体信息传达;当人们受到领导层的激励时(这也是利益相关者非常重要的另一个方面!),采用水平就会提高。我们看到很多项目因为缺乏来自发起倡议的同一人员的适当支持而失败。领导者有必要通过几次内部宣传活动,适当地在公司内传播信息,帮助人们拥抱变革。一些建议的问题是:团队结构如何?它们是否得到了高管的支持?云项目的预算、治理和评估是如何进行的?流程和技术 这与组织利用云原生服务进行扩展的能力有关。一些建议的问题包括:组织在多大程度上使用托管和无服务器云服务抽象基础设施?自动化流程和运行通过其的可编程基础架构的实施水平如何?自动化是成功的关键因素,因为它一方面减少了人力投入,另一方面有助于进行低风险和频繁的变更,这是创新的关键要素。成功需要这三个元素协同工作。许多组织通过建立一个名为卓越中心(CoE)的专门小组来实现这一点,其目标是确定方向并推动公司朝着人-流程-技术的和谐方向发展。我们将在第12章中通过一个具体的示例重新审视这一概念。步骤2:通过采用云方法降低总体拥有成本(TCO)对于大多数企业来说,在制定战略之后的第一步是制定(并找到)预算。将企业数据仓库(DWH)和数据湖迁移到云端可以大大节省传统实施的成本。让我们看看为什么会这样以及如何为实现最大节省做好准备。云端为何成本更低将数据迁移到云端可以因为多种因素为您节省资金:运营成本降低 在本地,贵公司需承担整个系统的运营成本,并且很多维护工作都是手动进行的。云服务提供商(我们将其称为超大规模提供商)则通过构建非常高效的方式来管理大型机群。例如,亚马逊通过在低利润业务中运行世界上最大、最可靠的网站之一,将这方面的专业知识带到云基础设施中,并提供非常低的成本。同样,谷歌运行着九个服务,必须以非常高效的方式运行,因为每个服务(如搜索、Gmail 和 YouTube)有超过十亿的免费用户使用。公共云的用户由于云服务运营中内置的高度自动化而受益,绝大多数云服务不需要维护,因为大多数活动(如硬件维护、安全检查、软件包更新等)在幕后自动管理。计算和存储的适度规模 与购买与预期峰值使用量相匹配的设备不同,云提供商允许您根据需求和使用情况调整计算资源的规模。例如,您可以从小规模开始,随着需要创建的报告数量随时间增长而增加机器的数量。这个好处既适用于云提供商的服务(如Amazon EMR、Google Cloud Dataproc 和Azure HDInsight),也适用于在云基础设施上运行的第三方工具,如Databricks 或Teradata Vantage。工作负载的自动缩放 许多云服务(例如Azure Cosmos DB、AWS Aurora、Google Cloud Composer、Snowflake 和Actian Avalanche)允许您在高峰时段分配更多机器,在低峰时段分配更少机器。请注意,我们说的是更少的机器,而不是零机器。尽管在非工作时间完全关闭服务可能很诱人,但请考虑是否真的希望保留那种实体店的模式。您公司的网站希望在晚上不关闭。您的后端系统也不应该关闭。在晚上保留服务紧急请求的能力通常会取得戏剧性的效果。无服务器工作负载 一些现代云服务(例如Google Cloud Platform 上的BigQuery、AWS 上的Athena、Azure Functions)是无服务器的 - 您可以提交代码,系统会帮您执行。将无服务器系统视为在所有超大规模提供商的客户之间共享的自动缩放集群。因此,无服务器系统将云基础设施操作的成本优势提升到整个堆栈。由于劳动力往往是IT预算中最昂贵的项目,无服务器云服务将带来成本效益最高的解决方案。请注意,许多供应商将其自动缩放服务定位为“无服务器”,因此您应验证所涉服务是否真正是无服务器 - 只有在是多租户的情况下,您才能获得无服务器解决方案的劳动力成本优势。如果群集属于您,您将需要管理该群集(例如,找出谁在群集上运行什么作业以及何时运行),因此您将无法获得无服务器解决方案的劳动力成本优势。既然您对为何云可能成本较低有了更好的理解,那么让我们来看看如何估算您可能实现的节省金额。节省多少成本呢?在图2-3中,我们展示了我们在一个真实的数据湖上进行的概念验证(PoC)的结果。我们首先将工作负载不加修改地转移到了云上,然后将其放到了可伸缩的基础设施上,最后进行了现代化改造。我们测量了云成本会是多少。由于这是一个PoC,系统在这些配置下运行的时间不足以测量运行这些系统的人员成本。实际节省的金额当然会根据您平台和工作负载的具体细节而异。作为一个粗略的估计,将工作负载不加修改地转移到云上通常会提供约10%的节省。添加合适大小通常会额外增加5%。自动伸缩通常会增加40%的节省,而无服务器通常会额外增加30%。如果您充分利用所有这些节省,例如通过将在本地使用Hive的工作负载更改为在云上使用无服务器解决方案,成本节省可高达95%。在将工作负载迁移到云上之前,分析源工作负载是至关重要的。在某些情况下,纯粹的搬迁可能不会有益,因为工作负载是为利用本地环境的特定硬件功能而开发的。在这些情况下,评估更新代码(如果可能)以使工作负载现代化,并使其能够利用自动伸缩和无服务器功能是很重要的。云计算何时有助于?在所有这些背后的是基本的云经济学。忽略价格折扣的影响,用 10 台机器运行一个任务 10 小时的成本与用 100 台机器运行一个小时的任务的成本相同,或者与在 1,000 台机器上运行任务 6 分钟的成本相同。能够在多租户集群中为您提供 1,000 个“热”实例的能力是使无服务器成本效益如此高效的原因。当然,这不仅仅是成本问题——如果一个操作在今天需要花费 10 小时,而现在可以在 6 分钟内得到结果,这样更及时的决策可能带来的业务价值远远超过了计算成本的增加。有哪些工作负载不适合完全迁移到云?从一般的角度来看,任何类型的工作负载都可能成为云环境获取前述所有好处的潜在目标。可能有一些情况,混合方法(例如,在本地环境中的工作负载的一部分以及云中的其余部分),我们将在第 9 章深入探讨,可能更适合一些情况:比如一个稳定的工作负载(即,不需要增长且没有峰值),规模大,非常特定,比如全球尺度的数值天气预报模型。在这种情况下,有一部分工作负载需要专业硬件(例如,共享内存,高速互连),这消除了云的即时硬件成本优势,以及需要了解天气模型的专业运维人员,并且它几乎每天都经历相同的负载。这一部分可以保留在本地,同时拥有其他可以立即从云采纳中受益的相关元素(例如,数据备份)。 短暂且峰值的工作负载往往最能从云迁移中受益,主要是通过减少花费宝贵时间进行资源配置的需要。短暂和峰值工作负载还将受益于自动缩放和按使用量付费的云经济学。因此,在基于成本的优先考虑云迁移时,请从短暂和峰值工作负载开始。此外,与云计算相比,与员工流失相关的风险降低了,因为技术栈是众所周知的,企业支持是可用的。另一方面,使用定制数据中心,你的 IT 部门可能会受到许多限制!第3步:打破信息孤岛在将所有数据迁移到云端后,您可以开始思考如何更好地从中获取价值。开始从数据中获取价值的最佳方法之一是打破数据孤岛。换句话说,避免拥有多个不相关、分离且有时不可见的数据集。我们现在处于图2-1金字塔的第三层。打破数据孤岛涉及在分散和价值之间找到合适的平衡。分散是好的,因为数据距离领域专家越远,数据质量就降低。因此,您必须让领域专家对数据负责。不要将数据集中在IT中。同时,请记住,通过结合组织内的数据甚至是合作伙伴共享的数据,您可以获得最大的AI/ML价值。打破组织不同部分之间的隔离是关键。如何解决这个问题?如何让组织的不同部分保持对其数据的控制,同时为任何有权访问数据的人提供访问权限?我们将在以下部分探讨如何做到这一点。统一数据访问在云上实现数据的统一存储位置并不意味着要采用中央所有权结构。例如,数据可以存储在Azure Blob Storage上,但每个部门都可以将“他们的”数据放在“他们的”存储桶中。对数据的访问应该通过云服务提供商的身份和访问管理(IAM)进行管理。避免将本地身份验证机制(如LDAP或Kerberos)带到云上。如果需要维护混合基础架构,在本地的Kerberos角色可以映射到云上的IAM。如果使用需要自己的身份验证机制(例如MySQL)的软件,请使用身份验证代理以避免登录机制的多样化。避免使用既不提供IAM也不提供IAM代理的软件。将数据和见解锁定将在长期内给你带来很多困扰,无论该软件在近期有什么好处。如果使用多云,请确保在SaaS单点登录(SSO)身份验证机制上进行标准化,例如Okta,然后将Okta身份验证映射到各个云的IAM。另一种方法是,如果您有一个“主”云,可以将该云的IAM与其他云进行联合:例如,如果Azure是您的主云,但您希望在Google Cloud上执行一些数据工作负载,则可以将Google Cloud Identity与Azure Active Directory进行联合。确保基于实际发出请求的用户对数据进行审计,而不是通过破坏与个人用户的关联的服务账户。由于有关数据访问的隐私和政府法规继续变得更为严格,避免陷入任何以其自己的云项目中运行或以不透明方式读取数据的软件。这意味着每个部门都将管理其数据并对其进行分类。例如,他们可以将数据仓库中的某些列标记为包含财务数据。数据治理政策可能是只有会计和副总裁以上级别的人员被允许查看财务数据。此政策由IT部门(而不是数据所有者)使用云IAM来强制执行。不要陷入中央控制数据以打破数据隔离的诱惑。数据质量会随着距离领域专家的距离而降低。您希望确保领域专家创建数据集并拥有存储桶。这允许本地控制,但对这些数据集的访问将通过云IAM角色和权限进行控制。使用加密、访问透明性和遮蔽/动态技术可以帮助确保即使数据准确性的责任属于领域团队,也能实现全组织的安全性。选择存储存储数据的选择取决于数据的类型:结构化和半结构化数据应存储在专为SQL分析进行优化的位置。Google BigQuery、AWS Redshift、Azure Synapse、Actian Avalanche、Snowflake 等是不错的选择。这些工具允许您集中数据,同时由不同团队管理不同的数据集,但作为同一大型数据仓库的一部分。另一种选择是将结构化或半结构化数据存储在开放格式中,比如使用分布式表格格式(如Apache Iceberg或Databricks Delta Lake)在云块存储(如AWS S3)之上,例如Parquet。虽然在使用这些开放格式(而不是BigQuery中的原生存储机制,比如Capacitor)存储数据时可能在SQL分析中稍微降低性能,但存储成本更低并且支持非SQL分析(如机器学习)的灵活性可能是值得权衡的。无结构数据应存储在为从各种计算引擎读取进行优化的格式和位置。使用标准的云友好格式,如Parquet、Avro、TensorFlow Records 和 Zarr,并将文件存储在Google Cloud Storage、Azure Blob Storage 或 AWS S3上。逗号分隔值(CSV)和JavaScript对象表示法(JSON)是人类可读且相对易于处理的格式,因此也有其用武之地。通常,每个分析数据集或存储桶将位于单个云区域(或多个区域,如欧盟或美国)。我们将这样的存储层称为分布式数据层,以避免陷入数据湖与数据仓库的争论。鼓励团队提供对其数据集的广泛访问("默认开放")。数据所有者控制访问权限,并负责对受组织范围数据治理政策约束的数据进行分类。专业团队还可能有能力标记数据集(用于隐私等)。对其数据集的权限由数据所有者管理。培养您的团队,使他们能够发现和标记数据集,并构建集成流水线,不断增加分布式数据层的广度和覆盖范围。语义层在构建民主化数据文化时可能出现的一个附带效应是可能会看到分析孤立。同一变量可能在组织的不同部分以不同的列名被称为。每次计算关键绩效指标(KPI)都是计算它的一次机会,有可能以错误或不一致的方式计算。因此,鼓励数据分析团队构建一个语义层(以便可以标准化词汇表并可以在其他所有地方计算一次并重复使用KPI),并通过它应用治理——见图 2-4。类似 Looker、Informatica、Collibra、AtScale 和 Cube 的工具可以帮助定义和标准化语义层。使用这些工具的优势是它们是多云的,并且可以跨足在场地和云之间。因此,您可以在所有环境中标准化数据治理。在云上,实际的查询是由底层的数据仓库执行的,因此在使用这些工具创建仪表板时不会发生数据复制。不要制作数据副本。提取和复制会增加安全风险,使数据依赖关系难以追踪,并降低分析的及时性。建立一个轻量级的语义层,并将计算带到单一数据源。还有一种趋势是在不同环境中提供一致的控制界面。例如,Google 的 BigQuery Omni 允许您在 BigQuery 界面中处理 AWS S3 存储桶、Azure Blob 存储和 MySQL 中的数据。Informatica、Collibra、Looker 等工具提供了对不同云和本地环境中数据的一致界面。正如您所见,消除数据孤岛是释放数据潜力的关键步骤,因为它实现了团队之间更好的可见性和更好的协作。现在让我们看看如何进入下一步,以更快地利用您手头的这些数据。第四步:更快地在上下文中做决策商业决策的价值随着延迟和距离的增加而降低。例如,假设你能够在一分钟内或一天内批准一笔贷款。一分钟的批准时间比一天的批准时间要更有价值得多。同样,如果你能够做出一个考虑到空间上下文(无论是基于用户当前居住地还是当前访问地点)的决策,那么这个决策就比不考虑空间上下文的决策更有价值。因此,你的平台现代化的一个重要目标应该是,在不复制数据的情况下,可以在数据上执行地理信息系统(GIS)、流处理和机器学习(ML)操作。前一节的原则,即将计算带到数据所在地,也应适用于GIS、流处理和ML。批处理到流处理在我们与许多组织合作的情况下,数据规模每年增长在30%到100%之间。由于复利的力量,这意味着在未来五年需要为数据增长规划4倍到32倍的容量。大规模增加数据量的一个反直觉方面是,随着数据量的增加,更频繁地处理数据变得更有意义。例如,假设一个业务根据其网站流量创建每日报告,而这份报告需要两个小时才能生成。如果网站流量增长了4倍,报告生成时间将增加到八个小时,除非业务投入四倍数量的机器来完成工作。与此不同的方法是,通过每天计算四次六小时的数据统计信息,并将这些报告汇总,以创建每天更新四次的每日报告,如图2-5所示。这两种方法的计算成本几乎相同,然而第二种方法可以带来相当大的业务利益。将这种方法推广开来,将批处理数据处理更改为流处理变得有意义,尤其是随着数据量的增加,许多企业开始进行这种转变。上下文信息另一个加快决策的关键因素是自动化。随着数据质量的提高,或者企业将焦点转向其长尾客户,有必要减少用户体验中的摩擦。对于你产品的频繁、熟练的用户来说,他们能够忍受的问题要比偶尔、不那么复杂的用户多得多。能够捕捉到沮丧的用户并为他们提供上下文的帮助变得至关重要。实时的、基于位置的可视化越来越成为决策的方式。在许多情况下,这些可视化是内嵌到用户使用的应用程序中的。例如,Shopify为商家提供了图形和图表,展示他们的店铺表现如何。为了以规模做到这一点,这些图形实际上被嵌入到网站中,而不是作为独立的仪表板产品。确保位置信息是你的数据模式的一部分是最佳实践,无论是店铺的位置还是交货卡车的位置。因此,如果你的模式包括地址,请确保该模式要求对地址进行地理编码并使其成为规范形式。事后向数据集添加干净的地理信息是非常困难的。成本管理尽管很少有技术高管会对前述观点提出异议,但流处理有着在实施、监控和长期维护方面成本高昂的声誉。2 如何在不超出预算的情况下实现实时决策呢?首先,不要构建两个系统,一个用于批处理,另一个用于流处理。相反,将批处理视为流处理的一种特殊情况。诸如Apache Flink和Apache Beam(甚至Spark结构化流)之类的软件工具使这成为可能。其次,不要自定义构建监控、可观测性、延迟到达、扩展等功能。Flink和Beam是开源技术,但为了执行它们,利用完全托管的服务,如AWS上的Kinesis Data Analytics或GCP上的Cloud Dataflow——这是因为管理和排除故障流处理基础架构的技能相当罕见。另一种方法是将流处理视为批处理的一种特殊情况(或者根据Flink的理念相反)。采用这种方法的人尝试进行微批处理,通过尽可能快地处理小量数据。当需要非常新鲜但不一定是实时的数据时,这种方法可以奏效。这意味着等待一小时或一天才能运行批处理是不可接受的,但与此同时,了解过去几秒发生了什么并不重要。接下来,将流式数据落入提供最新信息给读取者(并且能够大规模处理它们)的DWH或存储层。换句话说,只要您能够实时落地数据,对数据的所有分析都将反映最新信息,而无需采取进一步的努力。既然您已经了解了如何利用最新和与上下文相关的信息,让我们看看如何注入AI/ML以更好地理解数据。步骤5:利用打包的人工智能解决方案实现飞跃2010年代技术领域最令人振奋的发展之一是深度学习的崛起,这是人工智能(AI)的一个分支。AI涵盖了计算机作为决策工具的问题类别。通常情况下,AI系统是通过编写计算机以思考或行动像人类一样来构建的。为了做到这一点,专家的思考过程必须被精心编码成计算机可以遵循的规则。由于人类通常无法精确解释他们的判断,这种专家系统很少表现得很好。 机器学习(ML)是一类AI技术,与其捕获人类逻辑不同,ML“模型”显示大量正确的决策,预期该模型将来能够推断如何做出正确的决策。由于收集数据比捕获逻辑更容易,ML能够解决各种问题。然而,数据通常必须是结构化数据,类似于关系数据库中保存的数据。在2010年代中期,一组称为深度学习的技术开始崭露头角。这些技术使用“深度神经网络”,能够理解图像、语音、视频、自然语言文本等非结构化数据。这反过来导致了像Google Photos(即使用自然语言查询搜索图片的能力)或Alexa(即通过自然语言处理进行交互)这样的技术的发展。在企业中,深度学习也使组织能够从非结构化数据(如产品目录和用户评论)中提取信息。为企业用例提供了预构建的生成式AI解决方案,例如内容创作、客户体验、编码协助以及其他工作流助手。 由于AI变得越来越成熟,不再需要在组织中投入大量工程时间来构建AI能力。相反,您可以通过许多可购买或定制的AI解决方案来利用AI的好处。这些解决方案可以分为几类:预测性分析、理解和生成数据、个性化和打包解决方案。预测分析机器学习模型是通过正确决策的示例进行训练的。企业数据仓库(DWH)通常是这类训练示例的重要来源。例如,假设你的业务是购买二手车、维修并销售。你想要创建一个系统来估计在拍卖会上购买的车辆的维修成本。很明显,你企业的历史数据是你购买和整修车辆的实际维修成本的良好来源。换句话说,历史数据中的正确答案存在于你的数据仓库中(见图2-6),可以用来训练机器学习模型。然后,训练好的机器学习模型可以用于预测随后在拍卖会上出现的车辆的维修成本。检测欺诈交易、估计机器故障时间、广告是否会被点击、销售多少商品、客户是否会购买等问题都是预测性分析问题的示例。通过教模型根据已捕获的其他因素预测历史记录中的一个值,可以解决这些问题。了解影响维修成本等事物的因素,将组织中与此估算有关的所有数据纳入数据仓库是成功进行预测性分析的先决条件。构建企业数据仓库后,有许多预建的预测性解决方案可用于创建必要的模型。事实上,像AWS Redshift和Google BigQuery这样的数据仓库提供了在不移出数据仓库的情况下连接到AWS SageMaker和Google Cloud Vertex AI等服务,从而能够训练自定义机器学习模型。理解和生成非结构化数据ML模型也可以经过训练以理解和生成非结构化数据。 例如,自眼底图像中识别眼病、从交通摄像头检测非法左转、从视频中转录文本以及在评论中识别辱骂性语言的问题,都是使用ML模型来解释非结构化数据(图像、视频或自然语言)的例子。深度学习已经彻底改变了对非结构化数据的理解,每一代模型都将错误率降低到产品如Google Home中的问答系统、Gmail中的智能回复以及Google Photos中的图像检索极其准确的程度。与预测性分析不同,很少需要创建和训练模型来理解非结构化数据。相反,可以直接使用像Azure Vision API、Google Video Intelligence API和AWS Comprehend这样的预构建模型。使用这些API来丰富您的数据。例如,即使没有ML知识,开发人员也可以使用Google的NLP API提取评论的情感,并将情感作为附加列添加到您的DWH中。如果这些预构建模型提供的标签不足以满足您的用例怎么办?也许图像识别API返回一个响应,指示图像包含一个螺丝,但您希望API返回一个值,即它是您目录中的Item #BD-342-AC。即使在这种情况下,也无需从头开始训练模型。AutoML模型(即用于问题领域(如图像识别)的标准ML模型,可以使用自定义数据进行训练)如来自Google Cloud、Azure、H2O.ai、DataRobot等的模型可以通过简单地在自己的数据上进行微调来定制。通常,每种类型的示例可能只需十几个。AutoML模型适用于图像、视频和自然语言。使用它们来获取适用于您问题的高质量定制API。除了解释非结构化数据外,AI技术还存在用于生成此类数据的技术。被称为生成式AI(我们将在第10章进一步讨论)的技术可用于生成文本、图像、语音、音乐和视频。在这方面,也存在预构建(“基础”)模型,它们已经能够解决各种各样的问题(称为零样本学习)。这些模型可通过云供应商(Azure OpenAI、Vertex AI)和独立供应商(如OpenAI、Anthropic、Midjourney、Cohere等)的API获得。在许多企业用例中,有必要将数据和ML平台与生成式AI模型集成以将模型“植根”于现实中。例如,可以通过传递一些示例(称为少样本学习)和制定适当的输入(称为提示)来自定义基础生成式AI模型的行为。这些示例可以从数据平台获取。像Hugging Face和LangChain这样的框架提供了特定问题的开源解决方案,比如基于企业文档的问答。这涉及从数据平台通过矢量相似性搜索检索适当的文档,并在ML平台上运行链。有时,这些解决方案在云上以全托管方式提供(例如Google Enterprise Search)。最后,可以针对特定任务微调这些基础模型(称为监督微调),云提供商通过其ML平台提供此功能。个性化与为所有用户提供完全相同的产品相比,机器学习提供了提供更为个性化体验的机会。客户分割、定向和产品推荐等问题属于个性化的范畴。个性化是由客户的历史行为驱动的。例如,如果您是零售商,您可以基于其他人的行为(“购买此商品的用户还购买了...”)、基于用户自己的购买历史或基于商品相似性向用户推荐产品。所有这些信息都存在于您的企业数据仓库中(或至少可以存在)。因此,您可以使用存储引擎来支持推荐引擎。事实上,Google BigQuery ML具有一个推荐模块,Google的推荐 AI 是基于 BigQuery 数据运行的。同样,Azure Machine Learning 中的推荐模块是基于 Azure Synapse 运行的。在本节考虑的这三类 AI 中,很容易在几小时到几天内建立一个快速原型。这些模型都已存在;数据需求不繁重,您可以通过单击或单个 SQL 查询训练机器学习模型。打包解决方案除了在前面讨论的个别机器学习模型和技术之外,始终密切关注解决特定领域问题的打包解决方案,这些解决方案以即插即用的方式提供。例如,通过自然语言进行交互的对话型人工智能能够准确处理常见的例行问题,比如了解商店的营业时间或请求餐厅预订。现在,对话式人工智能能够将建议的答案填充到呼叫中心支持代理的屏幕上,以回答客户的问题。这些解决方案可以轻松集成到企业的电话系统中。像订单到现金、库存管理、货架缺货识别等解决方案已经准备好集成到您的业务中。毫无疑问,在您阅读本文时,将会有更多的打包和即插即用解决方案可用。我们鼓励您通过采用这些打包解决方案,跨越您当前技术堆栈实际可实现的局限性。步骤 6:将以人工智能为驱动的工作流程投入运营您数据平台战略的最后阶段是超越简单的预测和决策,实现端到端工作流程的自动化。您希望能够以完全自动和自治的方式解决业务问题。例如,您不仅希望预测机器何时会下次故障;您还希望在机器故障之前安排维护。实际上,您希望以最佳方式执行此操作,以降低运营成本、延长机器寿命并避免您的维修设施不堪重负。要实现所有这些,首先必须了解您在组织内希望实现的自动化水平(例如,完全自动还是仅提供协助?),然后需要专注于建立数据文化,为实现期望目标构建路径。最后但同样重要的是,需要强化数据科学团队,他们是实现您人工智能愿望的推动力。确定自动化根据我们的经验,许多组织在决策方面采用一次性的方式。如果它们采用数据驱动的方法并投资于使这些数据驱动的决策更加系统化,将会获得可观的回报。这种系统化的自动化是打破信息孤岛、获取数据的综合视图和提供建议的逻辑终点,如图2-7所示。随着企业数据成熟度的提高,领导层需要鼓励企业从一个阶段迈向下一个阶段。为了实现这种日益成熟,高管和员工都需要明确最终目标是什么。是决策的全自动化吗?是AI引导的决策制定吗?换句话说,人类是否完全不参与其中?还是他们在“掌握”整个过程?例如,考虑一下谷歌地图和贵国空中交通管制系统生成导航指令的方式的差异。前者是人类不参与其中的例子,完全是自动化的。在空中交通管制的情况下,系统提供指导,但是是人类最终给予降落飞机最终的导航指令。当然,在这两种情况下,人类都会接收并执行导航指令。自动化与协助的合适平衡通常是组织经过相当多的实验后确定的。它因行业而异,因组织而异。相对混合通常是同一行业中不同企业竞争的方式之一。因此,这通常是由产品管理主导的,而不是工程。建立数据文化组织将需要构建应用程序和系统,以实现最终目标,无论这个目标是全自动化还是为专家提供帮助。构建这样的系统将需要组织灌输一种数据文化。仅仅建立一个分析型数据平台是不够的。同样需要改变组织的文化,使其变成一个以数据驱动的实验为常态,并且成功的实验能够被操作化和扩展的组织。 建立数据文化是解锁创新的关键,因为这将增强组织内的数据素养,传播读取和处理数据所需知识。仅仅因为你建立了一个能够打破数据孤岛的平台并不意味着它们就会消失。仅仅因为决策可以以数据驱动的方式进行并不意味着旧的启发式方法会轻易被抛弃。成功的组织会进行一个转型计划,涉及向整个创意工作人员提供有关数据工具(如商业智能仪表板和嵌入式分析)的培训。他们试图改变员工被奖励的方式,以鼓励承担风险和创业精神。他们试图建立一种衡量业务中所有重要指标的方式。最后,他们致力于为他们的员工提供数据工具,以便他们能够推动变革。填充你的数据科学团队为了使你的组织从数据和人工智能投资中获得价值,你的数据平台需要启用几个角色:数据工程师、数据科学家、机器学习工程师、开发人员和领域专家。除非所有这些群体都在你的数据平台上积极合作,否则你不能说你已经建立了一个未来准备好的数据平台。数据工程师:负责摄取、准备和转换数据。他们使用ETL工具将数据加载到数据仓库(DWH)中。他们监控数据管道,确保管道正常运行且不损坏数据源。数据科学家:进行数据分析,帮助决策者了解业务的运行情况,回答假设问题,建模结果并创建创新模型。产品管理团队是此处的关键决策者。数据科学团队进行分析,帮助产品经理了解产品路线图上不同项目的投资回报率。例如,农业技术公司的数据科学家可能回答有关不同种子每英亩产量及其与土壤类型的关系的问题。他们可能回答假设问题,例如如果商店的产品组合更改为更多的某一种子而不是另一种子,预期的利润是多少。他们还可能对业务策略进行建模,例如创建个性化的收获计划,并将其分解为组件模型并构建它们。机器学习工程师:将整个工作流封装到机器学习(Machine Learning, ML)管道中,并确保以可监视的方式执行。对模型进行常规运行监视,包括对韧性(模型是否正在运行?是否处理所有请求预测的用户?)和准确性(结果是否正确?)的监控。模型随时间漂移。这有时是因为环境本身发生变化(某种新型害虫开始影响某种类型的种子,因此其产量估计不再有效),有时是因为用户适应模型(农民开始根据相应种子品种的价格变化多种大豆而不是玉米,这改变了对某些类型的肥料的需求)。机器学习工程师寻找这种漂移并使用新数据对模型进行重新训练。这被称为MLOps。已部署的模型可作为API提供。开发人员调用模型,并在最终用户使用的应用程序中呈现其结果。领域专家:也被称为业务分析师,利用预测性分析实现数据驱动的决策。数据科学家观察专家的决策,并探讨通过打破阻止这些数据被定期访问的数据孤岛来加速决策的方法。他们使用打包的人工智能解决方案来丰富数据,从而继续数据驱动的创新循环。第7步:数据的产品管理为了最大程度地发挥数据的作用,请应用产品管理原则。几年前,许多高管所说的“将数据视为产品”实际上意味着他们想要直接从数据中获利,比如在数据市场上出售数据。然而,如今这样的市场大多数包含由专门从多个来源聚合数据的公司创建的数据(例如,零售流量、信用卡交易记录、产品评论)。很少有公司成功地从他们的第一方数据中获利。那么,当今一家典型企业希望将数据视为产品时,这意味着什么呢?将产品管理原则应用于数据我们更倾向于将期望的结果与实现这一目标的过程结合起来。期望的结果是,通过将数据视为产品,您的组织将最大限度地发挥数据的作用,而以上定义中突出的特征(有用性、标准化、治理)对此至关重要。我们对数据产品有一个广义的看法,数据集当然属于其中,但数据管道、仪表板、依赖数据的应用程序和机器学习模型也同样在范围之内。期望的结果只有在有达到这一目标的路径时才有价值。为了将数据视为产品,在构思和构建数据产品时应用产品管理原则。什么是产品管理原则呢?(1)制定产品战略,(2)以客户为中心,(3)进行轻量级的产品发现,以及(4)专注于找到市场适应性。我们建议采用与这些原则一致的10个数据实践(参见图2-8)。1.了解并维护企业数据流的映射产品经理的一个关键职责是简化。将数据视为产品意味着数据产品团队维护着业务中数据流的高层模型,以便轻松进行可发现性的沟通。您需要在多个粒度级别维护这个映射。以电商网站为例,最高级别可能是:网站流量产品目录网站内容订单库存客户在更详细的粒度级别,网站流量可能被细分为会话数据、页面数据等。捕捉每个数据集的收集方式、处理方式、哪些角色可以访问以及如何访问、是否包含个人身份信息(PII)或其他属性、所做的质量保证等信息。还要捕捉每个数据集的生产用例。随着粒度从较高级别逐渐降低,映射开始包含数据平台实现的细节,逐渐成为数据目录。2.确定关键指标一个数据目录仅仅是当前存在的记录。它并没有说明数据的重要性,或者数据是否符合目标(除非您利用临时标签来完成)。它不告诉您需要改进的内容。您的数据产品战略的重要部分是在企业内就关键指标达成一致——您将测量什么,如何测量以及指标的目标数值是多少(目标会随时间而变化)。您跟踪的指标范围应包括:业务关键绩效指标 数据需要支持哪些业务结果?SLA(服务水平协议) 数据的可用性如何?数据质量如何?刷新频率如何?使用情况 数据在公司内部被广泛使用的频率是多少?满意度 客户(可能是内部客户)对可用数据及其易用性的满意程度如何?对于前面介绍的假想的电商网站,业务结果可能涉及增加客户生命周期价值,提高免费套餐的转化率等。对于显示给内部采购人员(用于补货)的库存,SLA可能是它在99.99%的时间内可用,每小时刷新一次,并且保持在下周预测销售量的上方。您可能希望库存预测不仅被内部采购使用,还被物流团队使用,并被整合到仪表板中。您可能还有一个衡量预测库存量被多少次覆盖的指标。3.同意的标准、承诺的路线图和有远见的待办事项数据目录是当前存在的记录。度量标准捕捉你的目标是什么。然而,这两者都没有解释接下来的发展方向。根据客户反馈、利益相关方意见和市场状况,随时间调整产品愿景是重要的。在此过程中,利益相关方会要求你提供功能和时间表,并期望你信守承诺。为了应对变化和用户反馈,你需要三样东西:优先级标准: 利益相关方事先同意的优先级标准,这有助于在产品路线图上实现透明度和全组织的认同。产品路线图: 由产品发现过程提供支持的产品路线图,以便团队能够在没有信息和原型的情况下同意时间表。待办事项清单: 认为重要但尚未在路线图上的事项将被记录在产品待办事项清单中。通常,产品待办事项清单包括需要解决的客户问题(而不是需要构建的功能)。在很多方面,待办事项清单(而不是路线图)构成了你的长期产品愿景。组织待办事项清单以讲述一个清晰的故事。路线图需要具有高度的承诺度,你应该能够对路线图上的时间表和功能做出承诺。一个很好的方法是就优先级标准达成共识,进行产品发现,并维护一个产品待办事项清单。回顾我们的一个假设性数据产品(参见前一步)是未来一周的库存预测,我们需要达成一致意见,以衡量预测的好坏。是很少出现缺货吗?是最小化采购和存储物品的成本吗?是在仓库级别发生缺货?还是在公司级别?这些形成了优先级标准。如果有人要求你定制易腐食品的库存模型,是否值得做?你最初会将其添加到产品待办事项清单中。然后,你将进行产品发现,以确定执行这个项目的投资回报率 - 这将包括增加/减少仓库冷藏的成本,例如。只有在了解价值后,你才会将其添加到产品路线图上。4.面向你拥有的客户构建产品往往,数据团队会陷入技术口号的泥淖:他们只提供API,或坚持让每个人将数据发布到他们的企业数据仓库,或期望符合一个统一的词典。向产品管理学习,深入了解你的客户是谁。他们在构建什么?一个移动应用程序还是一个月度报告?他们了解什么?SQL 还是 Java?他们使用什么工具?仪表板还是 TensorFlow?他们是否需要在数据更改时收到警报?他们是否需要实时数据的移动平均值?他们是否关心测试覆盖率?然后,以目标客户能够使用的方式提供数据。例如,你可以将数据提供给数据仓库(供数据分析师使用),通过API使其可访问(供开发人员使用),将其发布到特征存储中(供数据科学家使用),或在仪表板中可用的语义层(供业务用户使用)。例如,我们作为示例使用的假设性库存预测数据产品,如果将被内部采购员(业务用户)利用,那么预测结果将需要在用于订购补给的应用程序中提供。因此,这些预测结果可能需要通过API提供给应用程序开发人员使用。5.不要转嫁变更管理的负担变化和冲突是不可避免的。数据的供应商会更改格式;数据的使用者会有新的需求;数据的速度会发生变化;相同的数据可能会通过多个渠道提供;由于成本原因,您的客户可能会转向另一家供应商。这些问题不仅仅是进行更改的团队或使用数据的团队的问题。将数据视为产品的重要部分是确保数据的用户不必负担变更管理的责任。尽量确保演进模式和服务,使变更对下游用户透明。当不可向后兼容的更改不可避免地发生时,对更改进行版本化,并与利益相关方合作,将它们从旧版本的数据移动到新版本。这可能涉及创建一个迁移团队,其任务是将企业从一个版本迁移到下一个版本。变更管理的原则也适用于安全性。确保为个人身份信息(PII)和合规性建立保护措施,而不是将负担转嫁给数据产品的用户。假设我们的假设性库存预测数据产品被定制以包括易腐食品的预测。如果这涉及请求有关所售物品的额外信息,则必须负责确保所有现有物品的物品目录得到增强。这项数据工程工作是项目范围的一部分,它影响是否值得进行这项工作的投资回报率。6.面试客户以发现他们的数据需求你如何发展产品待办事项、确定需求的优先级并添加到路线图上?一个重要的纪律是确保你不断地与客户交流,发现他们在解决遇到的问题时需要什么样的数据。他们是如何绕过当前数据产品的不足之处的?这些问题会进入你的产品待办事项清单,供你优先考虑和解决。在任何新的数据产品想法进入产品路线图之前,重要的是已经通过潜在的(内部或外部)客户对产品的需求进行了验证。按照规格构建("构建它,他们会来")是非常危险的。更安全的做法是构建已经通过与客户验证的想法的实现。你如何做到这一点呢?7.广泛使用白板和原型制作与需要该数据产品的客户一起使用白板设计。这确保了在数据平台上实现的内容能够满足他们在质量、完整性、延迟等方面的需求。在构建任何数据流或转换之前,与他们一起讨论数据的潜在用途。在这里,其中一个最好的工具是原型。通过构建一个最小可行原型,可以验证数据的许多用例。我们是什么意思呢?如果销售团队认为构建一个客户数据平台将帮助他们实现产品交叉销售,可以通过手动匹配个别产品销售渠道的一组记录,尝试交叉销售生成的客户来验证这一点。我们建议使用原型以及与最终产品的潜在用户进行的面试来界定问题,包括:需要构建什么:确定项目成功所需的一切,从数据流到用户界面预期的业务关键绩效指标(KPI)方面的预期投资回报率在编写任何代码之前执行此操作。只有当你清楚知道需要构建什么以及预期的投资回报率时,你才应该将项目添加到路线图上。在那之前,将问题保留在待办事项清单中。对于我们假设的库存预测数据产品的情况,您将与主要产品用户一起验证输入模式以及如何使用预测,并检查仓库可以容纳多少更多的冷藏设施等等。在编写任何代码之前,您将执行此操作,可能通过在电子表格中进行预测并对各种产品的整套情景进行游戏化。8.只构建立即使用的内容优先快速投产而不是构建所有必要的功能。这意味着你应该使用敏捷、迭代的过程,仅构建当前需要的数据集、数据管道、分析等。不要专注于开发太多没有显著影响的功能:你付出的努力可能得不偿失。使用产品待办事项清单记录未来需求。只有在确定将使用这些功能的客户并能在白板/原型会话中给出反馈后,才构建这些功能。9.规范化常见实体和关键绩效指标(KPIs)为业务中的常见实体和关键绩效指标(KPIs)提供规范化、丰富的数据集。通常,这些丰富的实体支持大量高回报投资的用例(例如,客户数据平台、内容管理平台),或者是出于监管/合规目的所需的(例如,计算税收的方式)。通常,你只会有少数几个这样的标准化数据集和指标,因为这样的丰富需要跨业务单位的广泛协作,并降低了它们的发布速度。10.在你的数据平台中提供自助服务功能在组织中找到适合的方式来平衡灵活性和标准化。不要过分追求前一步(标准化)。不要构建拥有任何人可能需要的所有内容的中心化数据集。相反,要使团队能够自给自足。这是将微服务原则应用于数据的方法。实现这种平衡的一种方式是提供小型的、自包含的数据集,客户可以通过与其他数据集以特定领域的方式连接来进行定制。通常,这被实现为数据网格,每个业务单元负责其发布到共享分析中心的数据集的质量。总结在本章中,您了解了组织应该采取的创新数据过程的更多信息。本章的主要要点如下:通过确定关键业务目标、聚集合适的利益相关方并在整个企业中建立变革管理,创建战略计划。确保您的利益相关方集合中包括那些如果采用数据平台将最受益的业务单元。通过转向云来降低总体拥有成本。在这样做时,不要试图复制您在本地所做的技术选择。相反,选择能够自动缩放、分离计算和存储、支持 NoOps 并允许您支持各种应用程序而无需数据移动的技术。打破数据孤岛,以便将整个组织的数据连接在一起。确保每个部门都控制其数据并对其质量负责是很重要的。然而,所有数据都应该在统一平台上可用,以实现跨组织访问。通过将数据实时流入数据仓库并确保数据访问始终反映最新数据,以更快地在上下文中做出决策。在相关的情况下,数据表应该包括上下文元数据,如位置信息。利用可定制和适应性的 AI 模型,这样您就不必构建定制的 AI 模型。这些模型有助于无论您的需求是预测分析、理解非结构化数据还是个性化,都可以从您的数据平台推动。预测分析和个性化可以由数据平台驱动。非结构化数据可以通过使用预建的 AI 模型处理它并将其添加到数据平台中。将重点从一次性 ML 模型扩展到自动化整个工作流。这一步通常由产品管理领导,此处的分析通常会为产品路线图提供信息。通过聘用和配备由数据工程师、数据科学家、ML 工程师、开发人员和业务分析师组成的团队,建立创新文化。使用产品管理原则制定您的数据产品战略:以客户为中心,通过白板和原型发现产品,并在标准化和灵活性之间找到正确的平衡。在本章中,我们讨论了如何构建创新的数据组织的策略。在下一章中,我们将关注设计数据分析平台时必须考虑的关键方面:现有员工目前具备或将需要掌握的技能。
0
0
0
浏览量2061
攻城狮无远

第三章:设计您的数据团队

在设计数据平台时,有几个技术方面需要考虑:性能、成本、运营开销、运营卓越、整合新的分析和机器学习方法等。然而,如果不解决公司文化的问题,这些技术方面将无法发挥作用——采用新技术需要员工愿意改变他们的思维模式和工作方式。另一个需要记住的关键方面是现有员工目前具备的技能以及他们需要掌握的技能。在某些情况下,学习新技能并改变工作方式的员工最终可能会进入与数据平台实施之前不同的角色。在本章中,我们探讨了组织如何规划和协调这些思维模式、工作流程、技术技能和角色的变化。每个组织都是独特的,因此构建数据平台将涉及为每个部门和其中的员工制定详细的计划。在本章中,我们描述了不同类型组织的这种详细计划会是什么样子。分类数据处理组织组织可以通过采用不同的策略基于其人才而取得成功。没有普遍适用的“最佳”方法。一支防守强大的体育队伍应该发挥他们的优势,专注于防守,而不是试图复制一支拥有技术出色进攻球员的球队的进攻。同样,如果你的组织拥有一支强大的数据分析团队,它应该专注于其人员,而不是试图转变为一个充满数据工程师的组织。根据你的员工技能和用例的复杂性,为你的组织决定最佳策略。你是否需要一小组数据工程师,他们能力强但成本高?还是应该利用大型且已经存在的数据分析员团队来丰富和转换可行动的数据?这些工作者需要多少领域知识?培训现有工作人员执行更高价值的工作是否切实可行?还是应该投资于生成式AI或无代码工具,并使这些基础技术的重要部分对更大范围的工作人员可用?最佳技术方法在组织内也会有所不同 - 销售团队和工厂车间之间的工作人员构成将有所不同。因此,详细的计划涉及为每个业务单元详细说明最佳的技术方法。从技术上讲,该计划还将在基于标准ETL的方法(需要ETL工具的硬技能)和基于ELT的现代方法之间做出选择(需要更通用的SQL技能)。考虑图3-1中勾画的传统用户画像价值链。你可以看到组织中的每个数据用户都拥有一组小而专业的技术技能。如果一个组织希望扩大其数据分析团队的范围,它还必须扩大其数据工程和数据科学团队的规模,以确保有足够的人具备正确的技术技能来支持数据分析员。公共云提供的新范式为数据处理、数据分析和算法开发提供了新的可能性。云技术现在使得新的工作方式成为可能 - 它们允许分析师执行曾由数据工程师管理的批处理或实时数据处理任务,同时还允许他们尝试使用现成的数据科学解决方案。此外,给定的任何用户画像的潜在范围 - 他们拥有的技能和他们负责的职责 - 已经增加。随着数据技术变得更加可访问,数据工作者能够承担新的任务,并在没有传统用户画像相关瓶颈的情况下处理数据价值链。这导致技能在角色之间的融合,使现有团队更容易扩展到其他职责。专注于使用SQL代码通过ELT方法解决问题的数据分析员和更倾向于使用ETL方法和通用代码(例如Python、Java、Scala等)的数据工程师/数据科学家之间的区别变得不那么明显。混合方法(如图3-2所示)变得越来越普遍,因为它们可以充分利用ETL和ELT模式的优势。我们今天看到的大多数组织都属于这种混合模型,尽管角色的平衡以及数据处理必须通过ETL或ELT的程度取决于组织的类型,存在一些变化。以数据分析为驱动的组织以数据分析为驱动的组织是在决策制定中数据分析师发挥核心作用的组织。值得注意的是,组织是否以分析师为主导不是一个非黑即白的问题,而是一系列重叠特征的谱系:成熟行业: 这些组织是知名且经过时间验证的企业,拥有完善(也许是过时的)的系统。它们所处的行业通常是成熟且稳定的。主要工作涉及对各种产品或情况进行人工分析。数据分析为驱动的组织的典型例子包括零售商的商品单位(例如沃尔玛)和大型银行的商业贷款处理部门(例如摩根大通)。企业数据仓库(EDW)和批处理ETL: 在技术层面上,中心信息枢纽是一个随时间建立起来的EDW,具有较高水平的技术债务和传统技术。在数据仓库内部的数据转换是通过定期的ETL过程进行的,比如夜间批处理。这种批处理过程增加了数据提供的延迟。商业智能: 组织中的大多数数据专业人员习惯于通过对集中式数据仓库运行SQL查询、使用BI工具创建报告和仪表板,并使用电子表格来访问类似数据来回答业务问题。因此,内部人才储备最擅长于SQL、BI工具和电子表格。请注意,即使在零售和金融等成熟行业中,新兴的数字组织(电子商务和金融科技)可能会寻求捕捉增长最快的数字领域和具有最大潜力的客户细分市场。这样的数字原住民可能与传统大公司(例如Etsy与沃尔玛;Paytm与摩根大通)有不同的员工组成;我们不会将数字原住民与传统公司放在同一类别中。现在,您对我们所说的以分析为驱动的组织有了更清晰的了解,让我们讨论进行转型的主要手段。简单来说,数据工作者现在能够更高效地处理其拥有的数据,并能够以更高效的方式执行更多任务。这是因为技术变得更加易于访问,而且数据工作者能够将他们的技能与其他数据专业人员的技能相融合。这导致了一种更高效和有效的数据处理方式。组织可以广泛分为三种类型:以数据分析为驱动、以数据工程为驱动和以数据科学为驱动。在接下来的部分中,我们将介绍为每种类型构建数据处理组织的理想方式。在现实中,公司将包括属于这些类别的不同部门或业务单位,因此它们将发现自己应用所有这些策略。一些团队将是角色的组合,因此转型将涉及混合方法。在考虑将成为数据团队一部分的不同类型用户时,请记住在第二章学到的内容:“为了最大程度地发挥从数据中获得的优势,请应用产品管理原则。”您应始终使用产品管理原则来制定数据产品策略,以客户为中心,通过白板和原型发现产品,并在标准化和灵活性之间找到正确的平衡。愿景为在以分析驱动为特征的组织中普及云数据平台的使用,分析师应该能够通过熟悉的界面,如电子表格、SQL 和商业智能工具进行高级分析。这意味着提供易于使用的工具,将数据引入目标系统,并与分析和可视化工具实现无缝连接。这在传统企业数据仓库中曾是常见做法。使用 SQL 对数据进行丰富、转换和清理,使用 ETL 工具进行编排。同样,物化视图和用户定义函数可用于在现代数据仓库中丰富、转换和清理数据。然而,这假设分析师已经能够访问所有数据源。创建复杂的摄取管道曾经是昂贵且常常繁琐的。因此,由于资源和成本的限制,数据管道是在数据仓库之外管理的。这在使用新的云数据仓库时已不再成立。摄取的角色现在仅仅是将数据带到云附近,而转换和处理部分则移回了云数据仓库。这导致数据被暂存在存储桶或消息系统中,然后被摄取到云数据仓库中。所有这些不仅减少了使数据可用所需的时间,还使数据分析师能够专注于使用他们习惯的工具和界面寻找数据洞见。因此,在新世界中,ELT 应该取代 ETL 工具——数据分析师可以使用 SQL 编排工具,如 dbt 或 Dataform,串联 SQL 语句来进行 ELT。直接从源或暂存区摄取数据使分析师能够利用他们的关键 SQL 技能,并增加他们接收数据的及时性。他们无需等待繁忙的数据工程团队实施 ETL 管道。总之,扩展云数据平台的使用最佳方法是为分析师提供易于使用的工具(如 dbt)和界面(如 Excel 或 Tableau),以便他们可以轻松掌握。这将使他们能够进行高级分析,而无需等待数据工程团队实施复杂的 ETL 管道。一旦数据在云数据仓库中可用,就是开始分析的时候了。过去,许多数据分析是使用电子表格完成的,但电子表格通常难以处理新世界中需要分析的大量数据。尽管 Google Sheets 和 Excel 具有连接到数据仓库的实时功能,但仍然有些繁琐。我们建议为分析师提供使用现代商业智能工具创建可处理大型数据集的可视化和报告的权限(例如 Power BI、Tableau 或 Looker)。人物角色分析驱动型组织数据部门中的主要角色包括数据分析师、业务分析师和数据工程师。数据分析师数据分析师接收、理解并满足业务的请求,理解相关数据。数据分析师的目标是满足组织的信息需求。他们负责数据的逻辑设计和维护。他们的一些任务可能包括创建符合业务流程的表格布局和设计,以及重新组织和转换数据源。此外,他们负责生成能够有效传达业务请求的趋势、模式或预测的报告和见解。为了构建分析驱动型组织的任务,有必要通过两种方式扩展数据分析师社区的经验和技能集。首先,推动数据分析师学习业务的趋势是至关重要的。数据分析师需要深入了解业务领域。其次,数据分析师需要获得分析和呈现数据的技术技能,无论数据的量或大小。幸运的是,现在可以使用 SQL 和 BI 工具来实现这一点。数据分析师技能的扩展,既包括业务领域,也包括大数据,如图 3-3 所示。业务分析师业务分析师是领域专家,利用数据进行分析洞察。基于云的数据仓库和无服务器技术已经将业务分析师的责任扩展到传统领域专家的领域。这是因为分析师现在可以专注于通过分析数据为业务增值,而不是浪费时间在管理和技术管理任务上。此外,存储在数据仓库中的数据的数量和类型不再是限制因素,因此分析师现在可以更深入地了解业务以获取洞察。数据仓库可以同时充当数据的着陆区域和结构化及半结构化数据的记录系统。这意味着业务分析师可以在一个地方获得所有需要分析的数据。总体而言,基于云的数据仓库和无服务器技术使业务分析师能够更加高效,为业务增加更多价值。 尽管业务分析师可以使用无代码和低代码的机器学习模型,但他们在涉及机器学习或自然语言文本的更复杂工作流方面可能会遇到困难。他们还没有实施复杂数据科学算法的技能,比如排名或推荐。因此,如果您的激活需求更为复杂,仍然需要一个数据科学团队。数据工程师数据工程师专注于下游数据管道和数据转换的前几阶段,如加载和集成新数据源。他们还负责管理数据治理和数据质量流程。在以分析为驱动的组织中,数据工程师的数量将较少,因为数据分析团队基本上可以自给自足,能够构建简单的数据管道和机器学习模型。分析驱动的组织倡导ELT的概念,而不是传统的ETL。主要区别在于常见的数据处理任务是在数据加载到数据仓库后处理的。ELT广泛使用SQL逻辑来增强、清理、规范化、精炼和集成数据,使其准备好进行分析。这种方法有几个好处:它减少了行动时间,数据立即加载,并且可以同时提供给多个用户。因此,这样的组织的变更管理策略必须专注于诸如SQL、视图、函数、调度等方面。即使在分析驱动的组织中,数据工程团队通常仍然掌控从源系统提取数据的过程。尽管可以通过使用基于SQL的工具简化此过程,使数据分析师能够执行其中的一些工作,但仍然需要一个强大的数据工程团队。仍然有一些批处理作业需要创建数据管道,更适合使用ETL。例如,从主机到数据仓库传输数据需要额外的处理步骤:数据类型需要映射,COBOL书籍需要转换等。此外,对于实时分析等用例,数据工程团队将配置流数据源,如Pub/Sub、Kafka主题或Kinesis数据流。处理通用任务的方式仍然相同——它们可以被编写为通用的ETL管道,然后由分析师重新配置。例如,将来自各种源数据集的数据质量验证检查应用于目标环境将遵循由数据工程师设置的模板。技术框架有关面向分析驱动组织的高层参考架构,有三个基本原则:SQL 作为标准 技术应该根据当前的组织文化进行定制。应优先考虑提供 SQL 接口的组件,无论它们在数据处理流程的哪个阶段。从企业数据仓库(EDW)/数据湖到结构化数据湖 信息系统基础设施及其数据应进行集成,以扩展对新的和多样化数据源进行分析处理的可能性。这可能涉及将传统的数据仓库与数据湖合并,以消除信息孤岛(有关 lakehouse 架构的详细信息,请参阅第 7 章)。先读取后模式的模式 由于存储成本较低,组织在收到数据之前不再需要对数据结构施加严格的规则。从写入模式(schema-on-write)转向读取模式(schema-on-read)允许对数据进行实时访问。数据可以保留其原始形式,然后转换为最有用的模式。此外,数据平台可以管理保持这些副本同步的过程(例如,使用物化视图、变更数据捕获等)。因此,请不要害怕保留相同数据资产的多个副本。将这些原则结合起来,我们可以定义一个高层架构,如图 3-4 所示。这个信息架构满足上述三个原则,并支持一些关键的数据分析模式:“传统”商业智能工作负载,如创建仪表板或报告允许通过 SQL 进行数据管道管理的自发分析界面(ELT)使用机器学习技术实现数据科学用例将数据实时流入数据仓库并处理实时事件这两种模式与传统的 SQL 数据仓库世界相当相似,但后两种则以 SQL 抽象的形式呈现更高级的分析模式。在 ML 领域,Redshift ML 或 BigQuery ML 允许数据分析师使用标准 SQL 查询在存储在数据仓库中的数据上执行 ML 模型。SQL 流扩展,如 Dataflow 和 KSQL,使得能够聚合具有无限制、实时源(如 Pub/Sub 或 Kafka)的数据流成为可能。即使无需投资于新的配置文件和/或角色,这项技术也能开启许多可能性。在为面向分析驱动组织选择 ELT 和 ETL 时,数据准备和转换是关键考虑因素。应尽可能使用 ELT,因为它允许使用 SQL 在结构化数据湖中对数据进行转换。这种方法提供了与广泛的数据集成套件相同的功能,但无需牺牲数据质量或运营监控。诸如 dbt 之类的产品将软件工程方法引入数据建模和构建数据工作流程中;dbt 实际上允许构建类似于由代码构建的 ETL 工作流的 ELT 工作流,但是使用的是 SQL。这使得数据分析师(不是系统程序员)能够进行可靠而复杂的数据转换。简而言之,对于面向分析驱动组织,ELT是比ETL更好的选择,因为它允许更灵活和更可控的数据转换。此外,dbt提供了数据建模和构建数据工作流的软件工程方法,有助于提高数据转换的可靠性和复杂性。数据工程驱动型组织一个以工程为驱动的组织专注于数据集成。有些公司(例如金融科技公司Plaid)的业务是为整个行业构建集成管道。更常见的情况是这是一个大型企业中的一个团队。例如,一家投资公司可能有一个团队的工作是从许多供应商那里获取、重新格式化并摄取金融数据(例如股市数据、公司的SEC文件等)和另类数据(例如信用卡支出、电子收据、零售客流等)。愿景当您的数据转换需求变得复杂时,您需要数据工程师在公司的数据战略中发挥核心作用,以确保您可以成本效益地构建可靠的系统。数据工程师位于数据所有者和数据消费者之间的交叉点。业务数据所有者是业务团队与数据架构的联系人,了解业务并为数据架构提供数据。数据消费者专注于从架构中提取不同数据的洞察力。在这里,通常会找到数据科学团队、数据分析团队、商业智能团队等。这些团队有时会将来自不同业务单位的数据结合起来,并生成(机器学习模型、交互式仪表板、报告等)工件。在部署方面,他们需要数据工程团队的帮助,以确保数据是一致且可信的。数据工程师有以下责任:在构建分析系统和操作系统之间的集成时,传输数据并丰富数据(如在实时用例中)。解析和转换来自业务单位和外部供应商的混乱数据,使其成为有意义且干净的数据,并具有详细的元数据。应用数据运营(DataOps),即将业务的功能知识与应用于数据生命周期的软件工程方法相结合。这包括监视和维护数据源。部署机器学习模型和其他数据科学工件,以分析或使用数据。 构建复杂的数据工程流水线是昂贵的,但可以提高能力:丰富 数据工程师创建了从不同来源收集数据并将其合并以创建更有价值数据集的过程。然后可以使用这些数据做出更好的决策。训练数据集 机器学习模型的质量很大程度上取决于用于训练这些模型的数据。通过从多个来源引入数据并统一它们以准备用于训练机器学习模型的数据集,数据工程师可以提高数据科学团队的生产力。非结构化数据 当机器学习模型需要使用非结构化数据(例如评论文本或图像)时,存在特殊的挑战。这些数据通常不遵循传统DWH将使用的严格模式。此外,它需要清除个人身份信息(例如聊天记录中的电话号码)和不适当的内容(尤其是图像),并进行转换(例如使用嵌入)。产品化 还需要数据工程工作来将临时数据科学工作产品化并从中获得价值。没有数据工程,数据科学家将被困在进行实验和为特定用例制作应用的困境中,但这些应用很少被产品化或概括。实时分析 应用程序,如异常检测和欺诈预防,需要立即做出响应。在这种用例中,数据消费者需要在数据即时到达时即时处理信息。他们必须以低延迟进行操作。这种实时分析需要在目标DWH之外进行转换。通常所有这些都需要定制应用程序或最先进的工具。实际上,很少有组织的工程能力卓越到可以真正称之为工程组织。许多组织属于我们称之为混合组织的范畴(见图3-2)。人物角色数据工程师开发并优化所有获取数据所需的过程,以及执行相关分析和生成报告所需的解决方案。知识数据工程角色需要深入了解数据库和编程语言,同时具备在部门间合作所需的一定业务技能。无论他们在哪个规模的组织中工作,数据工程师都需要具备一些标准的技能才能取得成功。最有价值的知识包括:数据仓库和数据湖解决方案 Cloudera Data Platform、Oracle Exadata、Amazon RedShift、Azure Synapse、Google BigQuery、Snowflake、Databricks、Hadoop生态系统等,数据工程师可以处理海量数据。数据库系统 SQL 和 NoSQL 数据库系统。他们应该知道如何在关系数据库管理系统(RDBMS)中进行信息存储和检索。ETL 工具 传统工具如 Informatica 和 Ab Initio 以及现代框架如 Apache Beam、Spark 和 Kafka。编程语言 Python、Java、Scala。数据结构和算法 数据结构和算法允许组织和存储数据以便轻松访问和操作,这对于数据工程师是一项必不可少的技能。自动化和脚本编写 数据工程师应该能够编写脚本以自动化重复性任务,因为他们必须处理大量数据(例如 bash、PowerShell、HashiCorp Terraform、Ansible 等)。容器技术 将数据项目推向生产的事实标准。在本地机器上开发的项目可以“交付”到一个暂存和生产集群(通常)而无需问题。数据流水线和机器学习模型是可复制的,并且可以在任何地方以相同的方式运行。编排平台 由于需要集成执行数据工程任务的各种解决方案和工具的增多,编排工具如 Apache Airflow 已成为必不可少的。认证衡量了数据工程师的知识和熟练程度,确认个体具备必要的专业知识以为企业的数据战略做出贡献。例如 AWS Certified Data Analytics、Cloudera Certified Professional (CCP) Data Engineer、Google Professional Data Engineer 和 Microsoft Certified: Azure Data Engineer Associate。责任数据工程师负责确保不同业务部门生成和需要的数据被摄入到架构中。这项工作需要两种不同的技能:业务功能知识和数据工程/软件开发技能。这种技能组合通常被称为 DataOps(它起源于过去几十年中在软件开发领域发展起来的 DevOps 方法,但应用于数据工程实践)。数据工程师还有另一个责任。他们必须帮助部署数据使用者生成的工件。通常,数据使用者没有足够的深度技术技能和知识来独自负责其工件的部署。对于非常复杂的数据科学团队也是如此。因此,数据工程师必须掌握其他技能:机器学习和商业智能平台知识。让我们澄清一下:我们不希望数据工程师变成机器学习工程师。机器学习工程是将数据科学家开发的机器学习模型部署到实时生产系统的过程。另一方面,数据工程师构建的基础设施是供他人用于处理数据的,比如数据存储和数据传输。数据工程师需要了解机器学习,以确保提供给模型的第一层数据(输入)是正确的。在推断路径中交付数据的第一层,数据工程技能在规模、高可用性等方面确实需要发挥作用。通过负责解析和转换来自各个业务部门的混乱数据,或者实时摄取,数据工程师使数据使用者能够专注于创造价值。数据科学家和其他类型的数据使用者可以抽象出数据编码、大文件、旧系统和复杂的消息队列配置(用于流处理)的细节。将这种知识集中在一个高技能的数据工程团队中的好处是显而易见的,尽管其他团队(业务部门和使用者)也可能有自己的数据工程师,作为与其他团队进行接口的角色。最近,我们甚至看到了由业务部门成员(数据产品负责人)、数据工程师、数据科学家和其他角色组成的小组。这实际上创建了完整的团队,他们在数据流上拥有自治权,并对从传入数据到影响业务的数据驱动决策的整个过程负有全部责任。技术框架数据工程团队已经需要广泛的技能。不要让他们的工作变得更加困难,期望他们还要维护运行数据管道的基础设施。数据工程师应该专注于如何清理、转换、丰富和准备数据,而不是关注他们的解决方案可能需要多少内存或多少核心。参考架构摄取层由 Kinesis、Event Hubs 或 Pub/Sub 用于实时数据,以及 S3、Azure Data Lake 或 Cloud Storage 用于批处理数据组成(参见图 3-5、3-6 和 3-7)。它不需要任何预分配的基础设施。所有这些解决方案都可用于各种情况,因为它们可以自动扩展以满足输入工作负载的需求。一旦数据被收集,我们提出的架构遵循传统的提取、转换和加载(ETL)的三步过程。对于某些类型的文件,直接加载到 Redshift、Synapse 或 BigQuery(使用 ELT 方法)也是可行的。在转换层,我们主要推荐使用 Apache Beam 作为数据处理组件,因为它具有统一的批处理和流处理模型。Apache Beam 的运行器包括 GCP 上的 Cloud Dataflow,以及其他超大规模云提供商上的任何托管 Flink 或 Spark 实现。在此架构中,Dataflow 的替代方案是使用基于 Spark 的解决方案,例如 Amazon EMR、Databricks、Azure HDInsight 或 Google Dataproc。然而,这些解决方案不是无服务器的,而且运行空闲的 Spark 集群是一项主要的成本。话虽如此,也有作为 AWS SageMaker/Glue 的一部分以及作为 GCP Serverless Spark 服务提供的无服务器 Spark 替代方案。主要用例是那些已经在 Spark 或 Hadoop 中拥有大量代码的团队。这些 Spark 解决方案使得可以直接将数据管道迁移到云端,而无需审核所有这些管道。数据处理的另一种选择是使用无代码环境来创建数据管道,使用拖放界面,例如由 AWS Glue、Azure Data Factory 或 GCP Data Fusion 提供的界面。传统的 ETL 工具,如 Informatica、Ab Initio 和 Talend,也可以在云中以无服务器模式运行,并在底层使用 Kubernetes 集群。其中一些工具使用 Hadoop/Spark 解决方案或类似的专有计算引擎,在这种情况下,我们之前提到的所有内容也适用于 ETL 工具的情况。如果您的团队更喜欢在不编写任何代码的情况下创建数据管道,则图形化 ETL 工具是正确的选择。参考架构的优势图3-5、3-6和3-7中呈现的参考架构基于对无服务器NoOps技术和流水线的偏好。 通过使用无服务器技术,您可以将维护负担从数据工程团队中剔除,并为执行复杂和/或大型作业提供必要的灵活性和可伸缩性。例如,在零售商在黑色星期五期间规划交通高峰时,可伸缩性是至关重要的。使用无服务器解决方案使零售商能够查看他们在一天中的表现。他们不再需要担心在白天生成的大量数据的处理所需资源。参考架构为数据工程团队提供了编写用于数据流水线的完全定制代码的可能性。这可能是因为解析要求可能很复杂,而没有现成的解决方案可行。在流处理中,团队可能希望以低延迟实现复杂的业务逻辑。然而,团队应尽力通过创建可重用的库和使用诸如Dataflow模板等技术来重用代码。这将两者的优势结合在一起(重用和重写),同时节省宝贵的时间,可以用于高影响代码而不是常见的I/O任务。所呈现的参考架构还具有另一个重要特性:将现有的批处理流水线转换为流式处理的可能性。数据科学驱动型的组织以数据科学为驱动的组织是一种通过充分利用可用数据创造可持续竞争优势的实体。为此,该组织依赖自动化算法,通常(但并非总是!)采用机器学习(ML)。与以分析为驱动的组织依赖一次性报告和分析不同,以科学为驱动的组织试图以自动化方式做出决策。例如,一个以数据分析为驱动的银行可能会让数据分析师评估每个商业贷款机会,建立投资案例,并由高管签署。另一方面,以数据科学为驱动的金融科技公司可能会构建一个贷款批准系统,使用某种自动化算法对大多数贷款做出决策。愿景以科学为驱动的组织是一种从数据中提取最大价值,并利用机器学习和分析获得可持续竞争优势的实体。在构建这样一个数据科学团队时,应遵循一些原则:适应性: 平台必须足够灵活,以适应所有类型的用户。例如,一些数据科学家/分析师更倾向于创建自己的模型,而其他人可能更喜欢使用无代码解决方案或在SQL中进行分析。这还包括提供各种ML和数据科学工具,如TensorFlow、R、PyTorch、Beam或Spark。该平台还应足够开放,能够在多云和本地环境中运行,同时在可能的情况下支持开源技术,以避免锁定效应。最后,资源不应成为瓶颈,因为平台必须能够迅速扩展以满足组织的需求。标准化: 通过使平台更容易共享代码和技术工件,标准化提高了平台的效率。这提高了团队之间的沟通,并提升了他们的性能和创造力。标准化还使数据科学和ML团队能够以模块化的方式工作,这对于高效开发至关重要。标准化可以通过使用标准连接器连接到源/目标系统来实现。这避免了在ML和数据科学工作流中常见的“技术债务”。责任: 数据科学和ML用例通常涉及诸如欺诈检测、医学成像或风险计算等敏感主题。因此,数据科学和ML平台帮助使这些工作流程尽可能透明、可解释和安全至关重要。透明度与运营卓越性相关。在数据科学和ML工作流的所有阶段收集和监控元数据是至关重要的,以创建一个“纸迹”,让您能够提出诸如: 用于训练模型的哪些数据? 使用了哪些超参数? 模型在生产中的表现如何? 在最近的时期内是否发生任何形式的数据漂移或模型偏移? 此外,以科学为驱动的组织必须深入了解其模型。虽然对于传统的统计方法来说这不是问题,但是ML模型(如深度神经网络)更加不透明。平台必须提供简单的工具来分析这些模型,以便自信地使用它们。最后,成熟的数据科学平台必须提供所有的安全措施,以在精细的层次上保护数据和工件的使用情况。业务影响: 根据麦肯锡的说法,许多数据科学项目未能超越试点或概念验证阶段。因此,更重要的是预期或测量新举措的业务影响并选择回报率,而不是追逐最新的炫酷解决方案。因此,要识别何时购买、构建或定制ML模型,并将它们连接到单一集成堆栈是至关重要的。例如,通过调用API使用现成的解决方案而不是经过数月开发后构建模型,有助于实现更高的回报率并展示更大的价值。激活: 将模型嵌入到最终用户使用的工具中的操作化模型的能力对于实现向广泛用户提供服务的规模至关重要。将小批量数据发送到服务,并在响应中返回预测的能力,使具有较少数据科学专业知识的开发人员能够使用模型。此外,促使灵活API的边缘推断和自动化流程的无缝部署和监控也很重要。这使您能够在私有和公共云基础架构、本地数据中心和边缘设备之间分发AI。在构建以科学为驱动的组织时,会面临一些社会技术挑战。通常组织的基础设施不够灵活,无法适应快速变化的技术环境。平台还需要提供足够的标准化,以促进团队之间的沟通,并建立技术“通用语言”。这对于允许团队之间的模块化工作流程并建立运营卓越性至关重要。此外,安全地监控复杂的数据科学和ML工作流通常过于不透明。 以科学为驱动的组织应建立在技术开放度极高的平台上。因此,使广泛的人物能够在灵活且无服务器的方式中提供技术资源至关重要。购买还是构建解决方案是实现组织回报率的关键驱动因素之一,这将定义任何AI解决方案将产生的业务影响。同时,使大量用户能够激活更多的用例。最后,平台需要提供工具和资源,使数据科学和ML工作流具有开放性、解释性和安全性,以提供最大形式的责任。人物角色以数据科学为驱动的组织中的团队由拥有不同技能和经验的各种人员组成。然而,大多数团队包括四个核心角色:数据工程师、ML工程师、数据科学家和分析师。值得注意的是,这些角色并不总是清晰定义的,可能在一定程度上存在重叠。有效的组织结构将允许团队成员之间的协作,并充分利用所有团队成员的技能:数据工程师: 数据工程师负责开发数据管道,确保数据符合所有质量标准。这包括清理、合并和丰富来自多个来源的数据,将其转化为可用于下游分析的信息。ML工程师: ML工程师创建和监督完整的ML模型。虽然ML工程师是这四种角色中最稀缺的,但一旦组织打算在生产中运行关键业务工作流程时,它们就变得至关重要。数据科学家: 数据科学家是数据和ML工程师之间的桥梁。与业务利益相关者一起,他们将业务需求转化为可测试的假设,确保从ML工作负载中获得价值,并创建报告以展示数据的价值。数据分析师: 数据分析师提供业务洞察,并确保实施业务正在寻求的数据驱动解决方案。他们回答临时问题,并提供定期报告,分析历史和最新数据。关于公司是否应该建立集中式还是分散式的数据科学团队存在各种观点。还有一些混合模型,例如将数据科学家嵌入到集中式组织中的联邦组织。因此,更重要的是专注于如何使用前述原则来解决这些社会技术挑战。技术框架我们强烈建议在ML管道基础设施方面采用您的公有云提供商的标准化解决方案(即在Google Cloud上的Vertex AI,AWS上的SageMaker,或Azure上的Azure Machine Learning),而不是拼凑一些一次性的培训和部署解决方案。参考架构(参见图3-8)包括一个ML管道,用于自动化实验和训练。训练好的模型在一个管道中部署,其中通过容器化重复执行许多训练步骤。在特征需要按需计算成本过高或必须在服务器端注入时,请使用特征存储。将模型部署到端点。总结在本章中,您已经了解了设计数据团队以确保其在您所在的组织中取得成功的不同方式。最佳方法是找到一种合适的技能和经验组合,以补充您现有的团队并帮助您实现业务目标。主要要点如下:云技术有助于获取新的工作方式。任何给定角色的潜在面积已经扩大。数据工作者现在能够更高效地利用他们拥有的数据,做更多的事情。无论您的组织主要由数据分析师、数据工程师还是数据科学家组成,都可以建立数据文化。然而,通向数据文化的路径以及每种组织类型所需的技术是不同的。确定哪种组织分类对您来说是正确的,然后开始建立支持其愿景、相关技能和技术框架的人物角色。数据平台的愿景也因情况而异。在以分析为驱动的组织中,它应该使对数据的访问变得更加民主。在以工程为驱动的组织中,重点是以经济高效的方式确保可靠性。在以科学为驱动的组织中,它应该通过业务影响力提供竞争优势。以以分析为驱动的组织为例,应专注于SQL技能;以以工程为驱动的组织为例,应专注于ETL和DataOps能力;以以科学为驱动的组织为例,应专注于MLOps和AI能力。以以分析为驱动的组织为例,推荐的架构是基于数据仓库(或在数据仓库基础上建立的数据湖);以以工程为驱动的组织为例,推荐的架构是基于数据湖(或在数据湖基础上建立的数据湖);以以科学为驱动的组织为例,推荐的架构是与数据湖相连接的ML管道架构。这将在第5章至第7章中介绍。
0
0
0
浏览量2035
攻城狮无远

第五章:架构设计数据湖

数据湖是数据平台的一部分,从组织中捕获未经管理的原始数据,并支持Apache生态系统中的计算工具。在本章中,我们将更详细地讨论这一概念,这在设计现代数据平台时非常重要。正如您将在整章中了解到的那样,云计算可以提供对其上可以实施的不同用例的支持。我们将从回顾为什么要存储仅支持基本计算的原始未经管理的数据开始。然后,我们将讨论在云中的架构设计和实施细节。尽管最初数据湖仅用于基本数据处理,但现在通过API和连接器与其他解决方案的集成,可以使数据湖内的数据更适合特定目的,从而实现了对数据的民主访问和报告。最后,我们将以鸟瞰的视角看待通过利用数据科学笔记本在组织内加速数据分析和实验的一种非常常见的方式。数据湖与云计算——完美的结合数据帮助组织更快做出更好的决策。数据是从应用到安全等一切的中心,而更多的数据意味着对处理能力的更多需求,而云解决方案可以提供这种处理能力。在本地数据湖面临的挑战组织需要一个存储各种类型数据的地方,包括非结构化数据(图像、视频、文本、日志、二进制文件、网页内容)。这是企业采用数据湖的主要原因。最初,企业认为数据湖只是纯粹的原始存储。业务部门希望从IT部门存储的数据中提取洞察和价值,而不仅仅是存储数据。由于Hadoop生态系统的演进,数据湖使能够进行大数据分析的组织能够超越存储卸载的简单概念。数据湖为组织提供了先进的分析和机器学习能力。在2010年代,Hadoop及其相关技术推动了数据湖的大规模采用。然而,由于总拥有成本(TCO)、可扩展性、治理和敏捷性方面的缺点,企业一直难以从数据湖项目中获得足够的投资回报。本地数据湖的资源利用和管理总成本可能变得难以管理。资源密集型的数据和分析处理通常导致未能达到服务水平协议(SLA)。数据治理和安全性问题可能导致合规性问题。由于需要配置资源而导致的分析实验速度较慢。根据预测,到2025年,80%的组织数据将是非结构化的,本地环境已无法以合理的价格提供足够的环境。云解决方案,正如您在第2章中看到的那样,使组织能够首先降低TCO,然后构建创新平台,因为公司内部的人员可以专注于业务价值而不是硬件管理。云数据湖的好处云范式对数据湖的巨大好处在于:不需要将所有数据存储在昂贵、始终运行的Hadoop分布式文件系统(HDFS)集群中。对象存储解决方案(例如AWS S3、Azure Blob Storage或Google Cloud Storage)是完全托管、无限可扩展且成本较低的选择。Hadoop集群不仅提供存储功能,甚至还提供处理计算能力。这些集群可以按需在短时间内创建(几分钟或几秒钟),由于它们不需要始终运行,因此可以立即节省成本。这些Hadoop集群可以直接从对象存储读取/写入数据,即使这种数据访问速度较慢,但通过使用短暂的集群可以实现整体权衡的成本节省。超大规模云服务提供商通常提供利用较便宜的虚拟机(称为Spot Instances或Preemptible Instances)的能力作为工作节点。这些虚拟机的缺点是它们可以随时被系统驱逐(通常提前30到60秒通知),但由于底层的Hadoop技术具有工作节点容错功能,因此可以很容易地用新的实例替代。通过这种方法,额外的成本可以得到节省。如今,在超大规模云服务提供商上提供的大多数Hadoop服务都是完全托管的,并以PaaS模式提供(尽管您可以使用纯IaaS服务构建自己的Hadoop集群)。PaaS意味着您无需自己管理所有主节点和工作节点所需的所有虚拟机,而可以专注于构建用于从数据中提取价值的处理。与在组织内部生成数据孤立的本地Hadoop集群相比,云中可以有效地将存储(即对象存储中的数据可以注入到任何HDFS集群中)和计算(即按需创建的虚拟机)有效地分离,使组织能够更灵活地应对数据治理方面的挑战。云是数据湖的理想环境,因为它解决了本地数据湖面临的所有挑战:总体成本、可扩展性、弹性和一致的数据治理。市场正在大力投资于数据湖,特别是基于云的数据湖。Amazon EMR、Azure Data Lake和Google Cloud Dataproc都可在超大规模云服务提供商上使用。Databricks(是开源Spark引擎的主要开发者,该引擎是所有主要Hadoop发行版的一部分)构建了一个完整的多云数据平台,不仅提供存储和计算,还提供处理整个数据生命周期所需的全部功能。接下来,让我们深入了解云上数据湖的设计和实施细节。设计与实现数据湖的设计取决于您是否需要流式处理,您将如何进行数据治理,您使用哪些Hadoop功能以及您正在构建哪个超大规模云服务提供商的平台。让我们逐一来看这些。批计算和流计算在分析数据工作负载时,要回答的第一个问题是要处理的数据的年龄:这是已经存储了一段时间的数据,还是刚刚到达系统的数据?根据答案,选择两种主要的数据处理方法之一:批处理和流处理。批处理已经是主导方法已有20多年的历史,但近年来,特别是随着云计算的出现,流处理变得越来越受欢迎。流处理更适用于实时处理大量数据,而批处理更适用于离线处理大量数据。无论是批处理还是流处理,数据湖中有四个存储区域:Raw/Landing/Bronze(原始/着陆/青铜) 在这里,原始数据直接从源系统收集和摄入。Staging/Dev/Silver(暂存/开发/银) 在这里,更高级的用户(如数据工程师、数据科学家)处理数据,为最终用户准备数据。Production/Gold(生产/金) 在这里,存储生产系统使用的最终数据。Sensitive(敏感)(可选) 敏感数据所在的地方。它连接到所有其他阶段,并促进数据访问治理,以确保符合公司和政府法规。在2014年,提出了两种新的体系结构,以允许在规模上进行批处理和流处理:Lambda(由Nathan Marz提出)和Kappa(由Jay Kreps提出)。Lambda体系结构(如图5-1所示)使用独立的技术堆栈,批处理层跨越所有(历史)事实,速度层用于实时数据。系统中摄取的新数据同时存储在持久数据集(批处理层)和易失性缓存(速度层)中。第一个数据集然后被索引,并可供批处理视图的服务层使用,而第二个则通过速度层通过实时视图公开。这两个数据集(持久和易失性)可以并行或分离地查询,以回答所需的问题。这种体系结构通常部署在Hadoop环境中,其中HDFS可以作为批处理层的基础,而诸如Spark、Storm或Beam等技术可以用于速度层。最后,Hive可以成为实施服务层的解决方案,例如。在Kappa体系结构(如图5-2所示)中,您可以使用单一技术堆栈(例如Beam/Spark)执行实时和批处理处理。核心是流处理架构。首先,事件流平台存储传入的数据。然后,流处理引擎实时处理数据,使数据可用于实时视图。在这个阶段,数据可以持久化到数据库中,以在需要时执行批量分析并利用相同的技术堆栈。数据目录数据分布在多个站点,包括数据库、DWH(数据仓库)、文件系统和Blob存储。此外,数据可能存储在数据湖的不同区域:原始、分段、生产或敏感。这使得数据科学家难以找到他们需要的数据,企业用户难以访问最新的数据。我们需要一个解决方案,使所有这些数据源变得可发现和可访问。数据目录是描述组织数据集的所有元数据的存储库,它将帮助数据科学家、数据工程师、业务分析师和其他用户找到通向所需数据集的路径。在图5-3中,您可以看到一个可能的高层次体系结构,描述了数据目录如何与数据湖和数据平台的其他解决方案连接。如果数据存在于多个数据存储库中(其中之一是数据湖),但处理引擎和分析工作负载的Gold存储库位于数据湖内,请确保数据目录是全面的,并且数据没有重复。确保元数据包含有关数据集级别(例如主数据或副本)的信息。 在将主数据集引入数据湖并进行转换时,可能需要在计算后进行同步。在图5-3中,您可以看到这种与数据处理的数据目录的高级集成:元数据目录履行。搜索所需的数据资产。如果数据资产尚不存在于数据湖中,则将其复制到Bronze存储区。在复制的数据资产上执行所需的转换。根据需要更新原始数据资产。数据目录有助于理清组织可能拥有的各种数据集,因为它可以帮助找到重复使用或相似的数据集,并根据需要删除、停用或合并它们。数据目录可以帮助组织专注于对它们最相关的数据,并避免使用资源来存储和处理无用的数据,这可能导致成本节省。 每当数据在组织内共享时,具有关联数据合同是有益的。数据合同通常是JSON/YAML文件,捕捉数据生产者和消费者之间关于数据架构、摄入/发布频率、所有权和数据访问级别的协议,包括匿名化和脱敏等方面的协议。Hadoop生态Hadoop仍然是数据湖在本地和云端的事实标准。数据湖的概念始于MapReduce和Hadoop技术(参见“反模式:数据集市和Hadoop”)。在过去的15年中,Hadoop的流行逐渐增长,形成了一个丰富的生态系统,用于数据摄取、存储、处理和可视化。这导致了IBM和Cloudera等公司开发商业发行版。Databricks提供了多云Hadoop功能。在表5-1中,我们列举了一些按用例划分的框架中的热门工具。表5-1中列出的解决方案可以部署在本地环境中,但也可以通过IaaS方法轻松部署在云中。超大规模云服务提供商将这些热门产品转变为完全托管的解决方案,以减轻用户端的配置和基础设施管理负担,提高固有的可扩展性和弹性,并降低成本。表5-1还概述了云中最受欢迎的解决方案,以促进对最受欢迎的本地Hadoop技术的云迁移和采用。请参考表格,我们将审查三个主要云提供商的数据湖参考架构。云数据湖参考架构在本节中,我们将审查在公共云中利用主要三家超大规模供应商的服务实施数据湖的一些参考架构。与任何云架构一样,不存在一种适用于所有情况的设计。总会有多种不同的选项可供选择,可以满足您的具体需求。Amazon Web ServiceS在AWS上,托管的Hadoop服务是Amazon Elastic MapReduce (EMR)。然而,它仅提供分析数据处理。AWS建议我们更全面地思考数据湖,并考虑使用Amazon Athena或Amazon Redshift更好地进行结构化数据的分析。此外,原始数据可能尚不存在于云存储(Amazon S3)中,需要进行摄取。因此,在AWS上实现数据湖的推荐方式是利用AWS Lake Formation。它是一项完全托管的服务,使开发人员能够自动进行数据收集、数据清理/处理和数据移动,以使数据可用于分析和机器学习工作负载。它配备了一个权限服务,扩展了AWS IAM功能,实现更好的数据治理和安全性(例如,细粒度策略、列级和行级访问控制等)。查看图5-4中的架构,我们可以识别出以下主要组件:数据源,在这种情况下是Amazon S3、关系型和NoSQL数据库构建在Amazon S3存储桶之上的存储区由AWS Lake Formation协调的数据目录、数据治理、安全性和流程引擎提供对数据的访问的分析服务,如Amazon Athena、Amazon Redshift和Amazon EMR这种架构适用于处理结构化、半结构化和非结构化数据的任何类型的用例。一旦数据经过准备和转换,由于AWS S3服务的普遍性,即使对于数据湖之外的解决方案(如Databricks或Snowflake),它也可以轻松地提供,潜在地可以连接到平台上的任何其他服务。Microsoft Azure在Azure平台上,有几种实现数据湖的方式,因为有不同的解决方案可以帮助设计可能的目标架构。通常,我们会看到图5-5中所示的架构,其中我们可以识别出以下主要组件:Azure Data Lake Storage Gen2(ADLSG2):为大量数据优化的对象存储,与HDFS完全兼容,通常由用户实现数据湖的所有数据区域。Azure Data Factory:无服务器解决方案,用于摄取、转换和操作数据。Azure Purview:提供治理功能,用于查找、分类、定义和执行跨数据的政策和标准。Azure Synapse Analytics:用于针对存储在ADLSG2中的数据发出SQL查询的分析服务。Databricks:基于Spark处理引擎的完整分析和机器学习解决方案。Power BI:业务智能(BI)报告和可视化工具。必须注意的是,Azure Databricks可以与Azure HDInsight互换使用。两者之间的主要区别在于,Databricks是一个基于Apache Spark的分析平台,经过优化以在Microsoft Azure云服务平台上运行,而HDInsight是一个托管的完整Apache Hadoop分发版(即不仅包括Spark工具,还包括其他不太适应Azure的工具)。如果您想使用标准的Hadoop生态系统解决方案,应该利用HDInsight;而如果您更愿意利用基于Spark的完整分析和机器学习解决方案,那就选择Databricks。Google Cloud Platform在Google Cloud Platform上(参见图5-6),不同的无服务器和完全托管的组件通过API进行通信。我们可以识别以下主要组件:Data Fusion:用于批量和流处理的数据摄取和转换解决方案Pub/Sub:用于集成来自Data Fusion的输入和由Dataproc提供的Hadoop集群的消息中间件Dataproc:按需提供HDFS、Spark和非Spark功能的Apache Hadoop集群Cloud Storage:对象存储解决方案,用于实现数据区域Bigtable和BigQuery:分析和实时数据处理引擎Looker/Looker Studio:BI和可视化解决方案Dataplex:处理数据治理、数据目录和安全性的单一视图Composer:基于Apache Airflow的数据工作流编排服务,使用户能够编写、调度和监视流水线根据您的用例,Hadoop或HDFS集群可能并非总是最合适的选择。将数据存储在Cloud Storage中使您能够自由选择适合当前工作的正确工具,而不受限于HDFS集群上可用的工具和计算资源。例如,许多临时的Hive SQL查询可以轻松迁移到BigQuery,它可以使用其本地存储或直接读取/查询Cloud Storage。同样,流应用程序可能更适合于Apache Beam流水线,这些流水线可以在Dataflow上运行,Dataflow是一个完全托管的流分析服务,通过自动缩放和批处理最小化延迟、处理时间和成本。既然您熟悉了云数据湖的参考架构,让我们深入了解如何通过第三方解决方案扩展数据湖。集成数据湖:真正的超能力数据湖技术因其能够处理任何类型的数据,从结构化的表格格式到文本或图像等非结构化文件,而变得流行。这使得许多之前不可能的用例成为可能,例如通过对发票进行文本分析来分析供应商,识别电影场景中的演员,或者实现实时仪表板以监控电子商务网站上的销售情况。数据湖的超能力在于它能够将数据与无限数量的处理引擎连接起来,以激活您心中可能的任何用例。扩展湖的APIs在数据湖中,数据摄取过程始于将原始数据导入到着陆区。在数据摄取后,必须对其进行处理(可能需要多次处理)才能进行可视化和激活。这些步骤中的每一个都可能涉及数据湖本身的各种引擎,或者是托管在云中的第三方产品,有时甚至是在本地。为了使位于混合环境中的不同系统进行通信和数据交换,可以使用API。API是一种允许两个或多个系统通过共享的协议(如HTTPS和gRPC)进行通信的软件组件。大多数现代应用程序使用API来集成服务和交换数据。即使有临时连接器可用,它们通常也是基于API构建的。您可以将API视为数据可以从一个系统流向另一个系统的高速公路。用于保护数据流量的安全措施是收费站,速率限制是速度限制。通过这些高速公路,数据可以在多个系统之间流动,并且可以由任何类型的处理引擎处理,从标准ETL到现代ML框架如TensorFlow或PyTorch。组织可以使用API将其数据湖演进到所需的外部服务。API还可以用于监视、配置、调整、自动化以及访问和查询湖本身。Apache Iceberg,Apache Hudi和Delta Lake对数据湖的演进将数据湖与其他技术集成的主要目标是扩展其功能,超出Hadoop框架提供的开箱即用功能。考虑到标准的Hadoop堆栈,通常有一个缺失的元素,通常使用其他技术(例如在线事务处理[OLTP]数据库)来处理:ACID事务管理。Apache Iceberg、Apache Hudi和Delta Lake是建立在HDFS之上的开源存储层,旨在解决这一关键方面。尽管它们各自具有一组不同的功能(例如,Apache Iceberg支持比Apache Hudi和Delta Lake更多的文件格式),但它们有一些共同的元素:ACID兼容性,确保用户查询的信息是一致的克服HDFS在文件大小方面的固有限制——通过这种方法,即使是小文件也可以完美运作记录对数据所做的每一次更改,保证在必要时进行完整的审计并启用时光旅行查询在处理批量和流式插入和处理时没有差异与Spark引擎的完全兼容基于Parquet格式的存储,可实现高度的压缩流处理支持能够将对象存储视为数据库在行和列级别应用治理这些存储解决方案可以启用通常由其他技术处理的多个用例(例如DWH,我们将在第6章中更详细地研究),主要是因为它们能够防止数据损坏,以非常快的模式执行查询,并经常更新数据。在这些新启用的存储的交易功能(即更新/删除)在这方面发挥关键作用的特定场景非常明确,这些场景涉及GDPR和加利福尼亚消费者隐私法(CCPA)。根据这些法规,组织被迫具备根据特定请求清除与特定用户相关的个人信息的能力。在标准HDFS环境中执行此操作可能是耗时和资源密集的,因为必须识别与请求的个人数据相关的所有文件,然后将其摄取,过滤并写成新文件,最后删除原始文件。这些新的HDFS扩展简化了这些操作,使它们易于快速执行,并且更重要的是,可靠。 这些开源项目已被社区广泛采用,社区正在大力投资它们的开发。例如,Netflix广泛使用Apache Iceberg,而Uber的数据平台由Apache Hudi提供支持。尽管Delta Lake是一个Linux基金会项目,但主要贡献者是Databricks,这是Spark引擎背后的公司,该公司开发并商业化了一个完整的套件,用于处理基于Spark和Delta Lake的供应商专有版本的大数据工作负载。除了ACID之外,还有两个功能正在改变用户在数据湖中处理数据的方式:分区演进这是在文件(即表)上更新分区定义的能力。分区是允许文件系统将文件内容拆分为多个块以避免在检索信息时完成全扫描的信息(例如,提取2022年第一季度的销售数据时,您应该能够仅查询属于年初的数据,而不是查询整个数据集,然后过滤掉不需要的数据)。考虑到文件中分区的定义可能因业务需求(例如,我们希望收集有关设备工作时间的见解)而发生变化,有一个能够以透明且快速的方式处理这些更改的文件系统是至关重要的。HDFS(通过Hive)可以从逻辑角度实现这一点,但实际上是不可实现的,因为需要大量计算工作。请注意,在撰写本文时,此功能仅由Apache Iceberg提供。模式演进与分区一样,模式可能需要随时间而更新。您可能想要添加、删除或更新表中的列,文件系统应该能够在不需要重新转换数据的情况下进行大规模的操作(即,无需ELT / ETL方法)。请注意,在撰写本文时,只有Apache Iceberg完全支持这一点,但在使用Apache Spark作为引擎时,所有解决方案都能够处理模式演进。现在您已经了解了如何扩展数据湖的功能并丰富可以通过它解决的广泛用例,让我们看看如何实际与数据湖进行交互。使用Notebooks进行交互式分析在处理数据时,最重要的之一就是能够以交互方式轻松快速地访问数据并进行分析。在利用数据湖时,数据工程师、数据科学家和业务用户可以利用诸如Spark、Pig、Hive、Presto等众多服务来处理数据。在几个社区中,尤其是数据科学家社区中,一种备受欢迎的解决方案被认为是组织进行数据分析的瑞士军刀:Jupyter Notebook。Jupyter Notebook是一个开源应用程序,可用于编写包含文本、要执行的代码、图表和图形混合的实时文档。它可以被看作是一个实时书,除了使用类似Markdown的标记语言编写的文本之外,还包含一些执行一些数据操作(例如查询、转换等)的代码,并最终生成一些结果,生成图表或表格。从架构的角度来看,可以将在数据湖上运行的Jupyter Notebook视为三个不同的组件,如图5-7左侧所示。HDFS是存储,内核是处理引擎,而Jupyter是服务器,利用处理引擎来访问数据,并通过浏览器为用户提供编写和执行笔记本内容的用户界面。有几个内核可以利用(对于机器学习,通常会使用PyTorch),但对于数据工程而言,最常见的选择是Spark,可以通过用Python编写的代码使用PySpark访问(如图5-7右侧所示)。一旦正确配置,您可以立即在笔记本中直接编写文本和代码,并与数据集进行交互,就像利用Spark命令行界面一样。您可以使用笔记本开发和与组织内的其他人共享代码,进行快速测试和分析,或快速可视化数据以获得额外的见解。重要的是要强调结果不是静态的,因为它们是实时文档:这意味着如果底层数据集发生变化或代码片段发生变化,与笔记本共享的人在重新执行笔记本时会得到不同的结果。完成分析后,通常有两种不同的路径可以选择:与其他数据科学家、数据工程师、开发人员或任何希望做出贡献的人共享笔记本的源代码(Jupyter Notebook生成.ipynb文件)。他们可以在其环境中加载文件并重新运行代码(当然需要对底层存储系统和通过API集成的任何其他解决方案具有正确的访问权限)。通过生成HTML页面或PDF文档将结果静态化,可以与更广泛的用户共享。由于其极大的灵活性,无论是在可以利用的编程语言方面(多亏了众多可用的内核)还是在可以执行的活动方面,笔记本已成为交互式数据分析、测试和实验的事实标准解决方案(例如,您可以从数据湖中的数据生成图表,或者在将其移至生产环境之前在小数据子集上训练复杂的ML算法)。 我们在与几个组织合作时看到的情况是,笔记本方法是实现数据民主化征程的第一步(如我们将在下一节讨论的那样),因为它允许技术上更为熟悉的人立即获得对数据的标准化访问,促进协作。虽然数据科学家是笔记本使用的先驱,但数据工程师和分析工程师也在不断采用它们。我们甚至看到一些公司正在开发自定义库,以包含在笔记本模板中,以便促进与其他多个解决方案(现成或定制开发的)的集成,将标准化提高到另一个水平,并减少最终用户(甚至是痛苦)的学习曲线。这种标准化,借助容器技术,甚至已经在计算级别实现:公司内的用户每次启动笔记本时,背后都会启动一个带有足够计算资源和一组工具的容器,这些工具可以立即用于其数据分析。云超大规模提供了一些托管版本的Jupyter Notebook解决方案,例如AWS SageMaker、Azure ML Studio和Google Cloud Vertex AI Workbench等,可以帮助摆脱与底层基础架构管理相关的麻烦,因为它们是完全托管的。 现在您对如何利用Jupyter Notebook扩展数据湖有了更好的理解,我们将把注意力转向帮助您了解组织内的人员如何处理数据摄取到数据可视化报告,从完全的IT模型过渡到更为民主的方法。民主化数据处理和报告数据为组织提供的最大价值是使决策者和一般用户能够做出明智的决策。为此,数据应该能够被任何授权的个人访问和使用,而无需专业的临时专业知识。即使从技术角度实现了公司内部数据访问的民主化,仅专注于技术是不够的。组织还需要实施数据编目和治理操作。拥有描述正确数据集的良好元数据将使用户能够找到他们需要的正确信息,并激活正确的数据处理和报告。在本节中,我们将探讨在构建云数据湖时,组织如何从完全由IT驱动的方法转向更具民主化的方法的关键技术。建立对数据的信任当本书的一位作者几年前为一家重要的零售客户担任顾问时,他致力于开发解决方案,自动从销售数据库提取数据以生成供业务用户利用的报告。他需要随时回答决策者的以下问题:你从哪里获取这些数据?你是如何转换那些数据的?生成报告之前你进行了什么操作?一旦他被分配到另一个客户,组织的IT团队接管了整个端到端的流程。虽然他们可以修复错误并监视报告生成,但他们无法说服决策者数据和报告是可信的,因为他们没有足够的知识来回答这些问题。 这种信任显然是一个障碍,通过使最终用户能够以自主方式深入挖掘他们的数据并从摄取到最终报告值进行审计,这个障碍将被消除。这种方法在过去可能是不切实际的,但现在人们在数字方面更有经验,这在转变这种方法方面起到了很大的帮助。正如图5-8所示,在所有权和责任方面存在明显的过渡,从左边的旧世界到右边的新世界。虽然以前大部分工作是由IT部门完成的,但如今最终用户手中有了工具,使他们能够在数据目录化到数据可视化的大多数活动中具有很高的自主权。Atlan、Collibra、Informatica、AWS Glue、Azure Purview 和 Google Dataplex等工具使元数据收集的过程变得更加简单和快速。一方面,已经构建了大量自动化工具,以实现自动化的数据爬取,特别是通过与多个数据库和存储引擎的集成(例如AWS Redshift 和 Athena、Azure Synapse、Google BigQuery、Snowflake等),另一方面,通过丰富且易于使用的用户界面简化数据输入活动,让业务人员能够完成大部分工作。沿着这个链条往上走,我们发现即使是一直以来由专业用户(即数据工程师)负责的数据处理、准备、清理和整理步骤,现在也可以由最终用户直接处理。当然,仍然可能需要数据工程师/数据科学家利用先进的处理引擎(如Spark)完成数据处理的某些子集。对于需要专业人员(SMEs)参与的活动,例如探索、转换和过滤,已经开发并提供了一些工具,如Talend Data Preparation 或 Trifacta Dataprep,其明确目标是支持非数据工程/数据科学家用户:这些工具通过提供直观的界面,将处理委托给底层引擎(例如Beam),该引擎可以对大规模数据集应用所有变更。这种方法甚至强调了底层数据的访问,该数据存储在HDFS集群上或直接存储在AWS S3、Azure Data Lake或Google Cloud Storage等对象存储上,可以通过提供一系列功能和控制的多种不同工具实现。这意味着不同类型的用户可以利用不同类型的工具处理数据。例如,数据工程师可以利用Spark开发其数据转换流水线;数据科学家可以使用scikit-learn、TensorFlow或PyTorch实现ML算法;业务分析师可以使用Hive或PrestoSQL使用SQL查询执行假设分析。最后一步是数据可视化/报告,这通常是业务用户自己可以轻松完成的。这里有很多可以利用的解决方案(例如Microsoft Power BI、Looker、Tableau 和 Qlik等),它们为用户提供所需的灵活性。在这个阶段,用户往往自然而然地具有自主性,因为这些工具在某种程度上与他们在Excel中所熟悉的方法相似,因此用户不需要经历陡峭的学习曲线以熟练使用。数据可视化、BI和假设分析是业务用户可以轻松进行的典型分析。即使原始数据始终由IT部门管理,由于与第三方服务的集成,业务用户可能也可以以自主方式将数据导入数据湖。由于这一点,人们对于不同业务单元摄取的数据内容以及相关数据集的质量和正确性的信任需求正在增长。治理的概念正在获得推动以帮助处理这一问题。治理是指管理和监督组织数据资产的过程,以确保业务用户能够访问高质量、一致且易于获取的数据,可以看作是以下三个关键因素的组合:确定具有按时获取正确信息的关键利益相关方。保护数据免受任何内部和外部的滥用,重点关注人员。与公司内外的其他人合作,释放数据的价值。在许多公司中,我们看到治理不一定是人们被分配的一个角色,而更多地是他们在实地通过与工具的交互以及他们向内部社区提供信息的质量而赢得的一个头衔。事实上,有一些工具(例如Informatica Data Catalog)允许用户对治理者进行评级,方式类似于购买者在亚马逊上对卖家进行评级。既然您已经了解了将组织引向更现代和民主方法的各种选项,让我们讨论该过程的一个主要部分,仍然主要由IT团队掌握:数据摄取。数据摄取仍然是IT事务数据摄取仍然是IT事务中最关键且重要的步骤之一。如果规划不善,数据湖可能会变成一个“数据沼泽”,其中存在大量未使用的数据。这通常是因为组织倾向于将各种原始数据加载到数据湖中,即使他们并不完全了解为什么加载数据以及如何利用它。这种方法会导致大量未使用的数据堆积,这些数据在大多数时间内都不会被使用。但即使未使用的数据仍然留在湖中,并使用本应为其他活动空闲的空间。过时的数据还倾向于存在间断和不一致,当最终有人使用它时,可能导致难以排查的错误。因此,在摄取过程中遵循以下最佳实践非常重要:文件格式 有几种文件格式可供选择,各有利弊。如果主要目标是可读性,那么CSV、JSON和XML是最常见的。如果性能是最重要的目标,则Avro、Parquet或Optimized Row Columnar (ORC) 是更适合的:Avro(基于行的格式)在需要强烈I/O和低延迟操作时效果更好(例如,基于事件的源、流式仪表板)。Parquet和ORC(基于列的格式)更适用于查询用例。文件大小 通常在Hadoop世界中,文件越大越好。文件通常有数十吉字节大小,当处理非常小的文件(例如,物联网使用案例场景)时,建议使用流处理引擎(如Apache Beam或Spark Streaming)将数据 cons 许多小文件合并为几个大文件。数据压缩 数据压缩对于节省空间非常重要,特别是在每天可能摄取PB级数据的数据湖中。此外,为了保证性能,选择一个足够快速以在需要时即时压缩/解压缩数据的压缩算法非常关键。标准的ZIP压缩算法通常不适用于这样的应用,我们看到组织倾向于使用由谷歌开发的Snappy,它提供了与数据湖需求相一致的性能。目录结构 这是一个非常重要的主题,根据用例和要使用的处理引擎,目录结构可能会有很大变化。以物联网使用案例通过HBase处理为例:假设您正在全球范围内收集来自设备的数据,并且想要从特定位置的设备生成的消息中提取特定日期的信息。一个良好的目录结构示例可能是/country/city/device_id/year/month/day/hours/minute/second。如果用例是批处理操作,将输入数据放入名为IN的文件夹,将输出数据放入名为OUT的文件夹可能会很有用(当然,它必须用最佳前缀标识以避免误解和数据重复)。根据数据的性质(批处理与流处理)和数据的目标(例如,AWS S3、Azure Data Lake Storage、Google Cloud Storage),可以利用多种解决方案:用户可以使用脚本(例如PowerShell或bash)加载数据,也可以直接使用API或本机连接器摄取数据。用户可以直接将数据流入平台(例如AWS Kinesis、Azure Event Hub或Google Pub/Sub),也可以将其作为原始文件上传到HDFS以供将来处理。通常由IT部门管理这些活动,因为它们可能涉及大量自动化以及同时需要专门技能的集成/数据处理。然而,有一些解决方案,如Fivetran,正在简化用户配置数据摄取到云中的方式。对于这样的解决方案,甚至较不熟练的人(例如业务用户)也有可能在湖中加载数据,扩展我们之前讨论的民主化概念。数据湖中的ML正如我们之前讨论的,可以以原始格式将任何类型的数据存储在数据湖中,这与数据仓库不同,数据需要是结构化或半结构化的。特别是,可以将非结构化数据(图像、视频、自然语言等)以它们的典型格式(JPEG、MPEG、PDF等)存储在其中。这使得将各种类型的数据纳入数据湖变得非常方便,然后可以用于开发、训练和预测ML算法,特别是基于深度学习的算法(参考“应用AI”)。在原始数据上进行训练对于结构化数据,您可以利用像Spark、XGBoost和LightGBM等库。这些框架可以直接读取和处理CSV文件,而这些文件可以原封不动地存储在数据湖中。对于非结构化数据,最常用的ML框架是TensorFlow和PyTorch。这些框架可以以其原生形式读取大多数图像和视频格式,而原始数据将以此形式存储。因此,在图5-9中,我们演示了训练ML模型的步骤,准备的数据可以与收集和存储的数据相同,并且标签可以是数据本身的一部分,无论是CSV文件中的一列还是基于图像/视频组织的方式(例如,所有螺丝的图像可以存储在名为screws的文件夹中)。这使得在数据湖上训练ML模型变得非常简单。然而,有一些效率方面的考虑——直接读取JPEG或MPEG文件将导致ML训练程序受到I/O约束。因此,数据通常会以TensorFlow Records等格式从数据湖中提取和存储。由于这些格式在从云存储读取时优化了吞吐量,它们有助于最大程度地利用GPU。GPU制造商还提供了从云存储直接读取常见格式(例如Apache Arrow)到GPU内存的功能,从而加速ML过程。在数据湖中进行预测因为机器学习框架直接支持在原始格式中读取数据,因此在原始数据上调用模型也非常简单。例如,在图 5-10 中,我们向您展示了在 AWS 上的图像分类工作流程。请注意,图像以原样被摄入到云存储桶中,并且在上传的图像上调用了机器学习模型。类似的功能在 GCP 和 Azure 中也是可用的,我们将在第 11 章中更详细地介绍。如果选择数据湖作为主要平台,还可以考虑使用 MLflow,这是一个开源项目,用于实现端到端的机器学习工作流程。数据存储在数据湖中,分布式训练通常使用 Spark 进行,尽管也存在与 TensorFlow 和 PyTorch 的集成。除了训练和部署外,MLflow 还支持高级功能,如模型注册表和特征存储。直接在机器学习框架中处理原始数据的这种能力是数据湖的引人注目的优势之一。总结在本章中,您更详细地了解了真正的数据湖是什么,面临的挑战以及可以采用的模式,使其成为组织数据平台的核心支柱。以下是关键要点:数据在每个组织中都起着关键作用,帮助在短时间内做出坚实决策,可能是实时的。数据湖比数据仓库提供更大的灵活性,因为它允许用户处理任何类型的数据(结构化、半结构化,甚至是非结构化),并利用来自开源生态系统的各种解决方案和工具。在旧的本地环境中管理数据湖对组织来说是一项挑战。云环境是组织存储数据、降低总体成本并专注于业务价值而不是硬件管理的好解决方案。云采用的主要优势包括:(1)通过在存储和计算方面节省费用降低总体成本;(2)通过利用超大规模提供的可伸缩性;(3)通过利用底层平台的弹性来采用快速失败的方法,加速实验;(4)在平台上采用一致的数据治理方法,以符合安全性和控制要求。在数据湖中,存在描述组织数据集的所有元数据的存储库,这将帮助数据科学家、数据工程师、业务分析师以及可能需要利用数据找到通往所需数据集的路径的任何用户。数据湖支持批处理和流处理操作,并促进Lambda/Kappa模式的实施。市场在数据湖方面的投资强劲,特别是在基于云的数据湖。因此,可以通过利用API或与第三方解决方案集成来扩展数据湖。一种常见的集成是在HDFS之上采用Iceberg或Delta Lake,使存储符合ACID标准并启用其他类型的用例(主要是那些对数据强一致性要求较高的用例)。Jupyter Notebook是组织中实施数据分析、实验和基于测试的方法的最常见方式。数据湖促进了组织内部的数据民主化访问,因为大部分活动都可以进行自助服务。由于数据以数据湖中的原生格式存储,并且由于机器学习框架支持读取这些格式,因此数据湖在没有任何特殊钩子的情况下促进了机器学习。在接下来的章节中,我们将探讨数据湖的替代方案,即数据仓库。
0
0
0
浏览量2030
攻城狮无远

《Delta Lake Up & Running》第三章:Delta表的基本操作

Delta表可以通过多种方式创建。创建表的方式主要取决于您对工具集的熟悉程度。如果您主要是SQL开发人员,可以使用SQL的CREATE TABLE来创建Delta表,而Python用户可能更喜欢使用DataFrameWriter API或细粒度且易于使用的DeltaTableBuilder API。在创建表时,您可以定义生成列,其值是根据Delta表中其他列上的用户指定函数自动生成的。虽然存在一些限制,但生成列是丰富Delta表模式的强大方式。Delta表可以通过标准的ANSI SQL或使用流行的PySpark DataFrameReader API来读取。您可以使用经典的SQL INSERT语句写入Delta表,也可以将DataFrame附加到表中。最后,利用SQL的COPY INTO选项是快速附加大量数据的绝佳方式。根据您经常使用的查询模式对Delta表进行分区可以显着改善查询和DML性能。组成Delta表的各个文件将以子目录的形式组织,这些子目录与分区列的值对齐。Delta Lake允许您在事务日志中的提交条目中关联自定义元数据。这可以用于标记敏感的提交以进行审计。您还可以在表属性中存储自定义标签,就像您可以为云资源添加标签一样,现在您也可以将这些标签与Delta表关联起来。您还可以修改某些Delta功能。例如,您可以将delta.appendonly属性关联到表上以防止删除和更新。创建Delta表Delta Lake允许我们以三种不同的方式创建表:SQL数据定义语言(DDL)命令 SQL开发人员已经非常熟悉经典的CREATE TABLE命令,您只需添加一些属性即可使用它来创建Delta表。PySpark DataFrameWriter API 大数据Python(和Scala)开发人员很可能已经非常熟悉这个API,您可以继续使用它来操作Delta表。DeltaTableBuilder API 这是专为Delta表设计的新API。它采用了流行的Builder模式,为每个Delta表和列属性提供非常精细的控制。在接下来的章节中,我们将亲自体验这些表创建方法中的每一种。使用SQL DDL创建Delta表在Spark计算环境中使用的SQL版本被称为Spark SQL,它是Spark支持的ANSI SQL的变种。Spark SQL通常与ANSI标准SQL兼容。有关Spark SQL变种的更多详细信息,请参考Spark文档。正如前面提到的,您可以在Spark SQL中使用标准的SQL DDL命令来创建Delta表:%sql -- Create a Delta table by specifying the delta format, followed -- by the path in quotes CREATE TABLE IF NOT EXISTS delta.`/mnt/datalake/book/chapter03/rateCard` ( rateCodeId INT, rateCodeDesc STRING ) USING DELTA 您使用的表名的表示法是file_format | path_to_table,其中file_format是delta,而path_to_table是Delta表的路径。在实际应用中,使用这种格式可能会变得繁琐,因为文件路径可能会变得相当长。这就是目录的用处。目录允许您使用database.table_name表示法注册表,其中database是表的逻辑分组,而table_name是表的缩写。例如,如果您首先创建了一个名为taxidb的数据库,如下所示:%sql CREATE DATABASE IF NOT EXISTS taxidb; 然后,您可以按照以下方式创建上述表:%sql -- Create the table using the taxidb catalog CREATE TABLE IF NOT EXISTS taxidb.rateCard ( rateCodeId INT, rateCodeDesc STRING ) USING DELTA LOCATION '/mnt/datalake/book/chapter03/rateCard' 从此时开始,您可以将这个Delta表称为taxidb.rateCard,这比delta./mnt/datalake/book/chapter03/rateCard或者可能更长的路径更容易记忆和键入。Spark生态系统中最广泛使用的目录是Hive目录。当在创建表的数据湖位置运行目录列表时,您会看到我们的目录是空的(因为您尚未加载任何数据),除了包含表的事务日志的_delta_log目录:%sh ls -al /dbfs/mnt/datalake/book/chapter03/rateCard total 12 drwxrwxrwx 2 root root 4096 Dec 2 19:02 . drwxrwxrwx 2 root root 4096 Dec 2 19:02 .. drwxrwxrwx 2 root root 4096 Dec 2 16:40 _delta_log 当您打开_delta_log目录时,您会看到我们的第一个事务日志条目:%sh ls -al /dbfs/mnt/datalake/book/chapter03/rateCard/_delta_log total 15 drwxrwxrwx 2 root root 4096 Dec 2 19:02 . drwxrwxrwx 2 root root 4096 Dec 2 19:02 .. -rwxrwxrwx 1 root root 1886 Dec 2 19:02 00000000000000000000.crc -rwxrwxrwx 1 root root 939 Dec 2 19:02 00000000000000000000.json 在第2章关于事务日志的讨论中,您已经了解了可以写入事务日志条目的不同操作。其中一个操作是元数据操作,它描述了表的模式、分区列(如果适用)和其他信息。这个元数据操作总是写入为我们的新表创建的第一个事务日志条目中。要找到这个元数据操作,您可以在事务条目中搜索字符串"metadata":%sh grep metadata /dbfs/mnt/datalake/book/chapter03/rateCard /_delta_log/00000.json > /tmp/metadata.json python -m json.tool /tmp/metadata.json 这会产生以下输出:{ "metaData": { "id": "f79c4c11-a807-49bc-93f4-2bbe778e2a04", "format": { "provider": "parquet", "options": {} }, "schemaString": "{\"type\":\"struct\", \"fields\":[{\"name\":\"rateCodeId\", \"type\":\"integer\",\"nullable\":true, \"metadata\":{}},{\"name\":\"rateCodeDesc\", \"type\":\"string\",\"nullable\":true, \"metadata\":{}}]}", "partitionColumns": [], "configuration": {}, "createdTime": 1670007736533 } } 在这里,您可以看到Delta Lake已将表的模式写入事务日志条目,以及一些审计和分区信息。DESCRIBE语句SQL的DESCRIBE命令可用于返回Parquet文件或Delta表的基本元数据信息。返回表的元数据包括每个列的以下信息:列名列数据类型应用于列的任何注释以下是一个表级别的DESCRIBE命令示例:%sql DESCRIBE TABLE taxidb.rateCard; +--------------+-----------+---------+ | col_name | data_type | comment | +--------------+-----------+---------+ | rateCodeId | int | <null> | | rateCodeDesc | string | <null> | +--------------+-----------+---------+ 当您想要查找Delta Lake特定的属性时,还可以使用DESCRIBE TABLE EXTENDED命令,它提供更详细的元数据信息,包括以下通用属性:创建表的数据库的目录名称(在这种情况下是Hive元数据存储)Hive数据库表名底层文件的位置表的所有者表属性还包括以下Delta Lake特定的属性:delta.minReaderVersion 读取该Delta表的读取器所需的最低协议读取器版本。delta.minWriterVersion 向该Delta表写入数据的写入器所需的最低协议写入器版本。有关所有可用表属性的完整列表,请参考Delta Lake文档。以下是DESCRIBE TABLE EXTENDED命令的示例:%sql DESCRIBE TABLE EXTENDED taxidb.rateCard; 这会生成以下输出:+------------------------------+------------------------------+---------+ | col_name | data_type | comment | +------------------------------+------------------------------+---------+ | rateCodeId | int | <null> | | rateCodeDesc | string | <null> | | | | | | # Detailed Table Information | | | | Catalog | hive_metastore | | | Database | taxidb | | | Table | ratecard | | | Type | EXTERNAL | | | Location | dbfs:/.../chapter03/rateCard | | | Provider | delta | | | Owner | root | | | Table Properties | [delta.minReaderVersion=1, | | | | delta.minWriterVersion=2] | | +------------------------------+------------------------------+---------+ 到目前为止,我们已经介绍了如何使用SQL DDL创建Delta表。在接下来的部分,我们将切换回Python,并看看如何使用熟悉的PySpark DataFrames来创建新的Delta表。使用DataFrameWriter API创建Delta表Spark DataFrames类似于关系数据库表或带有标题的Excel电子表格。数据以不同数据类型的行和列的形式存在。用于读取、写入和操作DataFrame的函数集合被统称为Spark DataFrameWriter API。创建托管表DataFrameWriter API的一个好处是你可以同时创建表并将来自Spark DataFrame的数据插入其中,如下面的代码片段所示:INPUT_PATH = '/databricks-datasets/nyctaxi/taxizone/taxi_rate_code.csv' DELTALAKE_PATH = \ 'dbfs:/mnt/datalake/book/chapter03/createDeltaTableWithDataFrameWriter' # Read the DataFrame from the input path df_rate_codes = spark \ .read \ .format("csv") \ .option("inferSchema", True) \ .option("header", True) \ .load(INPUT_PATH) # Save our DataFrame as a managed Hive table df_rate_codes.write.format("delta").saveAsTable('taxidb.rateCard') 在这里,我们首先从taxi_rate_code.csv文件中填充DataFrame,然后通过指定.format("delta")选项将DataFrame保存为Delta表。表的模式将是我们DataFrame的模式。请注意,这将是一个托管表,因为我们没有为数据文件指定位置。您可以通过运行SQL的DESCRIBE TABLE EXTENDED命令来验证这一点:%sql DESCRIBE TABLE EXTENDED taxidb.rateCard; +------------------------------+----------------------------------------------+ | col_name | data_type | +------------------------------+----------------------------------------------+ | RateCodeID | int | | RateCodeDesc | string | | | | | # Detailed Table Information | | | Catalog | hive_metastore | | Database | taxidb | | Table | ratecard | | Type | MANAGED | | Location | dbfs:/user/hive/warehouse/taxidb.db/ratecard | | Provider | delta | | Owner | root | | Is_managed_location | true | | Table Properties | [delta.minReaderVersion=1, | | | delta.minWriterVersion=2] | +------------------------------+----------------------------------------------+ 我们可以看到表的数据存储在/user/hive/warehouse位置,并且表的类型设置为MANAGED。如果在表上运行SELECT命令,您可以看到数据确实成功从CSV文件加载:%sql SELECT * FROM taxidb.rateCard +------------+-----------------------+ | RateCodeID | RateCodeDesc | +------------+-----------------------+ | 1 | Standard Rate | | 2 | JFK | | 3 | Newark | | 4 | Nassau or Westchester | | 5 | Negotiated fare | | 6 | Group ride | +------------+-----------------------+ 创建一个非托管表您可以通过同时指定Delta表的路径和名称来创建一个非托管表。在以下代码中,我们按顺序执行了这两个步骤。首先,删除现有的表:%sql -- Drop the existing table DROP TABLE IF EXISTS taxidb.rateCard; 接下来,写入并创建表:# Next, create our Delta table, specifying both # the path and the Delta table N=name df_rate_codes \ .write \ .format("delta") \ .mode("overwrite") \ .option('path', DELTALAKE_PATH) \ .saveAsTable('taxidb.rateCard') 再次通过执行简单的SELECT语句,我们可以验证DataFrame的数据已被加载:%sql SELECT * FROM taxidb.rateCard +------------+-----------------------+ | RateCodeID | RateCodeDesc | +------------+-----------------------+ | 1 | Standard Rate | | 2 | JFK | | 3 | Newark | | 4 | Nassau or Westchester | | 5 | Negotiated fare | | 6 | Group ride | +------------+-----------------------+ 使用DeltaTableBuilder API创建Delta表创建Delta表的最后一种方法是使用DeltaTableBuilder API。由于它专门针对Delta表设计,与传统的DataFrameWriter API相比,它提供了更高程度的细粒度控制。用户可以更轻松地指定附加信息,如列注释、表属性和生成的列。生成器设计模式在软件语言中非常流行。生成器模式旨在“将复杂对象的构建与其表示分离,以便相同的构建过程可以创建不同的表示”。它用于逐步构建复杂对象,最后一步将返回该对象。在这种情况下,我们正在构建的复杂对象是一个Delta表。Delta表支持许多选项,设计一个具有许多参数的标准API是有挑战的。因此,DeltaTableBuilder具有许多小方法,比如addColumn(),它们都返回对生成器对象的引用。这样,我们可以继续添加其他对addColumn()或生成器的方法的调用。我们调用的最后一个方法是execute(),它收集接收到的所有属性,创建Delta表,并将对表的引用返回给调用者。要使用DeltaTableBuilder,我们需要进行以下导入:from delta.tables import * 这个示例创建了一个托管表:# In this Create Table, you do NOT specify a location, so you are # creating a MANAGED table DeltaTable.createIfNotExists(spark) \ .tableName("taxidb.greenTaxis") \ .addColumn("RideId", "INT", comment = "Primary Key") \ .addColumn("VendorId", "INT", comment = "Ride Vendor") \ .addColumn("EventType", "STRING") \ .addColumn("PickupTime", "TIMESTAMP") \ .addColumn("PickupLocationId", "INT") \ .addColumn("CabLicense", "STRING") \ .addColumn("DriversLicense", "STRING") \ .addColumn("PassengerCount", "INT") \ .addColumn("DropTime", "TIMESTAMP") \ .addColumn("DropLocationId", "INT") \ .addColumn("RateCodeId", "INT", comment = "Ref to RateCard") \ .addColumn("PaymentType", "INT") \ .addColumn("TripDistance", "DOUBLE") \ .addColumn("TotalAmount", "DOUBLE") \ .execute() 由于每个方法都返回对生成器对象的引用,我们可以继续调用 .addColumn() 来添加每个列。最后,我们调用 .execute() 来创建Delta表。生成的列Delta Lake支持生成列,这是一种特殊类型的列,其值根据用户指定的函数在Delta表中的其他列上自动生成。当写入具有生成列的Delta表并且未明确为它们提供值时,Delta Lake会自动计算这些值。让我们创建一个示例。为了保持与出租车主题的一致性,我们将创建一个黄色出租车表的简化版本:%sql CREATE TABLE taxidb.YellowTaxis ( RideId INT COMMENT 'This is our primary Key column', VendorId INT, PickupTime TIMESTAMP, PickupYear INT GENERATED ALWAYS AS(YEAR (PickupTime)), PickupMonth INT GENERATED ALWAYS AS(MONTH (PickupTime)), PickupDay INT GENERATED ALWAYS AS(DAY (PickupTime)), DropTime TIMESTAMP, CabNumber STRING COMMENT 'Official Yellow Cab Number' ) USING DELTA LOCATION "/mnt/datalake/book/chapter03/YellowTaxis.delta" COMMENT 'Table to store Yellow Taxi data' 我们看到了使用GENERATED ALWAYS AS的列,它从PickupTime列中提取了YEAR、MONTH和DAY。当我们插入记录时,这些列的值将自动填充:%sql INSERT INTO taxidb.YellowTaxis (RideId, VendorId, PickupTime, DropTime, CabNumber) VALUES (5, 101, '2021-7-1T8:43:28UTC+3', '2021-7-1T8:43:28UTC+3', '51-986') 当我们选择该记录时,我们可以看到生成的列已自动填充:%sql SELECT PickupTime, PickupYear, PickupMonth, PickupDay FROM taxidb.YellowTaxis +---------------------------+------------+-------------+-----------+ | pickupTime | pickupYear | pickupMonth | pickupDay | +---------------------------+------------+-------------+-----------+ | 2021-07-01 05:43:28+00:00 | 2021 | 7 | 1 | +---------------------------+------------+-------------+-----------+ 当我们明确为生成列提供一个值时,该值必须满足约束条件(<value> ⇔ generation expression)IS TRUE,否则插入操作将失败并报错。在GENERATED ALWAYS AS中使用的表达式可以是任何Spark SQL函数,只要在给定相同的参数值时始终返回相同的结果,有一些例外情况我们将很快提到。您可以考虑使用一个生成列来生成一个类似这样的唯一ID列:%sql CREATE OR REPLACE TABLE default.dummy ( ID STRING GENERATED ALWAYS AS (UUID()), Name STRING ) USING DELTA 然而,当您尝试运行此操作时,您会收到以下错误消息:Found uuid(). A generated column cannot use a non deterministic expression. UUID()函数在每次调用时都会返回不同的值,这违反了前面的规则。以下是此规则的一些例外情况,适用于以下类型的函数:用户自定义函数聚合函数窗口函数返回多行的函数使用列出的函数创建的GENERATED ALWAYS AS列是有效的,并可以在多种场景中非常有用,例如计算给定记录样本的标准差。读取Delta表在读取表时,我们有几种选项:使用DataFrameReader的SQL和PySpark。当我们在Databricks Community Edition中使用笔记本时,通常在笔记本内部同时使用SQL和PySpark单元格。有些操作,比如快速的SELECT,用SQL更简单和更快,而复杂的操作有时更容易在PySpark和DataFrameReader中表达。当然,这也取决于工程师的经验和偏好。我们建议采用一种务实的方法,根据您当前正在解决的问题,合理混合使用这两种方法。使用SQL读取Delta表要读取Delta表,我们只需打开一个SQL单元格并编写SQL查询。如果按照GitHub README文件中的说明设置了环境,我们将在/mnt/datalake/book/chapter03/YellowTaxisDelta文件夹中拥有一个Delta表:%sh ls -al /dbfs/mnt/datalake/book/chapter03/YellowTaxisDelta total 236955 drwxrwxrwx 2 root root 4096 Dec 4 18:04 . drwxrwxrwx 2 root root 4096 Dec 2 19:02 .. drwxrwxrwx 2 root root 4096 Dec 4 16:41 _delta_log -rwxrwxrwx 1 root root 134759123 Dec 4 18:04 part-00000-...-c000.snappy.parquet -rwxrwxrwx 1 root root 107869302 Dec 4 18:04 part-00001-...-c000.snappy.parquet 我们可以快速将Delta表位置注册到元数据存储中,如下所示:%sql CREATE TABLE taxidb.YellowTaxis USING DELTA LOCATION "/mnt/datalake/book/chapter03/YellowTaxisDelta/" 创建表之后,我们可以快速统计记录的数量:%sql SELECT COUNT(*) FROM taxidb.yellowtaxis 这会给我们以下的计数结果:+----------+ | count(1) | +----------+ | 9999995 | +----------+ 我们可以看到有近1000万行可供使用。我们可以使用另一种DESCRIBE命令变体来获取表的详细信息:%sql DESCRIBE TABLE FORMATTED taxidb.YellowTaxis; DESCRIBE TABLE FORMATTED命令格式化输出,使其更易阅读:+------------------------------+--------------------------------------+ | col_name | data_type | +------------------------------+--------------------------------------+ | RideId | int | | VendorId | int | | PickupTime | timestamp | | DropTime | timestamp | | PickupLocationId | int | | DropLocationId | int | | CabNumber | string | | DriverLicenseNumber | string | | PassengerCount | int | | TripDistance | double | | RatecodeId | int | | PaymentType | int | | TotalAmount | double | | FareAmount | double | | Extra | double | | MtaTax | double | | TipAmount | double | | TollsAmount | double | | ImprovementSurcharge | double | | | | | # Detailed Table Information | | | Catalog | hive_metastore | | Database | taxidb | | Table | YellowTaxis | | Type | EXTERNAL | | Location | dbfs:/.../chapter03/YellowTaxisDelta | | Provider | delta | | Owner | root | | Table Properties | [delta.minReaderVersion=1, | | | delta.minWriterVersion=2] | +------------------------------+--------------------------------------+ 由于Spark SQL支持大多数ANSI SQL子集,我们可以使用任何类型的复杂查询。以下是一个示例,返回FareAmount超过50美元的CabNumbers:%sql SELECT CabNumber, AVG(FareAmount) AS AverageFare FROM taxidb.yellowtaxis GROUP BY CabNumber HAVING AVG(FareAmount) > 50 ORDER BY 2 DESC LIMIT 5 这给我们的结果是:+-----------+-------------+ | cabnumber | AverageFare | +-----------+-------------+ | SIR104 | 111.5 | | T628190C | 109.0 | | PEACE16 | 89.7 | | T439972C | 89.5 | | T802013C | 85.0 | +-----------+-------------+ 我们还可以在Python中直接使用spark.sql,使用标准SQL作为参数。以下是一个简单的Python代码片段,执行与之前的SQL查询相同的操作:number_of_results = 5 sql_statement = f""" SELECT CabNumber, AVG(FareAmount) AS AverageFare FROM taxidb.yellowtaxis GROUP BY CabNumber HAVING AVG(FareAmount) > 50 ORDER BY 2 DESC LIMIT {number_of_results}""" df = spark.sql(sql_statement) display(df) 这会生成与SQL相同的结果:+-----------+-------------+ | cabnumber | AverageFare | +-----------+-------------+ | SIR104 | 111.5 | | T628190C | 109.0 | | PEACE16 | 89.7 | | T439972C | 89.5 | | T802013C | 85.0 | +-----------+-------------+ 我们建议使用三重引号语法,这样可以轻松跨多行定义字符串而无需使用继续行。此外,请注意我们有一个名为number_of_results的变量,然后将三重引号字符串转换为f-string,并使用{}语法将变量插入到限制中。使用PySpark读取表要在PySpark中读取相同的表,您可以使用DataFrameReader。例如,要实现记录的计数,我们使用以下方式:df = spark.read.format("delta").table("taxidb.YellowTaxis") print(f"Number of records: {df.count():,}") 输出:Number of records: 9,999,995 请注意,我们指定了Delta格式,因为我们的表是Delta表,我们可以使用.table()方法指定我们要读取整个表。最后,我们使用了一个f-string,这次使用了“:,”格式化符号,它会在每三位数字之间使用逗号分隔符。接下来,让我们重新创建之前在SQL中完成的按出租车编号排序的前五个平均费用的代码。以下是Python代码:# Make sure to import the functions you want to use from pyspark.sql.functions import col, avg, desc # Read YellowTaxis into our DataFrame df = spark.read.format("delta").table("taxidb.YellowTaxis") # Perform the GROUP BY, average (AVG), HAVING and order by equivalents # in pySpark results = df.groupBy("CabNumber") \ .agg(avg("FareAmount").alias("AverageFare")) \ .filter(col("AverageFare") > 50) \ .sort(col("AverageFare").desc()) \ .take(5) # Print out the result, since this is a list and not a DataFrame # you an use list comprehension to output the results in a single # line [print(result) for result in results] 我们将得到以下输出:Row(CabNumber='SIR104', AverageFare=111.5) Row(CabNumber='T628190C', AverageFare=109.0) Row(CabNumber='PEACE16', AverageFare=89.7) Row(CabNumber='T439972C', AverageFare=89.5) Row(CabNumber='T802013C', AverageFare=85.0) 我们可以简单地使用 groupBy() 函数按列分组:要计算平均值,首先我们要使用.agg()方法。在方法内部,我们可以指定要计算的聚合函数,本例中是.avg()(平均值)。在Python中,等效于HAVING条件的是.filter()方法,其中可以使用过滤表达式来指定筛选条件。最后,我们使用.sort()方法对数据进行排序,然后使用.take()来提取前五个结果。需要注意的是,.take()函数将返回一个Python列表。由于我们有一个列表,可以使用列表推导式来输出列表中的每个结果。写入到 Delta 表有多种方法可以写入Delta表。您可能希望重新创建整个表,或者只是想追加数据到表中。更高级的主题,如更新和合并,将在第四章中讨论。 首先,我们将清空YellowTaxis表,以便重新开始,然后我们将使用传统的SQL INSERT语句插入数据。接下来,我们将追加来自较小CSV文件的数据。我们还将简要介绍在写入Delta表时的覆盖模式,最后,我们将使用SQL的COPY INTO功能来合并一个大型的CSV文件。清空YellowTaxis表我们可以使用CREATE TABLE语句重新创建我们的Delta表:%sql CREATE TABLE taxidb.YellowTaxis ( RideId INT, VendorId INT, PickupTime TIMESTAMP, DropTime TIMESTAMP, PickupLocationId INT, DropLocationId INT, CabNumber STRING, DriverLicenseNumber STRING, PassengerCount INT, TripDistance DOUBLE, RatecodeId INT, PaymentType INT, TotalAmount DOUBLE, FareAmount DOUBLE, Extra DOUBLE, MtaTax DOUBLE, TipAmount DOUBLE, TollsAmount DOUBLE, ImprovementSurcharge DOUBLE ) USING DELTA LOCATION "/mnt/datalake/book/chapter03/YellowTaxisDelta" 表已准备好,我们可以开始插入数据。使用SQL INSERT插入数据要向YellowTaxis Delta表插入记录,我们可以使用SQL INSERT命令:%sql INSERT INTO taxidb.yellowtaxis (RideId, VendorId, PickupTime, DropTime, PickupLocationId, DropLocationId, CabNumber, DriverLicenseNumber, PassengerCount, TripDistance, RatecodeId, PaymentType, TotalAmount, FareAmount, Extra, MtaTax, TipAmount, TollsAmount, ImprovementSurcharge) VALUES(9999995, 1, '2019-11-01T00:00:00.000Z', '2019-11-01T00:02:23.573Z', 65, 71, 'TAC304', '453987', 2, 4.5, 1, 1, 20.34, 15.0, 0.5, 0.4, 2.0, 2.0, 1.1) 这将插入一行:+---------------------+---------------------+ | num_affected_rows | num_inserted_rows | +---------------------+---------------------+ | 1 | 1 | +---------------------+---------------------+ 使用带有插入的RideId的SQL SELECT语句和WHERE子句来验证数据是否已正确加载:%sql SELECT count(RideId) AS count FROM taxidb.YellowTaxis WHERE RideId = 9999995 输出:+-------+ | count | +-------+ | 1 | +-------+ 输出显示所有数据已正确加载。将DataFrame追加到表中现在让我们将一个DataFrame追加到我们的表中。在这种情况下,我们将从一个CSV文件加载DataFrame。为了正确加载数据,我们不希望自动推断模式。相反,我们将使用我们知道是正确的YellowTaxis表的模式。 我们可以通过从表中加载一个DataFrame轻松提取模式:df = spark.read.format("delta").table("taxidb.YellowTaxis") yellowTaxiSchema = df.schema print(yellowTaxiSchema) 这显示了表的模式如下:root |-- RideId: integer (nullable = true) |-- VendorId: integer (nullable = true) |-- PickupTime: timestamp (nullable = true) |-- DropTime: timestamp (nullable = true) |-- PickupLocationId: integer (nullable = true) |-- DropLocationId: integer (nullable = true) |-- CabNumber: string (nullable = true) |-- DriverLicenseNumber: string (nullable = true) |-- PassengerCount: integer (nullable = true) |-- TripDistance: double (nullable = true) |-- RatecodeId: integer (nullable = true) |-- PaymentType: integer (nullable = true) |-- TotalAmount: double (nullable = true) |-- FareAmount: double (nullable = true) |-- Extra: double (nullable = true) |-- MtaTax: double (nullable = true) |-- TipAmount: double (nullable = true) |-- TollsAmount: double (nullable = true) |-- ImprovementSurcharge: double (nullable = true) 现在我们有了模式,我们可以从追加的CSV文件中加载一个新的DataFrame(df_for_append):df_for_append = spark.read \ .option("header", "true") \ .schema(yellowTaxiSchema) \ .csv("/mnt/datalake/book/data files/YellowTaxis_append.csv") display(df_for_append) 我们看到以下输出(显示部分输出):+---------+-----------+---------------------+---------------------+ | RideId | VendorId | PickupTime | DropTime | +---------+-----------+---------------------+---------------------+ | 9999996 | 1 | 2019-01-01T00:00:00 | 2022-03-01T00:13:13 | +---------+-----------+---------------------+---------------------+ | 9999997 | 1 | 2019-01-01T00:00:00 | 2022-03-01T00:09:21 | +---------+-----------+---------------------+---------------------+ | 9999998 | 1 | 2019-01-01T00:00:00 | 2022-03-01T00:09:15 | +---------+-----------+---------------------+---------------------+ | 9999999 | 1 | 2019-01-01T00:00:00 | 2022-03-01T00:10:01 | +---------+-----------+---------------------+---------------------+ 我们现在有了四行额外的数据,它们都具有VendorId为1。现在我们可以将这个CSV文件追加到Delta表中:df_for_append.write \ .mode("append") \ .format("delta") \ .save("/mnt/datalake/book/chapter03/YellowTaxisDelta") 这将数据直接追加到Delta表中。由于我们在执行INSERT语句之前表中已有一行数据,并且插入了额外的四行数据,所以我们知道YellowTaxis表现在应该有五行数据:%sql SELECT COUNT(*) FROM taxidb.YellowTaxis +----------+ | count(1) | +----------+ | 5 | +----------+ 现在我们有五行数据。在写入Delta表时使用覆盖模式在前面的示例中,我们在使用DataFrameWriter API写入Delta表时使用了.mode("append")。Delta Lake还支持在写入Delta表时使用覆盖模式。当使用此模式时,您将以原子方式替换表中的所有数据。 如果在前一个代码块中使用.mode("overwrite"),我们将用df_for_append DataFrame覆盖整个YellowTaxis Delta表。 即使在代码中使用.mode("overwrite"),旧的分区文件也不会立即物理删除。为了支持时间旅行等功能,这些文件不能立即删除。当确保不再需要这些文件时,我们可以使用像VACUUM这样的命令来物理删除它们。时间旅行和VACUUM命令将在第6章中详细介绍。使用SQL的COPY INTO命令插入数据我们可以使用SQL的COPY INTO命令将数据追加到我们的表中。当我们需要快速追加大量数据时,这个命令特别有用。 我们可以使用以下命令将来自CSV文件的数据追加到表中:%sql COPY INTO taxidb.yellowtaxis FROM ( SELECT RideId::Int , VendorId::Int , PickupTime::Timestamp , DropTime::Timestamp , PickupLocationId::Int , DropLocationId::Int , CabNumber::String , DriverLicenseNumber::String , PassengerCount::Int , TripDistance::Double , RateCodeId::Int , PaymentType::Int , TotalAmount::Double , FareAmount::Double , Extra::Double , MtaTax::Double , TipAmount::Double , TollsAmount::Double , ImprovementSurcharge::Double FROM '/mnt/datalake/book/DataFiles/YellowTaxisLargeAppend.csv' ) FILEFORMAT = CSV FORMAT_OPTIONS ("header" = "true") CSV文件中的所有字段都是字符串,因此在加载数据时,我们需要在SQL SELECT语句中提供某种类型的模式。这会提供每列的类型,以确保我们加载了正确的模式。请注意,在这种情况下指定了FILEFORMAT,即CSV。最后,由于我们的文件有标题行,我们需要在FORMAT_OPTIONS中指定标题行。 该语句的输出为:+---------------------+---------------------+ | num_affected_rows | num_inserted_rows | +---------------------+---------------------+ | 9999995 | 9999995 | +---------------------+---------------------+ 您可以看到,我们仅用几秒钟就插入了近1000万行数据。COPY INTO命令还会跟踪并不会重新加载先前加载过的文件。我们可以通过再次运行COPY INTO命令来测试这一点:+---------------------+---------------------+ | num_affected_rows | num_inserted_rows | +---------------------+---------------------+ | 0 | 0 | +---------------------+---------------------+ 正如您所见,没有加载额外的行。最后,当我们检查最终的行数时,我们会看到现在有一百万行:%sql SELECT COUNT(*) FROM taxidb.YellowTaxis 输出:+-----------+ | count(1) | +-----------+ | 10000000 | +-----------+ 分区Delta表通常采用标准查询模式进行访问。例如,来自物联网系统的数据通常按天、小时甚至分钟进行访问。查询黄色出租车数据的分析师可能希望按VendorId等方式访问数据。这些用例非常适合分区。将数据分区以与查询模式对齐可以显著加快查询性能,特别是当与其他性能优化(例如Z-ordering)结合使用时。Delta表的分区由包含具有相同一个或多个列的值的数据行子集的文件夹组成。例如,对于黄色出租车数据,分区列可以是VendorId。在对表进行分区后,将为每个VendorId创建单独的文件夹。文件夹名称的最后部分将具有VendorId=XX:drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=1 drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=2 drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=4 一旦对表进行了分区,所有包含分区列的谓词的查询将运行得更快,因为Spark可以立即选择具有正确分区的文件夹。您可以在创建Delta表时通过指定PARTITIONED BY子句来进行数据分区。按单一列进行分区让我们拿YellowTaxis表来创建一个按VendorId分区的新版本。首先,创建分区表:%sql CREATE TABLE taxidb.YellowTaxisPartitioned ( RideId INT, VendorId INT, PickupTime TIMESTAMP, DropTime TIMESTAMP, PickupLocationId INT, DropLocationId INT, CabNumber STRING, DriverLicenseNumber STRING, PassengerCount INT, TripDistance DOUBLE, RatecodeId INT, PaymentType INT, TotalAmount DOUBLE, FareAmount DOUBLE, Extra DOUBLE, MtaTax DOUBLE, TipAmount DOUBLE, TollsAmount DOUBLE, ImprovementSurcharge DOUBLE ) USING DELTA PARTITIONED BY(VendorId) LOCATION "/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned" 注意PARTITIONED BY(VendorId)子句。现在您已经有了表,接下来将从旧的YellowTaxis表中加载数据,并将数据写入新表。首先,使用DataFrameReader读取数据:input_df = spark.read.format("delta").table("taxidb.YellowTaxis") 接下来,使用DataFrameWriter将数据写入分区的Delta表:input_df \ .write \ .format("delta") \ .mode("overwrite") \ .save("/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned") 现在,当我们查看表的目录时,我们将看到每个VendorID的子目录:%sh ls -al /dbfs/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned drwxrwxrwx 2 root root 4096 Dec 5 17:39 . drwxrwxrwx 2 root root 4096 Dec 2 19:02 .. drwxrwxrwx 2 root root 4096 Dec 5 16:44 VendorId=1 drwxrwxrwx 2 root root 4096 Dec 5 16:44 VendorId=2 drwxrwxrwx 2 root root 4096 Dec 5 16:44 VendorId=4 drwxrwxrwx 2 root root 4096 Dec 5 16:44 _delta_log 当我们查看不同的VendorId时,我们确实只看到了这三个ID:%sql SELECT DISTINCT(VendorId) FROM taxidb.YellowTaxisPartitioned; 我们会看到相同的ID:+----------+ | VendorId | +----------+ | 2 | | 1 | | 4 | +----------+ VendorId子目录包含个别的Parquet文件,如图所示,VendorId=4的情况:%sh ls -al /dbfs/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned/VendorId=4 total 3378 drwxrwxrwx 2 root root 4096 Dec 5 17:41 . drwxrwxrwx 2 root root 4096 Dec 5 17:39 .. -rwxrwxrwx 1 root root 627551 Dec 5 17:41 part-00000-...parquet -rwxrwxrwx 1 root root 618844 Dec 5 17:41 part-00001-...parquet -rwxrwxrwx 1 root root 616377 Dec 5 17:41 part-00002-...parquet -rwxrwxrwx 1 root root 614035 Dec 5 17:41 part-00003-...parquet -rwxrwxrwx 1 root root 612410 Dec 5 17:41 part-00004-...parquet -rwxrwxrwx 1 root root 360432 Dec 5 17:41 part-00005-...parquet 多列分区不必仅通过一个列进行分区。我们可以使用多个层次的列作为分区列。例如,对于物联网数据,我们可能希望按天、小时和分钟进行分区,因为这是最常用的查询模式。举个例子,假设我们不仅希望将YellowTaxis表按VendorId分区,还希望按RateCodeId分区。首先,我们需要删除现有的YellowTaxisPartitioned表及其底层文件。接下来,我们可以重新创建该表:%sql -- Create the table CREATE TABLE taxidb.YellowTaxisPartitioned ( RideId INT, … ) USING DELTA PARTITIONED BY(VendorId, RatecodeId) -- Partition by VendorId AND rateCodeId LOCATION "/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned" 注意更新的分区子句:PARTITIONED BY(VendorId, RatecodeId)。 完成这一步之后,我们可以以与之前相同的方式重新加载表格。一旦表格加载完成,我们可以再次查看目录结构。第一层看起来仍然相同:%sh ls -al /dbfs/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned drwxrwxrwx 2 root root 4096 Dec 13 15:33 . drwxrwxrwx 2 root root 4096 Dec 2 19:02 .. drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=1 drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=2 drwxrwxrwx 2 root root 4096 Dec 13 15:16 VendorId=4 drwxrwxrwx 2 root root 4096 Dec 13 15:16 _delta_log 当我们查看VendorId=1的目录时,我们可以看到按RatecodeId进行的分区:%sh ls -al /dbfs/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned/VendorId=1 drwxrwxrwx 2 root root 4096 Dec 13 15:35 . drwxrwxrwx 2 root root 4096 Dec 13 15:33 .. drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=1 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=2 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=3 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=4 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=5 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=6 drwxrwxrwx 2 root root 4096 Dec 13 15:16 RatecodeId=99 最后,当我们在RatecodeId级别进行查询时:%sh ls -al /dbfs/.../chapter03/YellowTaxisDeltaPartitioned/VendorId=1/RatecodeId=1 我们可以看到该分区的Parquet文件:drwxrwxrwx 2 root root 4096 Dec 13 15:35 . drwxrwxrwx 2 root root 4096 Dec 13 15:35 .. -rwxrwxrwx 1 root root 10621353 Dec 13 15:35 part-00000-...parquet -rwxrwxrwx 1 root root 10547673 Dec 13 15:35 part-00001-...parquet -rwxrwxrwx 1 root root 10566377 Dec 13 15:35 part-00002-...parquet -rwxrwxrwx 1 root root 10597523 Dec 13 15:35 part-00003-...parquet -rwxrwxrwx 1 root root 10570937 Dec 13 15:35 part-00004-...parquet -rwxrwxrwx 1 root root 6119491 Dec 13 15:35 part-00005-...parquet -rwxrwxrwx 1 root root 13820133 Dec 13 15:35 part-00007-...parquet -rwxrwxrwx 1 root root 24076060 Dec 13 15:35 part-00008-...parquet -rwxrwxrwx 1 root root 6772609 Dec 13 15:35 part-00009-...parquet 检查分区是否存在要确定表是否包含特定分区,您可以使用以下语句:SELECT COUNT(*) > 0 FROM <table-name> WHERE <partition-column> = <value> 如果分区存在,将返回true。以下SQL语句检查VendorId = 1和RatecodeId = 99的分区是否存在:%sql SELECT COUNT(*) > 0 AS `Partition exists` FROM taxidb.YellowTaxisPartitioned WHERE VendorId = 2 AND RateCodeId = 99 这将返回true,因为正如之前所示,这个分区是存在的。有选择性地使用replaceWhere更新Delta分区在前一部分,我们看到了通过分区数据可以显著加快查询操作的方法。我们还可以使用replaceWhere选项有选择性地更新一个或多个分区。有选择地将更新应用于某些分区并不总是可能的;有些更新需要应用于整个数据湖。但是,在适用的情况下,这些有选择性的更新可以带来显著的性能提升。Delta Lake可以以卓越的性能更新分区,同时保证数据完整性。要查看replaceWhere的实际应用,让我们来看一个特定的分区:%sql SELECT RideId, VendorId, PaymentType FROM taxidb.yellowtaxispartitioned WHERE VendorID = 1 AND RatecodeId = 99 LIMIT 5 在结果中,我们看到了各种支付类型的混合。+---------+----------+-------------+ | RideId | VendorId | PaymentType | +---------+----------+-------------+ | 1137733 | 1 | 1 | | 1144423 | 1 | 2 | | 1214030 | 1 | 1 | | 1223028 | 1 | 1 | | 1300054 | 1 | 2 | +---------+----------+-------------+ 假设我们有一个业务原因,规定了VendorId = 1和RatecodeId = 9的所有PaymentTypes应为3。我们可以使用以下PySpark表达式和replaceWhere来实现这个结果:from pyspark.sql.functions import * spark.read \ .format("delta") \ .load("/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned") \ .where((col("VendorId") == 1) & (col("RatecodeId") == 99)) \ .withColumn("PaymentType", lit(3)) \ .write \ .format("delta") \ .option("replaceWhere", "VendorId = 1 AND RateCodeId = 99") \ .mode("overwrite") \ .save("/mnt/datalake/book/chapter03/YellowTaxisDeltaPartitioned") 现在当我们查找这个分区的不同PaymentTypes时:%sql SELECT DISTINCT(PaymentType) FROM taxidb.yellowtaxispartitioned WHERE VendorID = 1 AND RatecodeId = 99 我们可以看到,我们只有PaymentType = 3。+-------------+ | PaymentType | +-------------+ | 3 | +-------------+ 我们可以验证其他分区未受影响:%sql SELECT DISTINCT(PaymentType) FROM taxidb.yellowtaxispartitioned ORDER BY PaymentType 这显示了所有的PaymentTypes:+-------------+ | PaymentType | +-------------+ | 1 | | 2 | | 3 | | 4 | +-------------+ 当您需要运行一个计算成本较高的操作,但只需要在特定分区上运行时,replaceWhere可以特别有用。在黄色出租车的情景中,假设数据科学团队要求您在YellowTaxis表上运行他们的算法。最初,您可以在最小的分区上运行它,快速检索结果,得到批准后,可以在整个剩余的分区上进行算法运行,通常在夜间执行。用户定义的元数据出于审计或法规遵从的目的,我们可能希望为某些SQL操作添加标签。例如,我们的项目可能要求您在向某些表插入数据时使用通用数据保护法规(GDPR)标签。一旦我们用这个标签标记了INSERT操作,审计工具将能够生成包含这个特定标签的语句的完整列表。我们可以在由SQL操作生成的元数据提交中指定这些标签作为用户自定义的字符串。我们可以使用DataFrameWriter的选项userMetadata,或者SparkSession配置spark.databricks.delta.commitInfo.userMetadata来实现这一点。如果两个选项都被指定,DataFrameWriter的选项将优先。使用SparkSession设置自定义元数据首先让我们看一下SparkSession配置。假设我们有一个INSERT操作,我们想要为审计目的分配一个GDPR标签。以下是一个SQL示例:%sql SET spark.databricks.delta.commitInfo.userMetadata=my-custom-metadata= { "GDPR": "INSERT Request 1x965383" }; 这个标签将应用于接下来的操作,这是一个标准的INSERT操作:INSERT INTO taxidb.yellowtaxisPartitioned (RideId, VendorId, PickupTime, DropTime, PickupLocationId, DropLocationId, CabNumber, DriverLicenseNumber, PassengerCount, TripDistance, RatecodeId, PaymentType, TotalAmount, FareAmount, Extra, MtaTax, TipAmount, TollsAmount, ImprovementSurcharge) VALUES(10000000, 3, '2019-11-01T00:00:00.000Z', '2019-11-01T00:02:23.573Z', 65, 71, 'TAC304', '453987', 2, 4.5, 1, 1, 20.34, 15.0, 0.5, 0.4, 2.0, 2.0, 1.1)
0
0
0
浏览量1337
攻城狮无远

《Delta Lake Up & Running》第九章:Delta Sharing

今天经济的数据中心特性需要组织与其客户、供应商和合作伙伴之间进行广泛的数据交流。虽然效率和即时可访问性至关重要,但它们常常与安全性的考虑发生冲突。组织需要一种开放而安全的数据共享方法,以在数字经济中蓬勃发展。通常情况下,数据共享需要在组织内部进行。组织拥有地理分散的位置和本地云解决方案。这些公司通常希望实施数据网格架构,其中所有权是分散的,数据管理是分布式和联合的。高效而安全的数据共享是有效地在整个组织中共享数据产品的关键推动因素。企业中的不同业务组需要访问数据以做出关键的业务决策。数据团队希望集成他们的解决方案,以创建业务的全面企业视图。传统的数据共享方法过去,在不同平台、公司和云之间共享数据一直是一个复杂的挑战。由于担心安全风险、竞争以及实施数据共享解决方案的巨大成本,组织不愿意共享数据。传统的数据共享技术在满足现代需求方面面临困难,例如与多个云环境兼容,支持开放格式,同时提供所需的性能。许多数据共享解决方案与特定供应商绑定,这对于在不兼容平台上运作的数据提供者和消费者造成了问题。数据共享解决方案以三种格式发展起来:传统的和自制(定制构建)的解决方案、现代云对象存储和专有商业解决方案。每种方法都有其利弊。传统和自制的解决方案组织已经构建了基于传统技术(如电子邮件、SFTP或自定义API)的自制系统来实现数据共享解决方案,如图9-1所示。这些解决方案的优势包括:供应商无关: FTP、电子邮件和API是有着良好文档记录的协议,使数据使用者能够利用各种客户端访问提供给他们的数据。灵活性: 许多自定义解决方案基于开源技术,使它们能够在本地和云环境中都能运行。这些解决方案的劣势包括:数据移动: 从云存储中提取数据、进行转换,并将其托管在FTP服务器上供不同接收方使用需要大量工作。这种方法还导致数据重复,阻碍组织即时访问实时数据。数据共享的复杂性: 自制解决方案通常涉及复杂的体系结构,因为涉及到复制和供应。这种复杂性会在数据共享活动中增加相当多的时间,并可能导致最终用户使用过时的数据。数据接收方的运营开销: 数据接收方需要执行数据提取、转换和加载(ETL)以适应其特定用例,进一步延迟了获取见解的时间。每当提供者更新数据时,消费者必须反复运行ETL管道。安全性和治理: 随着现代数据要求变得更加严格,保护和管理自制和传统技术变得越来越具有挑战性。可扩展性: 管理和维护这样的解决方案是昂贵的,它们缺乏处理大型数据集的可扩展性。专有供应商解决方案商业数据共享解决方案被广泛选择,是企业寻求在不建设内部解决方案的情况下寻找替代方案的一种选择。这些解决方案在不愿意为开发专有解决方案分配大量时间和资源的同时,又希望获得比云对象存储提供的更大控制力的情况下提供了一种平衡,如图9-2所示。这种解决方案的优势:简便性商业解决方案为用户提供了一种轻松的方式,使他们可以与同一平台上的其他用户共享数据。这种解决方案的劣势:供应商锁定商业解决方案通常缺乏与其他平台的互操作性,使得与使用竞争性解决方案的用户共享数据变得困难。这种限制降低了数据的可访问性,导致供应商锁定。此外,数据提供方和接收方之间的平台差异引入了数据共享的复杂性。数据移动数据需要加载到特定的平台上,这涉及额外的步骤,如ETL和创建数据副本。可扩展性商业数据共享解决方案可能受到供应商强加的扩展限制。成本上述挑战为与潜在客户共享数据增加了额外的成本,因为数据提供方需要在各种云平台上为不同的接收方复制数据。云对象存储云对象存储被认为是云环境中非常合适的解决方案,因为它具有弹性和无缝可扩展性,可以处理大量数据并轻松适应无限增长。领先的云提供商,如Amazon S3、Azure Data Lake Storage (ADLS)和Google Cloud Storage (GCS),提供了经济高效的对象存储服务,具有卓越的可扩展性和可靠性。 云对象存储的一个显著特点是能够生成带有签名的URL。这些URL为下载特定对象提供了有限时间的权限。通过共享预签名的URL,任何持有该URL的人都可以方便地访问指定的对象,促进了高效的数据共享。这种解决方案的优势有:就地共享数据:对象存储可以就地共享,使消费者能够访问最新的可用数据。可扩展性:云对象存储受益于通常在本地无法实现的可用性和耐久性保证。数据消费者直接从云提供商检索数据,为提供商节省了带宽。这种解决方案的缺点有:限于单一云提供商:接收方必须在同一云上才能访问这些对象。繁琐的安全性和治理:分配权限和管理访问存在复杂性。需要自定义应用程序逻辑来生成带有签名的URL。复杂性:管理数据共享的角色(数据库管理员、分析师)发现很难理解身份和访问管理策略以及数据是如何映射到底层文件的。对于拥有大量数据的公司,通过云存储共享是耗时、繁琐且几乎不可能扩展的。对于数据接收方来说,必须在原始文件上运行ETL管道,然后才能用于其最终用例。这种解决方案的不完整性和繁琐性阻碍了数据提供方和接收方轻松共享数据。开源 Delta Sharing不同于专有解决方案,开源数据共享不涉及特定厂商技术,不引入不必要的限制和财务负担。开源 Delta Sharing 对任何需要大规模共享数据的人都是可用的。Delta Sharing 的目标Delta Sharing 是一个开源协议,旨在实现以下目标:开放的跨平台数据共享: Delta Sharing 提供了一个开源、跨平台的解决方案,避免了供应商锁定。它允许在 Delta Lake 和 Apache Parquet 格式中与任何平台共享数据,无论是在本地还是在其他云上。在不移动数据的情况下共享实时数据: 数据接收方可以直接连接到 Delta Sharing,无需复制数据。这个特性实现了现有数据的轻松实时共享,避免了不必要的数据复制或移动。支持各种客户端: Delta Sharing 支持多种客户端,包括流行的工具如 Power BI、Tableau、Apache Spark、pandas 和 Java。它为使用不同用例的首选工具消费数据提供了灵活性,如商业智能、机器学习和人工智能。实现 Delta Sharing 连接器是快速而简单的。集中的治理: Delta Sharing 提供强大的安全性、审计和治理功能。数据提供方可以对数据访问进行细粒度控制,允许他们共享整个表或表的特定版本或分区。对共享数据的访问从单一执行点进行管理和审计,确保了集中的控制和合规性。大规模数据集的可扩展性: Delta Sharing 被设计用于处理大规模的结构化数据集,并支持共享非结构化数据以及未来的数据派生物,如机器学习模型、仪表板、笔记本和表格数据。Delta Sharing 利用云存储系统的经济性和可扩展性,实现了大规模数据集的经济可靠共享。Delta Sharing 的内部机制Delta Sharing 是一个开放的协议,定义了 REST API 端点,使得能够安全地访问云数据集的特定部分。它利用现代云存储系统(如Amazon S3、ADLS或GCS)的能力,以确保可靠地传输大型数据集。这个过程涉及到两个关键的角色:数据提供方和接收方,如图 9-3 所示。数据提供方和接收方作为数据提供方,Delta Sharing 允许您共享存储在云数据湖中的 Delta Lake 格式的现有表或其部分(例如,特定表的版本或分区)。数据提供方决定要共享的数据,并在其前端运行一个实现 Delta Sharing 协议并管理接收方访问权限的共享服务器。开源的 Delta Lake 包含一个参考共享服务器,Databricks 为其平台提供了一个,其他供应商预计很快也会跟进。作为数据接收方,您只需使用支持该协议的众多 Delta Sharing 客户端之一。开源的 Delta Lake 已发布了 pandas、Apache Spark、Rust 和 Python 的开源连接器,并正在与合作伙伴合作开发更多的客户端。实际的交换经过精心设计,通过利用云存储系统和 Delta Lake 的功能,确保了其效率。Delta Sharing 协议的工作方式如下(参见图 9-4):接收方的客户端进行身份验证到共享服务器(通过令牌或其他方法),并请求查询特定表。客户端还可以提供数据过滤器(例如,“country = US”)作为读取数据子集的提示。服务器验证客户端是否被允许访问数据,记录请求,然后确定要发送回的数据。这将是组成表的 ADLS(在 Azure 上)、S3(在 AWS 上)或 GCS(在 GCP 上)中的 Parquet 文件的子集。为了传输数据,服务器生成短暂的预签名 URL,允许客户端直接从云提供商读取这些 Parquet 文件,以便传输可以并行进行,具有大带宽,而无需通过共享服务器进行流式传输。这一功能在所有主要云中都可用,使得共享非常大型的数据集变得快速、便宜和可靠。设计的好处Delta Sharing 的设计为提供方和消费方带来了许多好处:数据提供方可以轻松共享整个表,或者仅表的某个分区的版本,因为客户端只能访问其中的特定子集对象。数据提供方可以使用 Delta Lake 上的 ACID 事务可靠地实时更新数据,接收方始终看到一致的视图。数据接收方不需要与提供方在同一平台上,甚至不需要在云中,共享可以在云之间甚至从云到本地用户之间进行。Delta Sharing 协议对于已经使用 Parquet 的客户端来说很简单实现。使用底层的云系统进行数据传输是快速、便宜、可靠且可并行化的。delta-sharing 代码仓库delta-sharing 代码仓库可以在 GitHub 上找到。其中包含以下组件:Delta Sharing 协议规范。Python 连接器。这是一个实现 Delta Sharing 协议的 Python 库,用于将共享的表读取为 pandas 或 PySpark 数据框。Apache Spark 连接器。该连接器实现了 Delta Sharing 协议,用于从 Delta Sharing 服务器读取共享的表。然后,您可以使用 SQL、Python、Scala、Java 或 R 访问这些表。Delta Sharing 协议的参考实现在 Delta Sharing 服务器中。用户可以部署此服务器来共享存储在 Azure、AWS 或 GCP 存储系统中的现有 Delta Lake 和 Parquet 格式的表。接下来,让我们使用 Python 连接器访问由 delto-io 提供的示例 Delta Sharing 服务器中的 Delta 表。第一步:安装 Python 连接器Python 连接器以 delta-sharing 作为 PyPi 库提供,因此我们只需将此库添加到我们的集群中,如图 9-5 所示。步骤 2:安装配置文件Python 连接器根据配置文件访问共享表。您可以通过访问链接下载示例 Delta Sharing Server 的配置文件。该文件将下载为名为 open-datasets.share 的文件。这是一个包含服务器凭据的简单 JSON 文件(在此示例中,令牌已模糊处理):{ "shareCredentialsVersion": 1, "endpoint": "https://sharing.delta.io/delta-sharing/", "bearerToken": "faaiexxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } 使用 dbfs cp 命令将 share 文件上传到 Databricks 文件系统中的 dbfs:/ 位置:C:\Users\bhael\Downloads>dbfs cp open-datasets.share dbfs:/mnt/.../delta-sharing/ C:\Users\bhael\Downloads>dbfs ls dbfs:/mnt/datalake/book/delta-sharing open-datasets.share C:\Users\bhael\Downloads> 步骤 3:读取共享表在 "01 - Sharing Example" 笔记本中,我们可以引用该文件:# Point to the profile file. It can be a file on the local # file system or remote file system. In this case, we have # uploaded the file to dbfs profile_path = "/dbfs/mnt/datalake/book/delta-sharing/open-datasets.share" 接下来,我们可以创建一个 SharingClient,将配置文件路径传递给它,并列出所有共享的 Delta 表:# Create a SharingClient and list all shared tables client = delta_sharing.SharingClient(profile_path) client.list_all_tables() 这将生成以下输出:Out[22]: [Table(name='COVID_19_NYT', share='delta_sharing', schema='default'), Table(name='boston-housing', share='delta_sharing', schema='default'), Table(name='flight-asa_2008', share='delta_sharing', schema='default'), Table(name='lending_club', share='delta_sharing', schema='default'), Table(name='nyctaxi_2019', share='delta_sharing', schema='default'), Table(name='nyctaxi_2019_part', share='delta_sharing', schema='default'), Table(name='owid-covid-data', share='delta_sharing', schema='default')] 要创建指向共享表的URL,我们使用以下语法:<profile file base name>#<share-name>.<schema-name>.<table-name> 我们现在可以构建URL并将共享的Delta表的内容读取为pandas DataFrame:# Create a URL to access a shared table # A table path is the profile path following with # ('<share-name>.<schema_name>.<table_name>) # Here, we are loading the table as a pandas DataFrame table_url = profile_path + "#delta_sharing.default.boston-housing" df = delta_sharing.load_as_pandas(table_url, limit=10) df.head() 输出(仅显示相关部分):+--+-------+----+-----+-----+-----+-----+ |ID|crim |zn |indus| chas| nox | rm | +--+-------+----+-----+-----+-----+-----+ |1 |0.00632|18 | 2.31| 0 |0.538|6.575| |2 |0.02731| 0 | 7.0 | 0 |0.469|6.421| |4 |0.03237| 0 | 2.18| 0 |0.458|6.998| |5 |0.06905| 0 | 2.18| 0 |0.458|7.147| |7 |0.08829|12.5| 7.87| 0 |0.524|6.012| +--+-------+----+-----+-----+-----+-----+ 如果我们想将表加载为标准的 PySpark DataFrame,可以使用 load_as_spark() 方法:# We can also access the shared table with Spark. Note that we have to use the # dbfs:/ path prefix here profile_path_spark = "dbfs:/mnt/datalake/book/delta-sharing/open-datasets.share" table_url_spark = profile_path_spark + "#delta_sharing.default.boston-housing" df_spark = delta_sharing.load_as_spark(table_url_spark) display(df_spark.limit(5)) 注意 URL 中的轻微更改,如前所讨论。这将产生与 pandas 示例相同的输出。总结利用开源技术实现数据交换为内部和外部使用打开了许多优势。首先,它提供了显著的灵活性,允许团队定制数据交换流程以满足特定的业务用例和要求。来自活跃的开源社区的支持确保持续改进、修复错误,并获得大量知识,进一步增强了团队和业务用户保持在数据共享实践前沿的能力。在使用Delta Sharing进行数据提供者和数据接收者时,以下是最重要的几个关键优势: 可扩展性对于处理不断增长的数据集和高需求的用例至关重要。 互操作性是另一个重要的优势。Delta Sharing作为一种开源技术,旨在与数据生态系统的其他组件协同工作,促进无缝集成。 此外,与专有解决方案相比,透明度和安全性得到了提高,因为Delta Sharing的源代码可以进行审查,这允许采取更强的安全措施,并能够响应并主动解决已识别的漏洞。 通过使用Delta Sharing,团队避免了对供应商的依赖,因为他们可以自由切换工具或供应商,而无需投资于适应新架构。开源社区中创新的快速步伐使团队能够拥抱尖端功能,并迅速适应数据管理和分析领域的新趋势。 利用Delta Sharing进行数据共享使数据生态系统更加敏捷、具有成本效益,并为在不断变化的环境和数据格局中实现组织成功提供了更好的数据驱动解决方案和洞察。在已学到的基础组件基础上,第10章将深入探讨如何构建完整的数据湖仓。
0
0
0
浏览量1983
攻城狮无远

《Delta Lake Up & Running》第十章:构建在Delta Lake上的数据湖仓

第1章介绍了数据湖仓的概念,它结合了传统数据仓库和数据湖的最佳元素。在本书中,您学到了支持湖仓架构的五个关键功能:存储层、数据管理、SQL分析、数据科学和机器学习,以及勋章架构。在深入探讨在Delta Lake上构建湖仓之前,让我们快速回顾一下行业数据管理和分析的演进:数据仓库:从1970年代到21世纪初,数据仓库的设计旨在将数据收集和 consolida 到业务背景中,为业务智能和分析提供支持。随着数据量的增长,速度、多样性和真实性也在增加。数据仓库在以灵活、统一和经济的方式解决这些要求方面存在挑战。数据湖:在21世纪初,数据量的增加推动了数据湖的发展(最初在Hadoop上以及后来在云上),这是一个成本效益的中央存储库,可以以任何规模存储任何格式的数据。但是,即使带来了额外的好处,仍然存在额外的挑战。数据湖没有事务支持,不适用于业务智能,提供有限的数据治理支持,并且仍然需要其他技术(例如数据仓库)来充分支持数据生态系统。这导致了一个过于复杂的环境,其中包含不同的工具、系统、技术和多份数据的拼贴。湖仓:在2010年代末,湖仓的概念出现了。这引入了数据仓库的现代化版本,提供了所有的优势和功能,而不会影响数据湖的灵活性。湖仓利用了低成本、灵活的云存储层,结合了数据湖,并通过支持ACID事务的技术提供了数据的可靠性和一致性保证。这种灵活性有助于支持流处理、分析和机器学习等多样的工作负载,所有这些工作负载都在单一的统一平台下实现,最终实现对所有数据资产的单一安全性和治理方法。随着Delta Lake和湖仓的出现,由于这种架构模式启用的关键特性,端到端数据平台的范式已经开始发生变化。通过将湖仓的能力与本书学到的内容相结合,您将学习如何启用湖仓架构提供的关键功能,并完全上手Delta Lake。存储层在任何设计良好的架构中,第一步或第一层是决定在哪里存储您的数据。在一个不断增加的数据量以不同形式和形状从多个异构数据源进入的世界中,有一个允许以灵活、经济且可扩展的方式存储大量数据的系统是至关重要的。这就是为什么像数据湖这样的云对象存储是湖仓的基础存储层的原因。什么是数据湖?如前述于第1章中定义的,数据湖是一个经济高效的中央存储库,可存储任何规模的结构化、半结构化或非结构化数据,以文件和数据块的形式存在。这在数据湖中是可能的,因为在写入数据时它不强制使用模式,所以数据可以按原样保存。数据湖使用扁平的体系结构和对象存储来存储数据,与数据仓库不同,数据仓库通常将数据存储在具有目录和文件的分层结构中并强制使用模式。每个对象都附带有元数据和唯一标识符,以便应用程序可以轻松访问和检索。数据的类型结构化数据:结构化数据具有预定义的结构或模式。这种数据通常是关系数据库中以表格形式呈现的,包含行和列。半结构化数据:半结构化数据不符合典型的关系格式,而是以模式或标签松散结构化,例如键/值对。半结构化数据的示例包括Parquet、JSON、XML、CSV文件,甚至电子邮件或社交媒体信息。非结构化数据:非结构化数据不包含有组织的结构,没有任何模式或模板。它通常以媒体文件的形式呈现,例如照片(例如JPEG)或视频文件(例如MP4)。组成视频的数据本身是非结构化的。非结构化和半结构化数据通常对于AI和机器学习用例至关重要,而结构化和半结构化数据对于BI用例至关重要。由于Delta Lake本地支持这三种数据分类,因此可以创建一个支持数据湖中这些多样化工作负载的统一系统。这些工作负载可以在设计良好的处理架构中相互补充,您将在本章的后面学到更多内容。数据湖有助于解决与数据量、类型和成本相关的许多挑战,而Delta Lake在数据湖之上运行,优化为在云数据湖上最佳运行。云数据湖的关键优势云数据湖相对于本地数据湖或数据仓库有助于最好地支持湖屋架构的存储层,原因如下:单一存储层: 湖屋的最重要特征之一是统一平台,云数据湖有助于消除和 consol 种数多的数据孤岛和不同类型的数据到一个单一的对象存储中。云数据湖允许您在单一系统上处理所有数据,防止在不同系统之间来回传输数据的额外复制。数据在不同系统之间的移动减少,也意味着错误发生的地方更少。单一存储层减少了需要覆盖不同系统的多个安全策略,并有助于解决在系统之间进行协作时的困难。它还为数据消费者提供了一个查找所有数据源的单一位置。灵活的按需存储层: 无论是速度(流式处理还是批处理)、容量还是种类(结构化还是非结构化),云数据湖都允许以最终的灵活性存储数据。根据Rukmani Gopalan在他最近出版的书《云数据湖》中的说法,"这些系统设计用于处理以任何速度进入数据湖的数据:连续发射的实时数据以及按计划定期摄入的大量数据。" 不仅数据灵活,基础设施也很灵活。云提供商允许您按需配置基础设施,并迅速弹性地扩展或缩小。由于这种程度的灵活性,组织可以拥有一个提供无限可扩展性的单一存储层。解耦的存储和计算: 传统的数据仓库和本地数据湖通常具有紧密耦合的存储和计算。存储通常是廉价的,而计算则不是。云数据湖允许您解耦这一点,并独立扩展存储,以极低的成本存储大量数据。技术集成: 数据湖通过标准化的API提供简单的集成,因此具有完全不同的技能、工具和编程语言的组织和用户(例如SQL、Python、Scala等)可以同时执行不同的分析任务。复制: 云提供商提供了易于设置的数据湖到不同地理位置的复制。启用复制的简便性使其在满足合规性要求、业务关键操作的故障转移、灾难恢复以及通过将对象存储在不同位置降低延迟方面非常有用。可用性: 大多数云系统为云数据湖提供不同类型的数据可用性。这意味着对于很少访问的或已归档的数据,与频繁访问的“热”数据相比,您可以设置生命周期策略。这些生命周期策略允许您将数据移动到不同的存储可用性类别(对于很少访问的数据有更低的成本),以满足合规性、业务需求和成本优化。成本: 使用云数据湖,您通常按使用付费,因此您的成本始终与您的数据量保持一致。由于只有一个存储层,数据在不同系统之间的移动减少,可用性设置和存储与计算解耦,因此您只需为数据存储支付隔离和最小化的费用。对于更大的成本分配,大多数云数据湖提供桶或容器(文件系统,不要与应用程序容器混淆)来存储数据的不同层次(例如原始与转换数据)。这些容器允许您对组织的不同区域进行更精细的成本分配。由于数据源和数据量呈指数级增长,因此在不限制可以存储的数据的体积或种类的情况下,非常重要的是分配和优化成本。图10-1展示了在撰写本文时流行的云数据湖的不同类型,以及存储在其中的不同类型的数据。总的来说,存储层是湖屋架构的关键组成部分,因为它使组织能够以经济高效和可扩展的方式存储和管理大量数据。现在您已经有了一个明确定义的存储数据的地方,接下来需要适当地管理它。数据管理尽管云数据湖允许弹性地以其原生格式存储大规模数据,但湖屋架构的下一部分是促进数据管理。根据Gartner的定义,数据管理(DM)包括在企业中跨数据主题区域和数据结构类型的全谱中实现对数据一致访问和交付的实践、架构技术和工具,以满足所有应用程序和业务流程的数据消耗需求。数据管理对于任何组织都是关键的功能,很大程度上取决于在数据访问和交付中使用的工具和技术。传统上,数据湖将数据简单地管理为半结构化格式(例如Parquet)中的“一堆文件”,这使得难以实现数据管理的一些关键功能,例如可靠性,由于缺乏对ACID事务、模式强制执行、审计跟踪以及数据集成和互操作性的支持。数据湖上的数据管理始于可靠性的结构化事务层。这种可靠性来自支持ACID事务、开放表格格式、批处理和流处理数据处理之间集成以及可伸缩的元数据管理的事务层。这就是Delta Lake作为湖屋架构的核心组件引入的地方,支持数据管理。在图10-2中,您可以看到云数据湖支持的不同数据类型,Delta Lake建立在数据湖之上,充当结构化事务层。Delta Lake为您的数据湖带来耐久性、可靠性和一致性。Delta Lake有几个关键元素有助于促进数据管理:元数据:Delta Lake的核心是元数据层。这一层提供了广泛且可扩展的元数据跟踪,使Delta Lake的大多数核心功能得以实现。它提供了一种抽象层,以实施ACID事务和各种其他管理功能。这些元数据层也是启用治理功能(例如访问控制和审计日志记录)的自然场所。ACID事务:Delta Lake将确保在单个表上存在并发事务时,数据保持完整。对ACID事务的支持为数据湖带来一致性和可靠性,这是通过事务日志实现的。事务日志跟踪所有提交,并使用隔离级别保证数据的一致性和准确的数据视图。版本控制和审计历史:由于Delta Lake将有关哪些文件属于表的信息存储为事务日志,它允许用户查询旧版本的数据并执行回滚,也称为时间旅行。这为业务需求或法规要求提供了数据的完整审计跟踪,并且还可以支持机器学习过程。数据集成:这可以定义为将不同来源的数据 cons 收集到单一位置。Delta Lake可以处理表上的并发批处理和流处理操作;它为用户提供了查看数据的单一位置。不仅如此,执行INSERT、UPDATE和DELETE等DML操作的能力使您能够执行有效的数据集成并在所有表上保持一致性。模式强制执行和演变:与传统的RDBMS系统一样,Delta Lake提供了模式强制执行以及灵活的模式演变。这有助于通过约束保证数据的清洁和一致的数据质量,同时为由其他进程和上游来源引起的模式漂移提供了灵活性。数据互操作性:Delta Lake是一个与云无关的开源框架,由于它通过提供一组API和扩展与Apache Spark无缝交互,因此在项目、其他API和平台之间具有大量不同的集成能力。这使您能够通过不同的工具和框架轻松集成现有的数据管理工作流和流程。Delta UniForm与Apache Iceberg提供格式互操作性,进一步扩展了可以集成的系统和工具的范围。与使数据跨组织安全共享变得简单的Delta Sharing一起,Delta Lake避免了供应商锁定,并实现了互操作性。机器学习:因为机器学习(ML)系统通常需要处理大量数据并使用传统SQL不太适用于的复杂逻辑,它们现在可以轻松地使用Delta Lake的DataFrame API访问和处理数据。这些API允许ML工作负载直接受益于Delta Lake提供的优化和功能。现在可以将所有不同工作负载的数据管理集中到Delta Lake中。通过添加一个更适合处理数据管理的结构化事务层,湖屋同时支持原始数据、为分析策划数据的ETL/ELT流程以及ML工作负载。通过Delta Lake提供的数据管理功能对数据进行整理的ETL/ELT传统上被认为是在数据仓库的背景下进行的,但通过Delta Lake,您可以将这些过程集中到一个地方。这也使ML系统能够直接受益于Delta Lake提供的功能和优化,以完成跨不同工作负载的数据管理和整合。通过结合所有这些努力,您可以为数据湖带来更大的可靠性和一致性,并创建一个在企业中成为所有类型数据的单一真相来源的湖屋。SQL分析在数据分析、商业智能和数据仓库领域,通常被认为 SQL 是最常见且灵活的语言之一。部分原因不仅在于它提供了易于学习的曲线和低门槛的入口,还因为它能执行复杂的数据分析操作。SQL 不仅允许用户快速与数据互动,还让各技能水平的用户编写即席查询、为 BI 工具准备数据、创建报告和仪表板以及执行各种数据分析功能。由于这样的原因,SQL 已成为从数据工程师到业务用户等所有人的商业智能和数据仓库首选的语言。这也是湖屋架构在可伸缩性和性能方面实现出色 SQL 性能并实现 SQL 分析的原因。幸运的是,以 Delta Lake 为事务层的湖屋架构具有可伸缩的元数据存储,可通过 Apache Spark 和 Spark SQL 轻松访问。通过 Spark SQL 进行 SQL 分析Spark SQL 是 Apache Spark 的一个模块,也被称为库,用于处理结构化数据。它提供了一个编程接口,使用 SQL 和 DataFrame API 处理结构化数据,所有这些都由 Spark SQL 引擎支持。与其他 SQL 引擎类似,Spark SQL 引擎负责生成高效的查询和紧凑的代码。这个执行计划在运行时进行调整。Apache Spark 生态系统由不同的库组成,其中 Spark Core 和 Spark SQL 引擎是它们构建的基础(图 10-3)。Spark SQL 库支持 ANSI SQL,允许用户使用他们熟悉的 SQL 语法查询和分析数据。正如我们在前几章中看到的,Delta 表可以使用 Spark SQL 和 PySpark 中的 sql() 方法轻松查询,例如:%python spark.sql("SELECT count(*) FROM taxidb.tripData") 或者,类似于本书中的大多数查询的编写方式,您可以在笔记本或某些集成开发环境 (IDEs) 中使用魔术命令和 %sql 来指定语言引用,然后直接在单元格中编写 Spark SQL:%sql SELECT count(*) as count FROM taxidb.tripData 不仅如此,Spark SQL 库还允许您使用 DataFrame API 与数据集和表进行交互:%python spark.table("taxidb.tripData").count() 分析师、报告生成者和其他数据使用者通常会通过 SQL 接口与数据进行交互。这个 SQL 接口意味着用户可以利用 Spark SQL 在 Delta 表上执行简单或复杂的查询和分析,充分利用 Delta 表提供的性能和可扩展性,同时还能利用 Spark SQL 引擎、分布式处理和优化。由于 Delta Lake 确保了串行性,因此完全支持并发读取和写入。这意味着所有数据使用者可以在通过不同的 ETL 工作负载更新数据的同时自信地读取数据。简而言之,Spark SQL 引擎生成一个执行计划,用于优化和执行查询,使查询尽可能快速。图 10-4 说明您可以使用 Spark SQL 库表达 SQL 查询,或者您可以使用 DataFrame API 与数据集交互,并利用 Spark SQL 引擎和执行计划。无论是使用 Spark SQL 还是 DataFrame API,Spark SQL 引擎都会生成一个查询计划,用于优化和执行在集群上的命令。一般来说,建议在 ETL 和数据摄取过程3或机器学习工作负载中使用 DataFrame API,而大多数数据使用者(例如分析师、报告生成者等)将使用 Spark SQL 与 Delta 表进行交互。您还可以通过标准的 JDBC/ODBC 数据库连接器或使用命令行与 SQL 接口进行交互。JDBC/ODBC 连接器意味着 Spark SQL 还提供了与 Power BI、Tableau 和其他 BI 工具等外部工具进行交互和使用表进行分析的桥梁。通过其他 Delta Lake 集成进行 SQL 分析尽管 Delta Lake 强大地集成了 Spark SQL 和 Spark 生态系统的其他组件,但它也可以被许多其他高性能的查询引擎访问。支持的查询引擎包括:Apache SparkApache HivePrestoTrino 这些连接器有助于将 Delta Lake 引入到除了 Apache Spark 之外的大数据 SQL 引擎,并允许您进行读取和写入(取决于连接器)。Delta Lake 连接器存储库包括:Delta Standalone,用于读取和写入 Delta Lake 元数据的本地库与流行的大数据引擎(如 Apache Hive、Presto、Apache Flink)和常见报告工具(如 Microsoft Power BI)的连接器。 还有一些托管服务,允许您集成并从 Delta Lake 读取数据,包括:Amazon Athena 和 RedshiftAzure Synapse 和 Stream AnalyticsStarburstDatabricks 请查阅 Delta Lake 网站以获取完整支持的查询引擎和托管服务列表。 在进行 SQL 分析时,重要的是利用高性能的查询引擎,它可以解释 SQL 查询并针对 Delta 表执行大规模的数据分析。在图 10-5 中,您可以看到 lakehouse 由三个不同的叠加层组成,以及允许不同层之间进行通信的 API:存储层 用于结构化、半结构化和非结构化数据的可扩展、低成本的云数据存储的数据湖。事务层 符合 ACID 标准的开放表格式,通过 Delta Lake 实现可伸缩的元数据。API SQL API 允许用户访问 Delta Lake 并执行读取和写入操作。然后,元数据 API 帮助系统理解 Delta Lake 事务日志以适当地读取数据。高性能查询引擎 一种解释 SQL 的查询引擎,使用户可以通过 Apache Spark SQL 或 Delta Lake 集成的其他查询引擎执行数据分析。数据科学和机器学习的数据由于Delta Lake有助于在您的lakehouse中提供可靠且简化的数据管理,因此它对于用于数据科学活动和机器学习的数据及数据管道带来了各种好处。传统机器学习存在的挑战一般来说,机器学习运营(MLOps)是涉及端到端机器学习生命周期的一组实践和原则。在MLOps中,许多组织和数据科学家在试图在其组织中构建并最终将机器学习模型投产时面临一些常见挑战:数据孤立 数据孤立通常在数据工程活动和数据科学活动之间的差距不断扩大时开始形成。数据科学家经常花费大部分时间创建单独的ETL和数据管道,清理和转换数据,并将其准备成模型所需的特征。这些孤立通常会因为用于数据工程的工具和技术不支持数据科学家进行相同的活动而形成。批处理和流处理数据的整合 机器学习模型使用历史数据来训练模型,以便在流式数据上进行准确预测。问题在于传统体系结构不支持可靠地组合历史数据和流数据,这在将两种类型的数据输入到机器学习模型中时会产生挑战。数据量 机器学习系统通常需要处理大量数据,并使用复杂的代码,这不一定适用于SQL。如果数据科学家希望通过ODBC/JDBC接口从通过数据工程管道创建的表中消耗数据,这些接口可能会创建一个非常低效的过程。这些低效的过程主要是因为这些接口设计为与SQL一起工作,并对非SQL代码逻辑提供有限的支持。这导致非SQL查询的低效,可能是由于数据量、数据转换和非SQL代码逻辑通常包含的复杂数据结构。可复现性 MLOps最佳实践包括需要重现和验证ML工作流的每个阶段。重现模型的能力降低了错误的风险,并确保ML解决方案的正确性和鲁棒性。在可复制性中面临的最困难的挑战是一致的数据,只有在使用完全相同的数据时,ML模型才会重现完全相同的结果。由于数据随时间不断变化,这可能对ML可重现性和MLOps引入重大挑战。非表格数据 尽管我们通常认为数据存储在表格中,但越来越多的机器学习工作负载用于支持大量的非表格数据,如文本、图像、音频、视频或PDF文件。这些非表格数据需要与表格数据相同的治理、共享、存储和数据探索功能。如果没有某种功能来编目这些目录和非表格数据的集合,很难提供与表格数据一样的治理和管理功能。由于传统体系结构对将机器学习模型投产引入了挑战,机器学习往往变得非常复杂且孤立。这些孤立的复杂性为数据管理引入了更多挑战。支持机器学习的Delta Lake功能Delta Lake通过多种功能来支持机器学习生命周期,有助于弥补传统上由机器学习活动引入的数据管理挑战,并弥合用于BI/报告分析和高级分析的数据和流程之间的差距。以下是 Delta Lake 提供的几个支持机器学习生命周期的不同功能:优化和数据量: Delta Lake建立在Apache Spark之上,数据科学活动可以直接通过Spark SQL使用DataFrame API从Delta Lake表中访问数据,从而使机器学习工作负载能够直接受益于Delta Lake提供的优化和性能增强。一致性和可靠性: Delta Lake提供ACID事务,确保数据一致性和可靠性。这对于机器学习和数据科学工作流程非常重要,因为模型的训练和预测需要这种可靠性,以避免来自不准确或不一致数据的负面影响。整合批处理和流处理数据: Delta Lake表可以无缝处理来自历史和流处理源的持续数据流,这是通过Spark Streaming和Spark Structured Streaming实现的。这意味着您可以简化用于机器学习模型的数据流程,因为这两种类型的数据被整合到单个表中。模式强制执行和演进: 默认情况下,Delta Lake表具有模式强制执行,这意味着可以强制执行更好的数据质量,从而提高用于机器学习输入的数据的可靠性。但是,Delta Lake还支持模式演进,这意味着数据科学家可以轻松地向现有的机器学习生产表中添加新列,而无需破坏现有的数据模型。版本控制: 正如您在先前章节中了解到的,Delta Lake允许您轻松为数据进行版本控制并通过事务日志执行时间旅行。这种版本控制有助于减轻通常在ML可再现性中看到的一些挑战,因为它允许您在特定时间点轻松重新创建机器学习实验和模型输出。这有助于显着减少在MLOps过程中遇到的一些挑战,因为简化的版本控制可以提供更大的可追溯性、可重现性、审计和ML模型的合规性。集成: 在第1章和本章中,您了解到Delta Lake可以通过Apache Spark中的Spark SQL库进行访问。您还在第8章中了解到了Spark Streaming和Delta Lake与该库的集成。 Spark生态系统中的另一个库是MLlib。 MLlib为您提供对常见学习算法、特征化、流水线、持久性和实用程序的访问。实际上,许多机器学习库(例如TensorFlow、scikit-learn)也可以利用DataFrame API来访问Delta Lake表。Spark生态系统由在Spark Core之上运行的多个库组成,为所有类型的数据和分析用例提供了多功能的支持。除了Spark标准库之外,Spark还与其他平台(例如MLflow,这是一个用于管理端到端机器学习生命周期的流行开源平台)进行集成,该平台允许跟踪、记录和复制Spark MLlib模型。正如前面提到的,机器学习模型通常是使用诸如TensorFlow、PyTorch、scikit-learn等库构建的。尽管Delta Lake不是直接用于构建模型的技术,但它专注于解决并为许多机器学习活动面临的挑战提供有价值的基础支持。MLOps 和模型依赖于Delta Lake 提供的数据质量和完整性、可再现性、可靠性以及数据的统一性。Delta Lake 提供的强大数据管理和集成功能简化了 MLOps,并使机器学习工程师和数据科学家能够更轻松地访问和处理用于训练和部署模型的数据。将一切整合在一起通过Delta Lake启用的功能,可以更轻松地统一机器学习和数据工程的生命周期。Delta Lake使机器学习模型能够从历史(批量)和流式数据中学习和预测,所有这些数据都来自一个地方,同时本地利用Delta表的优化。ACID事务和模式强制有助于为用于机器学习模型输入的表提供数据质量、一致性和可靠性。Delta Lake的模式演进有助于机器学习输出随时间变化,而不会对现有流程引入破坏性变化。时间旅行功能使轻松审计或重现机器学习模型成为可能,并且Spark生态系统提供了额外的库和其他机器学习生命周期工具,以进一步支持数据科学家。在图10-7中,您可以看到完全构建的lakehouse环境的所有结果层。总的来说,Delta Lake的特性有助于弥合数据工程师和数据科学家之间的鸿沟,以减少信息孤岛并统一工作负载。借助Delta Lake和健壮的lakehouse架构,组织可以更快、更高效地构建和管理机器学习模型。勋章架构这个湖仓的设计理念围绕着统一的概念,将不同技术的最佳元素融合到一个地方。这意味着湖仓内部的数据流也很重要,需要支持数据的这种统一。为了支持所有用例,这个数据流需要将批处理和流处理数据合并成一个单一的数据流,以支持整个数据生命周期中的各种情况。第一章介绍了勋章架构的概念,这是一种流行的数据设计模式,包含青铜、白银和黄金层,最终通过Delta Lake实现。这一流行的模式用于以迭代的方式组织湖屋中的数据,以提高整个数据湖中数据的结构和质量,每一层在分析中具有特定的功能和目的,同时统一批处理和流处理数据流。勋章架构的示例见图10-8。图10-8中显示的青铜、白银和黄金层在表10-1中有简要概述。对于每个层次,您将看到其商业价值、属性以及实施细节(例如“如何完成”)。在接下来的部分中,您将更详细地了解构成勋章架构的不同层次。青铜层(原始数据)从数据源提取的原始数据被摄取到青铜层,没有经过任何转换或业务规则强制执行。这一层是我们原始数据的“着陆区”,因此,该层中的所有表结构与源系统结构完全对应。数据源的格式保持不变,因此当数据源是CSV文件时,它以CSV文件的形式存储在青铜层,JSON数据以JSON格式写入,依此类推。从数据库表中提取的数据通常以Parquet或AVRO文件的形式落入青铜层。在这一点上,不需要模式。随着数据的摄取,会维护详细的审计记录,其中包括数据源、是否执行了完整或增量加载以及支持需要的增量加载的详细水印。青铜层包括一个归档机制,以便数据可以长时间保留。这个归档和详细的审计记录可以在勋章架构下游的某个地方发生故障时用于重新处理数据。摄取的数据以“源系统镜像”的形式落入青铜层,保持源系统格式的结构和数据类型,尽管通常会增加额外的元数据,如加载日期和时间以及ETL过程系统标识符。摄取过程的目标是将源数据迅速、轻松地着陆在青铜层,只需足够的审计和元数据以启用数据血缘和重新处理。青铜层通常用作变更数据捕获(CDC)过程的源,允许新到达的数据通过白银层和黄金层立即进行处理。白银层在白银层,我们首先对数据进行清理和规范化。我们确保使用标准格式,如日期和时间,强制执行公司的列命名标准,去重数据,并在需要时执行一系列附加的数据质量检查,删除低质量的数据行。接下来,相关的数据被合并和融合。Delta Lake的合并功能非常适用于此目的。例如,来自各种来源(销售、CRM、POS系统等)的客户数据被合并成一个单一实体。已经在不同主题领域中重复使用的数据实体(称为协同数据)被识别并在视图中进行规范化。在先前的例子中,合并的客户实体就是这种协同数据的一个例子。在这一点上,数据的组合企业视图开始显现出来。请注意,我们在这里应用了“刚刚好”的理念,即我们提供足够的细节,付出最少的努力,确保我们保持对构建勋章架构的敏捷方法。在这一点上,我们开始强制执行模式,并允许模式在下游演变。白银层也是我们可以应用GDPR和/或PII/PHI执行规则的地方。由于这是强制执行数据质量并创建企业视图的第一层,它对业务非常有用,特别是对于自助分析和临时报告等目的。白银层被证明是机器学习和人工智能用例的出色数据源。事实上,这些类型的算法在白银层的“较不完善”数据中的表现比黄金层中的消耗格式更好。黄金层在黄金层,我们创建业务级别的聚合。这可以通过标准的Kimball星型模式、Inmon雪花模式维度模型,或者适用于消费者业务用例的任何其他建模技术来实现。在这里应用数据变换和数据质量规则的最终层,产生高质量、可靠的数据,可作为组织中的单一真相源。黄金层通过向下游用户和应用提供高质量、干净的数据,持续提供业务价值。黄金层的数据模型通常包括数据的许多不同视角或视图,具体取决于使用情况。黄金层将实施多种Delta Lake优化技术,例如分区、数据跳过和Z-ordering,以确保以高性能的方式交付质量数据。经过优化,以便由BI工具、报告、应用程序和业务用户进行最佳消费,这成为使用高性能查询引擎读取数据的主要层。完整的湖仓一旦您在基于Delta Lake的体系结构中实施了勋章架构,您就可以开始看到湖仓的全部优势和可扩展性。在本章构建湖仓的过程中,您已经看到了不同层次如何相互补充,以统一整个数据平台。图10-9展示了整个湖仓的情况,包括它在勋章架构下的外观。虽然勋章架构并非是湖仓中数据流的唯一设计模式,但它是最受欢迎的之一,而且理由充分。通过Delta Lake启用的功能,最终使勋章架构支持在单一数据流中统一批处理和流处理工作负载、机器学习、业务级别的聚合以及全面的分析,适用于所有角色。总结在本书中,您了解到开放表格格式的出现,Delta Lake的开源标准为数据湖带来了可靠性、可扩展性和整体更好的数据管理。通过克服许多传统技术的局限性,Delta Lake有助于弥合传统数据仓库和数据湖之间的差距,为组织带来了以湖仓形式的单一统一的大数据管理平台。Delta Lake继续改变组织存储、管理和处理数据的方式。其强大的功能,如ACID事务、数据版本控制、流处理和批处理事务支持、模式强制执行以及性能调整技术,使其成为数据湖中事实上的开放表格格式的选择。在撰写本文时,Delta Lake是全球范围内被广泛采用的湖仓格式,每月下载数以百万计,并拥有庞大的不断增长的社区贡献者。随着贡献者实力的增强和对Delta Lake的采用不断增长,Delta生态系统不断扩大,随着大数据领域的整体演变,Delta Lake的功能也将自然而然地得到扩展,这要归功于其开放源代码格式和开源社区的贡献。Delta Lake和湖仓范式的持续崛起在数据平台和数据管理的演进中构成了一个重要的里程碑。Delta Lake提供了在当今数据驱动的世界中实现规模成功所需的功能和特性,而湖仓则提供了一个统一、可扩展的架构来支持它。Delta Lake和湖仓将继续在简化架构和推动不断增长的数据和技术生态系统中发挥关键作用。
0
0
0
浏览量1403
攻城狮无远

《流式Data Mesh》第六章:自助式数据基础设施

第六章:自助式数据基础设施第1章描述了自助式基础设施。我们说它们是使数据产品工程对领域更简单的“便捷按钮”。这些便捷按钮隐藏了流式数据网背后的困难部分。实施便捷按钮背后的困难部分是中央团队的责任。 在本章中,我们将列出流式数据网格中所需的自助服务。我们将重点关注自助服务的名称和签名(即名称和请求/响应参数)。关于这些自助服务如何实现的讨论将保留到第7章。 自助服务是领域在使用流式数据网时看到和使用的接口(或API)。每个领域开发和部署流式数据产品所需的功能都应该有相应的自助服务。本章描述的自助服务将是从终端执行的命令行界面(CLI)。我们之所以这样做,纯粹是为了方便。这些相同的自助服务也可以实现为RESTful服务或Web界面。在GitHub存储库中可以找到一个CLI示例。我们定义的自助服务还旨在确保遵循所有安全要求、数据治理要求和一般最佳实践。由于领域通常不知道如何正确配置它们将使用的任何资源,这些自助服务将自动进行配置。这包括安全和数据治理的配置。在构建自助服务时,不要询问领域需要多少经纪人(broker)。他们不会也不应该知道答案。相反,问他们:“你想要一个特小号、小号、中号、大号还是特大号的流媒体平台?”你也可以问:“你预计要发布多少个数据产品?”或者甚至是“你预计会有多少个消费领域?”这些问题应该与自助服务将构建的资源的大小和容量相对应。为领域构建自助服务的最佳方法是遵循许多SaaS服务所做的。它们遵循一种无服务器模型,使其用户更容易理解并在其应用程序中利用。无服务器SaaS提供商的目的是不要求用户担心为其分配的“服务器”。用户可以更多地专注于他们的业务,而不是管理和调整服务器。自助服务应采用相同的模型:使流媒体数据网格成为无服务器,让领域只需专注于他们的业务。自助服务可以分为两个主要类别:与资源相关的服务和与数据治理相关的服务。在本章中,我们将使用CLI模型来帮助您了解如何在保持无服务器方法的同时,领域如何轻松与流媒体数据网格进行交互。在本章中,您将看到CLI命令的帮助信息(在每个命令后面使用“-h”参数表示)。显示的信息显示了其他可选或必需的子命令和参数。流式数据网格CLI让我们从示例6-1中的登录命令开始。领域工程师或数据产品所有者(我们将其称为领域角色)将登录流式数据网格。这将创建安全凭据和领域配置,并保存在领域角色操作系统上的隐藏文件夹中。随后的所有命令都可以方便地查找该信息。在这里,sdm.py是一个命令行界面(CLI),允许领域角色与流式数据网格进行交互。$ ./cli/sdm.py login You have successfully logged into the streaming data mesh. Your credentials have been stored in ~/.streaming-data-mesh/auth. 示例6-2展示了领域角色可以使用的所有命令。每个命令旁边都有一个描述。与资源相关的命令有cluster(集群)、connect(连接)和streaming(流式传输)。与数据治理相关的命令有access(访问)、users(用户)、login(登录)和logout(登出)。version(版本)命令仅用于提供CLI的版本信息。$ ./cli/sdm.py -h Usage: streaming-data-mesh.sh [tool] Available Commands: domain Manage domain. cluster Manage streaming platform. connect Manage connect cluster. streaming Manage stream processing platform. access Manage access to resources. users Manage domain users. login Log into the streaming data mesh. logout Log out of the streaming data mesh. version Version of the CLI. 我们将介绍每个命令,但不会涵盖每个子命令。对于未涵盖的子命令,帮助页面中提供的描述就足够了。 帮助信息遵循一种模式。示例6-3展示了这一点,以帮助您在本章更好地理解它。/cli/sdm.py the_command --help Usage: sdm.py the_command [OPTIONS] COMMAND [ARGS]... This is where the description of the command appears. Options: --the_option This is the description of the option. Commands: sub-command1 This is the description of sub-command1 sub-command2 This is the description of sub-command2 sub-command3 This is the description of sub-command3 This is how the command is written on the command line.
0
0
0
浏览量637
攻城狮无远

第三章:数据可观测性在数据组织中的角色

在上一章中,您了解了数据可观测性的定义以及数据技术和团队如何拥抱它。在本章中,我将系统地研究数据可观测性,分析它如何融入数据组织,比如数据架构和文化。因为数据文化本身就是一个复杂的系统,所以我将分两个部分来讨论它:通过数据组织中的数据工程角色来看待数据文化。数据在我们经济演进中的作用(因此也是组织的作用)。数据架构数据架构是数据组织的关键组成部分。它为每个部门的数据如何以个别和协作的方式提供价值奠定了基础。本节的目的不是定义理想的数据架构,如果存在的话,而是回顾数据可观测性必须在组织中引入的位置,以支持可持续的数据使用管理。不是所有的组织在数据架构方面都是平等的;有些组织已经有一个既定的架构,预计会发展或部分或完全替代,而其他组织则从白纸开始。因此,我将解释为什么在构建新架构时必须从一开始考虑数据可观测性,以及如何在现有数据架构中引入它作为附加组件,尽管它起到了中心作用。数据可观测性在数据架构中的位置如何?为了讨论数据可观测性在数据架构中的位置,我必须将数据可观测性分为两部分:数据可观测系统和数据可观测平台。这是为了遵循架构的一个基本原则:关注点分离。数据可观测系统数据可观测系统涉及数据可观测性作为数据应用程序的一个要素。回顾前几章,数据可观测性是一种能力,可以从数据应用程序内部引入,这些应用程序可以生成整个数据可观测性核心模型。因此,数据可观测性首先包含在数据架构的应用程序层中,如图3-1所示。在这一层面必须考虑两种不同情况:当数据工具、框架和库部分地或尚未具备数据可观测性,以及当它们在本质上具备数据可观测性时。当支持创建数据应用程序的组件并非完全具备数据可观测性时,根据工具和架构的不同,需要在一个或两个位置添加附加组件。这些位置在图3-1中用标签1和2表示。标签1位于“应用程序”上,代表了我将在第4章中讨论的引入数据可观测性能力。标签2表示工具被扩展以通过默认生成所有使用它们构建的数据应用程序的数据观测的附加构件(例如代理),我将在第5章中讨论。然而,在未来,所有工具和库都将通过设计或本质上具备数据可观测性,这意味着默认情况下所有数据应用程序都将具备数据可观测性,因此将被视为理所当然。因此,在架构图中我没有表示这个显而易见的部分,以避免膨胀架构图并加速新架构的定义。然而,在此期间,我必须考虑那些要么是遗留的,要么太封闭(黑盒)以至于无法让我们有机会应用第4章和第5章中介绍的实践的工具。对于这些工具,我在数据可观测系统中保留了一个位置,它一边与数据层接触,一边与数据可观测平台接触。第6章将详细介绍这个特定但暂时的情况。数据可观测性平台如图3-1所示,所有应用程序都是通过本地方式或适当的扩展进行数据可观测,这些扩展将在接下来的章节中介绍。数据可观测性平台是一个系统,用于汇总和观察由相互连接的数据应用程序生成的数据观测。每个应用程序生成的数据观测都有潜力解决第1章中强调的挑战。这些挑战涉及到需要在扩展环境中检测数据问题的系统,简化识别和解决根本原因的过程,并主动预测其再次发生的可能性。通过利用数据观测的力量,我们可以开发有效解决这些挑战的解决方案,确保数据生态系统更加强大和有韧性。此外,每个应用程序很可能在强(例如,编排)或弱(例如,使用相同的数据源)依赖于其他应用程序,因此在全局数据观测的相互作用中存在巨大的潜在价值。例如,通过遵守数据可观测性核心模型,可以利用有关这些依赖关系(例如,血统)的信息来自动检测和解决数据问题。您还需要考虑过去数据观测中存在的价值,因为可以更有效地基于过去状态来确定数据在其使用环境中的当前状态。一个简单的例子是数据摄取应用程序。摄取的数据量很可能遵循底层模式(例如,时间序列),可以从中学习,以确定当前摄取的数据量是否异常或不正常;参见“期望”。因此,数据架构必须通过一个额外的中央组件进行扩展,即数据可观测性平台。最起码,此组件负责摄取所有数据观测,对其进行聚合,并在理想情况下从中学习以提供这些功能。图3-2更新了图3-1,其中包含了该平台以及其他中央组件。数据可观测性平台不是孤立运行的,而应与其他平台(如编排器或应用可观测性平台)合作。推荐进行这种协作,因为正如第1章中所概述的,数据可观测性是可观测系统的一个维度。数据可观测性充当缓冲,为数据治理和所有其他数据管理领域之间提供控制和可见性。数据可观测性还在数据(工程)团队的效率方面发挥着重要作用,我将在后面的部分进行讨论。首先,让我们看看数据架构必须确保系统具备数据可观测性以及如何引入数据可观测性平台的情况。带有数据可观测性的数据架构在理想的情况下,您将从一开始就引入数据可观测性。然而,在现实世界中,首先要交付平台和第一个价值。如果时间和能力允许您“不工作于直接业务价值生成”,那么监控和降低总拥有成本(TCO)可能会成为下一个优先事项。我见过很多公司愿意进行关于数据可观测性的“概念验证”,但实际上,我们真的需要证明生成数据观测以增加对系统信息的了解是有益的吗?好吧,我理解数据可观测性平台,我的意思是工具,需要“测试”,以验证其能力是否与期望和需求相匹配。然而,是否应该使系统具有数据可观测性根本不应受到质疑。它必须被数据架构和整个组织的数据社区共同接受。因此,我建议尽早开始构建具有数据可观测性的系统,并寻找适合您特定环境的数据可观测性平台。 您如何做到这一点呢?让我涵盖两个广泛的类别:新的和现有的架构。不是所有情况都完全适用于这些类别;有些情况介于两者之间。因此,了解这两个类别提供了对哪种混合可能对您最有用的情况有很好的了解。在架构中引入数据可观测性取决于系统的具体设计和实施。对于新架构,建议选择那些已经具备数据可观测性或可以轻松变得可观测的数据技术。我还建议在架构的基础层引入数据可观测性平台,这里已经有其他解决方案,比如编排器。 另一方面,对于已经存在的架构,应该不会妨碍在基础层引入数据可观测性平台,因为它可以独立运行。然而,这并不会自动使建立在架构上的系统具有数据可观测性。要实现数据可观测性,关键是将现有的数据技术转变为可观测的技术,除非它们已经是可观测的。对于继承的技术,第六章可能会提供帮助。 在向架构中添加其他技术时,选择那些专门设计为原生数据可观测性的技术非常重要。这将使系统能够在其开发和运营过程中保持一致的数据可观测性水平。在任何架构的设计和实施中优先考虑数据可观测性至关重要,以确保数据可以得到有效的监控、管理和利用。数据可观测性如何帮助应对数据工程的潜在问题既然数据架构已经扩展,引入了数据可观测性的能力,那么在本节中,我将关注由Joe Reis和Matt Housley在《数据工程基础》(O'Reilly出版)中引入的数据工程潜在问题,我将在第五章中深入讨论。这些潜在问题通常被认为是复杂和不方便的,但在数据可观测性的基础上,它们要简单得多。 让我们从安全性开始。安全安全性必须融入组织的文化中,这意味着它不能复杂或难以实施。尽管自动化所有安全性任务具有挑战性,但系统正在不断发展,以处理其中的许多任务。例如,在软件开发中,可以检测到存在安全问题的软件包,并且专用平台可以自动生成拉取请求。对工程师来说,难点在于他们必须考虑尽可能多的灾难性情景,预见他们希望观察到的所有事件,并自动化预防(例如,过滤)和传播(例如,断路器)。然而,通过将数据可观测性系统与数据可观测性平台相结合,我们可以通过自动化繁琐和耗时的任务,只留下基于知识的任务(例如,计算已屏蔽数据的百分比),从而使工程师的工作变得更加轻松。这些任务与他们产生和维护的价值直接相关。因此,标准信息,例如与数据访问相关的观察结果,由已经存在的数据可观测系统设计提供。数据可观测性平台通过将这些观察结果与平台的安全可观测性和自动化组件相匹配,来完成其余的工作,如图3-3所示。其他观察结果还将支持工程师处理安全性问题,这些结果来自于提供观察结果的用户和应用程序可观测性领域。为了说明这一点,让我通过一些示例来为您演示。第一个用例是检测数据应用程序突然获取的数据比过去多50%或使用方式发生变化,尽管数据本身的大小并未发生变化。此外,可以防止数据在流程中没有应用加密步骤而被外部暴露,如图3-4所示。工程师可以在其自定义指标显示的掩码数据百分比下降时收到通知,这可能表明数据已泄露。另一个示例是警告指出,被标记为重要的数据没有备份,因为没有运行“备份”应用程序,该应用程序会在其他地方读取和写入这些数据,如图3-5所示。现在我们可以支持工程师在不大幅增加他们日常工作干扰的情况下达到令人满意的安全水平,让我们着手解决一个重要而广泛的基础问题——数据管理。数据管理在第1章中,我提出了DMBOK2数据管理框架,可以将数据可观察性引入作为一个新领域,围绕数据治理作为控制层跨越所有其他领域(图1-13)。在本节中,我将通过数据工程基础中提出的数据治理的视角来强调这一观点。数据治理为了充分了解数据治理中数据可观察性的影响和价值,重要的是要研究它与数据治理监管的其他数据管理领域的交叉点。通过分析这些交叉点,我们可以深入了解数据可观察性如何对实现大规模有效数据文化的画面起到关键作用。换句话说,要充分理解数据治理中数据可观察性的好处和重要性,有必要探讨它与数据治理监管范围内其他数据管理方面的关系。通过这样做,我们可以更全面地了解数据可观察性如何有助于成功在更大范围内实施数据驱动文化。可发现性数据必须在项目和领域之间进行重复使用。为此,需要在新项目范围的分析阶段中发现数据,例如。这意味着数据的特征应该被暴露给一个引擎,允许人类或另一个系统根据他们的需求进行匹配。如果不是这样,那么所有数据都需要系统地分析,以找到最适合的用途,最终需要越来越多的时间和资源。简而言之,它很快就会变得不可持续。特征的另一个词是元数据。我认为根据特征搜索数据比根据元数据搜索更直观。但是,元数据是用于描述这些信息的普遍接受的术语,因此我将使用该术语。要暴露的元数据可以分为以下类别,每个类别都涉及特定类型的搜索和相关的决策:业务元数据 项目、用途、(数据)定义技术元数据 描述性元数据:位置、谱系、架构、管道 内容元数据:容量、丢失数据、密度运维元数据 运行时标识符、依赖关系(编排)参考元数据 查找数据、代码等这些元数据与第2章中提出的大多数观察实体相似,或者可以从它们中派生。因此,一个数据可观察性系统也可以被描述为一个可发现性系统,因为与涉及的数据相关的观察将自动生成并汇总到数据可观察性平台中。这是数据可观察性平台的作用——与数据目录合作,数据管理组件通常用于数据发现。这种合作将使数据目录能够始终根据用户的搜索返回最合适的数据,或者最终将信息推送给已表示有兴趣在数据关于某些特征可用时收到通知的用户。问责制因为工程师使用或生成数据的全部或部分,所以他们对这些特定操作负有一定的责任。例如,如果在流程中使用的一些传入数据未满足工程师的假设,工程师将被认为要么没有注意到它,要么更糟糕的是生成了不适当的数据(例如GIGO)。因此,为了对这种新的责任有信心,工程师必须建立实施防御性方法(在第2章中引入并在后续章节中描述)的实践,以增加他们成功的机会,而不会影响他们的交付能力。他们必须使构建和使用数据系统的数据可观察。然后,他们必须将生成的观察与以下两个过程相结合:假设检查(±SLO)期望处理(±SLA)最后的选择是作为一个后备计划,如果需要,一个可以依靠数据可观察性平台来创建关于可能对工程师进行持续监视的潜在约束的可见性。数据质量在确保数据质量方面,数据工程师的角色通常具有挑战性。这是因为工程师响应于来自主题专家(SME)的新数据或适应数据的业务请求的请求,通常由多种因素触发,例如报告的探索、市场分析、关于领域的特定知识或可能影响业务的特殊事件的意识。然而,SME可能难以向工程师解释这些因素,因为他们可能没有相同领域特定知识的水平。结果,验证最终结果对于工程师和SME都可能很困难,因为结果的数量可能超出他们的个人领域的知识。此外,即使SME能够表达最终结果如何通过勾画他们的约束或期望来验证,也不总是可能确保穷尽性。此外,结果的验证过程可能在长期或在不断变化的情境下不成立,这是另一个重要的考虑因素。然后来的是,生成的数据通常会被原始请求者或其他用户重复使用,这些用户可能来自其他领域,他们发现数据对于他们自己的用例非常有价值。在理论上,工程师和利益相关者可以定义一组用于验证数据质量的约束。实际上,这导致了一系列令人不快的讨论,其中没有一方真正能够做出坚定的决定,因此不能承担全部责任。对于利益相关者来说,被分配为“先验”定义“好数据”的责任复杂且风险很高,现在和将来都是如此。工程师经常会在将项目部署到生产中时感到焦虑,而不确定他们应用的所有转换是否产生有效的数据。实际上,一个连续的改进过程对于在所有涉及的各方之间达成一致是必不可少的。每个方都应在此过程中有一个明确定义的角色。重要的是要承认数据问题是不可避免的,潜在的问题可能无限多。通过承认这一点并实施连续改进过程,每个人都可以共同努力,确保以合作和建设性的方式识别和解决数据问题。因此,在预测数据问题方面投入的时间和资源必须平衡得当,以提供足够的(或“足够好的”)一致性,以及升级此验证过程所需的迭代。现在,我们可以重新审视实现有效工程师和利益相关者之间高效协作的角色和责任:工程师验证已知约束。利益相关者描述已知约束。工程师将观察生成到数据可观察性平台。利益相关者使用数据观察来维护或创建约束,然而,平台应足够智能,可以自动执行此操作。当发现新类型的问题并需要预测未来时,工程师和利益相关者应该通力合作。这种关注分离允许迭代,减少了双方的时间成本,为双方提供了正确的期望和风险水平。换句话说,这是实现双赢局面的唯一方式。主数据管理和数据建模定义与组织的业务或内部流程相关的实体需要时间和资源。这些努力旨在制定跨数据团队和项目必须遵守的格式、结构和特征的标准。数据可观察性有助于识别那些未适当遵循这些定义的情况。举个例子,假设一个数据管道负责将日期信息写入数据源。日期信息的格式必须在所有条目中保持一致。为了确保这种一致性,可以实施一个数据可观察性指标来跟踪不符合所需日期格式的条目数量。可以通过规则(期望)来检查此度量标准,并且如果不匹配的条目计数不为零,则认为管道失败。通过监控这个数据可观察性度量标准,数据工程师可以主动识别和解决与日期格式不一致相关的任何问题,确保高质量的数据并将下游问题的风险降到最低。再次,数据可观察性充当重要的控制层,以确保实践和约定在组织中得到尊重、维持、鼓励和奖励。数据集成和互操作性随着专门的SaaS应用程序的爆炸增长,每个应用程序都有其特点和相关的人物,组织拥有多个工具并存在相对重要的重叠并不罕见。此外,已经认识到跨领域合并数据预计会产生令人惊讶的结果,例如未知的利基或未被充分利用的机会。因此,大多数工具都提供了分享其数据、集成外部数据或两者都能够更容易实现的能力。可以使用编程接口(例如Python、ETL)或更高级别的工具(例如Matillion等)更容易地实现此目标。因此,工具之间或通过数据平台之间的连接数量正在以设置它们的复杂性减少的速度增加。这是数据可观察性带来价值的地方——控制互连网络,并确保防止问题传播。数据生命周期管理这是一个广泛的主题。我将自己限制在仅删除和归档数据,以应对安全性或隐私要求,例如。在这些情况下,数据可观察性提供了一种嵌入式功能,可以公开执行删除操作以及其效果(删除了多少数据)或创建的归档(多少、多久以及何时创建的)。反过来也是数据可观察性的有效用法,因为它提供了一种机会,可以公开很少使用的数据(或其子集),这些数据可以被删除或存档。伦理和隐私随着人们对数据的潜在影响(积极或消极)的认识日益增长,组织还需要采取一种防御性方法,以防止不当使用或共享数据。这是必须作为组织数据文化的一部分实施的东西,因为大多数问题都是无意的,是由于错误而发生的。数据可观察性首先提供了关键的可见性,在伦理和隐私的情况下,这一点至关重要,因为组织必须尽可能透明。数据可观察性对伦理和隐私提供了巨大的支持,因为新的数据使用和共享都已记录,包括数据与谁共享。请注意,数据可观察性不是完整的安全机制。实际上,如果与共享数据的应用程序可以执行相关检查(例如使用计算共享策略),则可以避免共享。然而,这不是数据可观察性,而是真正的应用安全。然而,数据可观察性允许暴露数据的不当用途,因此可以采取预防措施,以避免将来出现类似的情况。还需要考虑的一种情况是,当有一个自动化的决策流程可能存在偏差,因为用于定义流程的数据存在偏差。这是出于不同原因发生的,最常见的原因是数据被扭曲,这意味着某些类别与其他类别相比被过多地代表。这种情况也可以得到数据可观察性的支持,例如,通过控制偏斜度度量的变化或简单地显示分类字段的直方图。DataOpsDataOps并没有一定的明确定义。然而,在《软件工程基础》中,Joe和Matt很好地描述了一些可能会在各种定义或理解中保持不变的领域:自动化、可观察性和事故响应。我将这些领域与一套旨在实现数据工程师多个目标的实践和工具联系在一起。这些目标包括:确保快速高效地部署到生产环境,同时保持结果的可靠性和准确性。在出现问题或错误时便于调试。提供多种自动问题检测方式,以尽快捕获和解决问题。通过实施这些实践和工具,数据工程师可以更高效、更有信心、更加放心地工作。让我们看看数据可观性在这个画面中的位置,从自动化开始。自动化扩展是关于扩展数据目标。为了平稳和可持续地扩展,必须满足以下条件:复杂性必须减少——以找到更多的人才。摩擦必须减少——以在生产中更快地前进。重复性任务必须自动化——以节省时间。因此,与在高度自动化的系统中创建和维护信任相关的所有活动必须自动化。否则,当团队处于时间紧迫的情况下,它们将成为瓶颈,并且在跳过这些任务时将会浪费。这就是为什么数据应用必须通过设计或扩展成为数据可观察的原因,如第5章中所提出的。可观察性这一部分基本上是闭环,因为数据可观察性是可观察性的一个领域。然而,可观察性必须是一个最佳实践,尽可能多地自动化,最好是通过设计,在数据转换过程的所有步骤和工具中。可观察性还在问题管理方面提供了价值,如下一节所讨论的,它负责生成优化整个过程所需的信息。如第2章中所介绍的,数据观察是为了使数据使用的可见性。因此,正如一些优化理论(例如,运营研究)中提到的那样,它们可以用于优化相关的维度,如时间、资源或结果。没有将数据观察融入监控的话,系统将无法自我改进或具有确定性的改进过程。我在另一本书《数据治理是什么?》中已经讨论了这个话题,在其中我涵盖了成熟性的方面。在第三部分中,我强调了成熟度的第5级是:优化(第5级)数据治理计划侧重于持续改进,监控过程的质量和性能以及它们在实现业务目标方面的作用。组织以高效、一致的方式改进和调整其实践,以适应战略变化。事故响应DataOps的最后一部分与问题管理相关,主要是与响应报告的问题相关的过程。事实上,如果在数据出现问题时,没有数据可观性和本书中描述的相关实践,那么所有的事情都会匆忙进行,以尽快恢复信任。因此,没有什么是明显的,但是参与这种情况的双方都会感到整个过程的无效。重新表达我的引言:问题管理效率低下,浪费了大量的资金。因此,数据可观性的作用是提供有关问题管理的可见性,从问题检测到解决,再到事后分析,并帮助确定什么构成了事故响应的适当过程。基于数据观察,包括用户参与和代码更改,可以从它们与已解决的工单数量的相关性、频率、涉及的数据、涉及的系统、解决过程的持续时间等方面绘制与组织改进或预防机制的相关性。因此,您可以将成本估计与TTD或TTR相关联,因此可以估算甚至预测(用于预算编制)总拥有成本(TCO),该成本考虑了与获取、实施和维护系统相关的所有成本。软件工程在数据工程领域,尽管这显然不仅限于该角色,但有一个重要的潜在因素。过去几十年来,特别是自计算机科学变得更加主流以来,已经建立和改进了一系列实践,其价值经常被低估。的确,数据不能直接与软件进行映射,或更准确地说,软件,因为有许多子类别需要考虑,它们有自己的特点,将这种或那种实践的适用性分隔开来——嵌入式软件、Web 应用程序或简单的网站,例如,有许多专门的最佳实践。因此,将数据视为软件的一部分并不完全荒谬,因为数据不能真正存在于没有处理的情况下。因此,许多软件实践适用于数据工程。您将在《数据工程基础》中找到其中的一些内容。我还会添加数据可观性,特别是使应用程序具备数据可观性,作为必须在所有数据从业者的DNA中编码的附加实践,就像软件工程师在其生产代码中记录、测试和检查接收到的消息的有效性(例如,编码、格式等)一样。现在我们已经涵盖了架构和实践概念,让我们来讨论数据可观性在组织概念中发挥重要作用的领域。为此,我将使用我强烈信仰的一种范 paradigm)——数据网格(data mesh)。支持数据网格的“数据作为产品”概念我会从一个声明开始这个最后的部分:我的目的不是解释什么是数据网格——我会把这个任务留给它的创造者Zhamak Dehghani或其他投入大量时间思考它的专家。我的目标是为您提供一些建议,说明为什么数据可观性无疑是数据网格实践和技术组件的一部分,至少是在组织中的实施中如此。如果您想要或需要关于数据网格的解释,现在有两件事情我建议您去做:阅读Martin Fowler的博客上的起源。 阅读Zhamak的书《Data Mesh》(O'Reilly)。在支持数据网格的许多概念中,我将专注于“数据作为产品”,这包括提供可以被视为组织资产的东西(例如,它可以被货币化)。在数据网格中,“数据作为产品”是支持数据产品实施的原则,这些数据产品是可以通过输入和输出API组合的工件,另外还有一个用于在纯数据流之外暴露的附加API,即控制API。该书将其表示为一个六边形,如“观测模型”所示。您可以看到API都带有标准化和领域无关的标注,这基本上是因为我在第2章中介绍的相同原因。暂时不考虑数据网格中的领域这个重要概念,我们可以将数据想象成产品,将它们的输入和输出API连接起来,形成一个图形(一个网格),如图3-8所示。您可以看到网格是通过API连接的。实际上,在图3-8中带有连接器的线代表了数据在传输,因为数据通过输入和输出API进行交换,并由控制API进行控制。然而,为了在这样的网格中解释数据可观察性,我使用了一个双重图表。在这种情况下,双重图表是指我们将输入和输出API连接视为节点,而链接仅表示控制API。由于这可能难以表示,图3-9显示了图3-8中网格的双重图表。实际上,这个双重图表可以被称为“数据使用网格”。我们可以看到数据产品DP1和DP2出现了两次。这并不意味着这些产品被复制了,而是受控信息的数据产品高度依赖于其使用方式(输出API的使用方式)。如果一个节点未满足期望,那么下一个边缘可以断开以避免传播,这就是创建一个(可能是孤立的)子图的断路器,该子图与找到的问题相关联。在这些示例中,假设DP2只在DP4的上下文中出错;这将在数据网格图中传播问题,并标记所有受影响的下游数据产品,包括DP3、DP4、DP6和DP7,而不仅仅是DP4本身。通过将数据使用网格纳入决策过程,只会影响DP2、DP4和DP7,因此DP3和DP6将不受影响。这显示了对期望的上下文管理的重要性,并突显了数据网格的好处——它围绕API构建。与通用数据源(例如文件、表等)相比,这一好处显而易见,通用数据源可以随机访问,而没有受控的API(例如JDBC、文件系统访问等)。尽管如此,我仍然没有介绍数据可观察性如何支持在数据网格中创建和管理数据产品。为此,我需要打开一个数据产品,看看数据可观察性可以添加到哪里(参见图3-10)。事实证明,在某种程度上,这里涉及到两个层次的数据可观察性。第一个层次是外部于数据产品的,涉及通过控制API公开可见性和可信度,供构建在其上的其他数据产品使用。然而,这个层次很可能是粗粒度的,因为在这个层次的意图不是解释数据产品的所有细节,数据产品的工作方式很可能会随着时间的推移而改变,而不一定会影响到消费者,而是为了验证从输出API输出的数据。在这个层次上,连接(上游数据血统)与输入API的可见性非常重要,以便在数据产品的边界内(其内部),如果在输出API上检测到的问题是从输入API传播的,还是由于数据的误用(在管道内)导致的,可以进行演示和分析。然而,在数据网格中,数据产品是原子的,意味着其内部可能非常复杂且不暴露。因此,当问题是由于数据产品的行为时,需要第二层数据可观察性,这一层是局部的,针对数据产品级别,因为它提供了解决问题所需的可见性。这种精细的数据可观察性提供了在数据产品级别高效运作所需的机制,并且实际上可以成为在更高级别(数据产品级别)提供的汇总视图的来源。由于数据可观察性的第一个层次在某种程度上是对第二个层次中可用信息的粗粒度(聚合),我只将数据可观察性平台连接到了后者,以最大程度地提供可见性而不重复冗余。混合这两个层次允许在数据产品级别定义SLO(服务水平目标),这些SLO首先与暴露给控制API的SLA(服务水平协议)相关联(自下而上)。其次,通过利用数据可观察性平台在依赖数据产品的输入API上进行的持续验证,可以调整SLO,这些验证也可以在控制API中暴露,以将此信息提供给上游数据产品。总结本章介绍了数据可观察性作为构建强大数据生态系统的关键要素。通过将其实施在数据平台的基础层,组织可以培养强大的数据文化并有效地管理其数据。数据可观察性还与DataOps愿景保持一致,并支持其所有支柱。此外,它填补了数据组织中的差距,以确保在规模上实施数据战略,并与数据网格等相关范例集成。然而,需要注意的是,数据可观察性平台不能独立运行,需要数据产品和数据管道具备数据可观察性。接下来的章节将探讨如何实现这一目标。
0
0
0
浏览量911
攻城狮无远

第五章:自动生成数据观察结果

在前一章介绍了低级别日志记录所面临的挑战之后,我们的重点转向探索替代方法,以增强数据可观测性并简化其采用,通过自动化。在本章中,我将介绍捕获和分析数据观察结果的新可能性和策略,为更全面的可观测性框架铺平道路。抽象策略上一章节解释了如何使用低级别的API将数据可观测性添加到处理数据的应用程序中。即使对于简单的流程,这种方法也可能变得繁琐,将任务留给最终用户——数据工程师或从业者。大部分工作,如第4章中介绍的代码指令,可以抽象为一个更高级别的框架,减少涉及的工作量,只要你足够时间来定义它。与其他最佳实践一样,缩短上市时间的压力可能会导致跳过生成数据观察的工程师知道他们将部署一些他们几乎没有信心的东西时的步骤。为了避免陷入这个陷阱,我们可以自动化各种最佳实践策略或技术——这是其他领域的经典优化实践,比如应用程序可观测性,其中大多数常见的观察通常会自动生成,例如Web服务的请求次数,计算引擎的内存使用情况,应用程序的版本(参见META-INF)。自动化始终具有较少的灵活性,因为它解决了常见任务。但是,您可以使用低级别API将这些任务扩展到您的特定需求。这些技术在应用程序可观测性中广泛使用,例如,在应用程序可观测性中自然适用。像Datadog、Splunk和OpenTracing等这样的参与者在开发的各种代理中使用它们。 第一个策略——使用事件监听器——利用您用于数据工作的工具可能触发的事件。事件监听器根据您用于转换或操作数据的框架和工具,您可能会遇到一些已实现了允许对其内部行为做出反应的概念,例如Apache Spark等。当发生某些事件时,这些技术使您能够注册侦听器以拦截事件传达的信息并根据事件采取行动。了解哪些行为可以被拦截,允许您以更自动化、系统化和标准化的方式创建关于它们的可见性。在数据可观察性中,与表示在第2章中解释的内部系统的模型相关的事件特别有趣。此类事件通常可用于读取或写入数据源以及运行转换。用于利用这些事件的组件通常被命名为监听器或处理程序。您可以创建并在事件总线上注册它们,事件总线是传播触发事件的系统。一些技术可以触发事件,这些事件一次包含所有可用信息,或者包含足够的信息,以便您几乎可以在一行日志或操作中生成几乎完整的模型,例如Apache Spark(在第6章中介绍)。在可能的情况下,基于事件的代理非常强大,因为您可以自动集成它们到框架级别或工具服务器中,发布大部分信息而无需花费太多时间。当然,此概念仅适用于创建标准化信息。任何自定义内容,如度量、算法或描述,必须由应用程序或用法引入。与期望相关的规则需要参与,它们留下了很少的标准化空间,同时保持了其效率和性能。通用规则适用于所有情况,太模糊以捕获自定义或边缘情况。应用程序各不相同。日志作为事件是一种特殊情况,如果您使用的框架、应用程序或系统没有提供创建数据可观察性目标所需的灵活性时,值得考虑作为后备方案。这些系统被称为黑盒子,包括一些遗留系统。但是,如果这些系统是封闭的,它们可能会生成一些日志,例如已执行的SQL或已应用的模块。日志在应用程序可观察性的一部分中为系统创建了一些可见性。但是,它们最终可能包含足够的信息,可以在上下文之外通过外部方式修补系统来重新创建部分数据可观察性,就像可以将符合Prometheus的客户端附加到Java应用程序以生成关于其执行上下文的日志一样(版本、运行时等)。面向切面的编程 (AOP)面向方面的编程策略与事件监听器策略有些相似,简单来说,它的思想是对某些行为做出反应以扩展它。它通常用于配置目的,因为它使组件能够以更声明性的方式集成。方面就像在函数将要执行或已经执行时触发事件一样。图5-1突出了事件监听器和方面之间的相似性和不同之处,这些主要与注入的代码运行的位置(例如,不同的线程)以及方式(例如,通过事件总线或注入)有关。
0
0
0
浏览量2014
攻城狮无远

《流式Data Mesh》第二章:流式Data Mesh介绍

第二章:流式Data Mesh介绍在第一章中,我们介绍并总结了数据网格架构的四个支柱。现在,我们将将这些入门知识应用于流式数据网格。简而言之,流式数据网格是一个满足所有支柱要求的数据网格,以流式数据的形式实现。换句话说,在数据从源头摄取后,数据在到达消费领域之前没有任何时刻停留在数据存储中。数据产品保留在流中,直到其保留期结束。将数据产品保留在流中需要使用到数据网格的所有自助服务工具和服务。考虑一个简单的ETL过程。从源头提取数据的组件需要将数据放入流中。接下来,对数据进行转换的引擎需要在流中进行转换。最后,发布数据产品的组件需要支持集成,以便消费者可以轻松地将数据产品流式传输到自己的领域中,并遵循面向流式数据产品的联邦计算数据治理。表格2-1展示了四个数据网格支柱,在流式设置下的具体情况。在本章中,我们将讨论在实现数据网格时,流处理相对于批处理的优势。我们还将介绍Kappa架构以及它如何实现这些优势。流处理的优势流式数据网格相比批处理具有一些优势,通常在构建第一个用例之后才能体现出来。这些优势体现在技术实现和业务结果两个方面。流式处理使实时用例成为可能流式数据网格的第一个优势是它使数据消费域能够处理真正的实时用例。在数据产品中,流式处理是实现真正实时能力的唯一方式。如果数据流水线在从操作平面复制数据到消费域的过程中任何时候存储数据,那么就会剥夺所有潜在消费域的实时能力。当您在流水线的任何环节存储数据时,就会强制数据流水线具有批处理语义,包括使用类似CRON的工具来安排从数据存储中读取或一组读取的时间表。即使现有的域目前没有任何实时用例,流式处理也支持批处理用例和实时用例。最好一开始就将数据流水线实现为流式处理。否则,不将数据网格实现为实时流式处理可能会导致巨大的技术债务,难以偿还。随着业务的发展,企业越来越注重数据驱动,并且对快速分析洞察的需求也会增加。即使今天没有任何实时用例,我们应该预期未来会出现实时用例。流式数据设置了数据处理和人工智能的新标准。批处理引起的时间延迟使企业在应对业务需求上变得缓慢。当由于批处理而导致分析模型推理获得的洞察力变得延迟时,这个问题变得尤为重要,进一步导致收入损失或潜在客户的不满意。此外,模型的底层数据变化非常快。从过时的数据源训练分析模型会导致对不再代表被推断数据的模型进行推断(评分)。这种数据不一致性通常被称为训练和服务偏差,对于模型稳定性来说是一个具有挑战性的问题,因为在提供服务时没有准确考虑到数据的变化。考虑采用流式架构的一个关键动机是尽量减少训练和服务偏差。流式处理提供了数据优化的优势当你从操作性存储中提取数据时,该数据被置于运动中。通过将其放入像Apache Kafka这样的流式平台中,数据保持在运动状态并通过分区和索引自动进行优化,从而实现高效的生产和消费。如果你将数据从流式平台中取出并存储到数据存储中,那么你将被迫重新优化数据,包括分区、索引、分桶或压缩,以便任何下游过程可以从数据存储中高效地消费数据。这引发了一个问题,如果数据处理尚未完成,为什么要将数据放入数据存储中。图2-1显示了当数据从流式层中移除并放入数据湖中时会发生的情况。从流式中获取的实时数据几乎总是会在数据湖中创建小文件,因为数据是按事件流式传输的。这些事件在集群上创建了较小的文件,而不是创建大文件进行批量上传(批量上传是批处理的特性,不能提供实时功能)。小文件是数据处理的绊脚石。除了存在块使用不佳的问题外,这种模式还有很多不利之处:这些小文件强迫数据湖为每个文件保留更多的元数据,导致性能下降,最终可能导致内存溢出(OOM)问题。如果数据湖是云存储,为数据和元数据进行的大量API调用会使云成本增加十倍。数据处理引擎也因为需要管理更多的元数据而降低了查询性能。处理引擎中的并行任务不均衡,导致只有少数任务处理大部分数据。这是一个令人头疼的问题。解决方案是添加数据压缩和优化步骤,将所有小文件合并为较大文件。这个额外的步骤会减少文件数量,如图2-1中所示,在创建小文件之后的下一个步骤中。下游步骤最终到达数据仓库。添加数据压缩步骤最终会增加数据的延迟,使我们离实时数据处理架构的目标更远。结果,训练和服务之间的偏差增加,性能受到影响。反向ETL提取、转换和加载(ETL)是一种将数据从源移动到目标的过程,通常用于将数据从操作层移动到分析层。数据的移动类似于水流向下流入湖泊。参与ETL过程的所有技术系统都支持向湖泊自然流动,因为它们就是为此而构建和优化的。相反,反向ETL(rETL)将数据以相反的方向移动:从分析层到操作层。公司之所以这样做,是因为他们的业务核心定义仅存在于数据仓库中,操作应用程序需要这些核心定义的数据:Putting high-priced analytical database systems in the hot path introduces pants-on-head anti-patterns to supportability and ops. Who can say with a straight face that putting a data warehouse between two tier 1 apps is a good idea? Not many shops treat analytical systems—the data warehouse, ELT systems, offline feature stores, visualization systems—like tier 1, business-critical components. Eric Sammer, “We’re Abusing The Data Warehouse; rETL, ELT, And Other Weird Stuff,” Decodable, May 2022反向ETL存在的问题是数据流的方向不自然。通过将原本在分析层的数据移动到上游,你实际上是逆流而行。支持正向ETL的系统被用于进行反向ETL。但是,这些系统更喜欢将数据下游移动,而不是上游。例如,数据仓库并不像操作数据库那样针对读取进行优化。它的优化目标是分析,用于为业务决策者提供洞察,而不是用于面向客户的应用程序的用户。这就是流数据网格的优势所在。如果数据仓库中的数据已经在流平台中,它可以轻松被操作层使用,以提供给面向客户的应用程序。与数据流向数据仓库或湖泊的单向“流动”不同,流数据网格的隐喻更像是一个网格,其中允许每个方向的数据流动。Kappa架构在我们讨论Kappa架构之前,我们首先需要了解它的前身——Lambda架构。这个模式最初由Nathan Marz和James Warren(Manning出版社)在大数据领域引入。其目标是在一个大数据系统上提供实时的使用案例。Lambda架构介绍Lambda架构是一个数据流水线,分为两个分支:实时流处理和批处理(见图2-2)。这两个分支也可以被称为“在线”分支和“离线”分支。这是因为批处理分支需要一个能够存储历史数据的数据存储(很可能是数据仓库或数据湖)。实时或速度分支需要一个内存存储。为了更好地选择数据存储,最好遵循图2-3中的CAP定理。对于Lambda架构的批处理分支,使用支持分区容错的数据存储以实现水平可扩展性,允许存储大量的历史数据。随着添加更多的数据,可以自动增加分区和节点,提高数据存储的计算和存储容量。在其他两个CAP特性中,对于批处理分支来说,可用性比一致性更为重要。这是因为一致性已经由流处理分支处理了。当你拥有一个高可用的数据存储(图2-3中的可用性)时,可以实现更好的数据弹性,因为批处理源上的分区容错需要多个数据副本。在大多数常见的使用情况下,三个副本是标准,允许最多两个副本不可用而不会丢失批处理数据源。通过同时具备可用性和分区容错(AP),可以创建一个能够存储大量历史数据且在部分故障中不会丢失任何数据的数据存储。对于流式分支,支持一致性的数据存储更为重要,因为它应该保存数据的最新状态。如果数据存储的分区之间存在不同的数据状态,就无法确定实时状态。在Lambda架构中,实时/在线数据存储负责构建实时视图。请参考表2-2中的历史事件作为键值对和表2-3中的实时视图。请注意,历史数据(由批处理分支填充)和实时视图(由流式分支填充)不一致。在表2-2中的历史事件中,键3缺失,因为批处理尚未完成或开始将其填充到历史离线存储中。当批处理开始时,键3最终将出现在表2-2中。它还包含键1的两个版本,因为这是历史数据存储。 在表2-3中的实时视图中,键2缺失,因为它是24小时前添加的,不再存在于在线数据存储中。它包含键3,因为它是最近到达的,并将自动出现在在线存储中。 物化视图是持续更新的,而普通视图在查询执行时运行。这个视图封装了每个键值对的最新状态。例如,如果将键=1且值为"foo"的记录发送到数据存储,然后立即发送另一个键=1且值为"bar"的记录,物化视图中的记录将是键=1且值为"bar",实际上是键的最新值。在Lambda流水线的最后,您需要将流式和批处理数据存储进行同步,以获得实时历史数据视图。当您尝试连接两个不同的数据存储时,这种同步变得棘手。最可能的是,同步将使用Python或基于JVM的语言编写。最终同步的表将类似于表2-4。Lambda架构的批处理分支受到小文件限制的严重影响。始终需要进行数据优化步骤,以确保数据在并行处理过程中保持平衡;否则,大部分数据可能只经过少数几个处理过程,导致数据流水线花费更长时间。随着数据的增长,数据压缩和优化步骤将开始侵害与消费者达成的SLA协议。如果这些消费者是付费客户,这些优化问题将立即影响业务。在Kappa架构中,必须在数据流水线的开始处考虑数据平衡。通过保持数据在整个数据流水线中的优化状态,直到达到目的地,可以避免数据压缩和优化步骤,而这些步骤可能需要几分钟甚至几小时才能完成。Kappa架构介绍Kappa架构是Lambda架构的简化版本,允许您只使用流式数据管道作为数据源。在Lambda架构中,历史数据是通过批处理管道从数据湖或数据仓库中读取的。那么在Kappa架构中,历史数据从哪里获取呢?在Kappa架构中,流式平台需要能够向其消费者返回历史数据。为了实现这一点,它要么通过在流式平台集群中添加更多的代理来扩展其存储能力,要么启用分层存储功能,将较旧的数据转移到代理之外的较低层中。推荐选择分层存储,因为它消除了通过添加更多代理来扩展流式平台的要求,这样做会增加成本。如果不使用分层存储,流式平台将持续需要进行水平扩展活动以容纳所有历史数据,这也需要对所有数据的顶级存储进行扩展。以这种方式进行水平扩展还会向集群添加计算能力,而实际上并不一定需要这么多计算能力。扩展需要向流式平台添加更多的分区和代理,以及更多的提交日志(或分区),从而使数据分布在整个集群中。分层存储提供了一种更简单和更便宜的方式来保存历史数据,但它需要改变流式平台与其消费者进行交互的方式,使其能够请求历史数据,而不仅仅是传统消息系统中的最新数据。这种改变展示了事件驱动与事件源的概念。Kappa架构需要一个允许事件源的流式平台,而不仅仅是事件驱动架构,以便能够检索历史数据。在事件驱动架构(EDA)模式中,服务订阅状态的事件或变化。订阅的服务对事件变化进行操作。一个例子是一个人将其状态从单身更改为已婚。订阅了此状态变化的服务可能会向人力资源部门发送工资和福利变化的通知。基本区别在于:流式平台可以无限地在提交日志中保存其数据。提交日志是Apache Kafka使用的一种数据结构。新数据始终追加到日志的末尾,且不可变(参见图2-4)。事件驱动架构(EDA)的一个子集是事件溯源,如图2-5所示。EDA通常建立在消息传递系统之上。一些仅支持EDA的传统消息传递系统包括IBM MQ、RabbitMQ、ActiveMQ、Kinesis、Google Pub/Sub等等。所有这些消息传递系统都支持EDA。提交日志可以配置为保留其数据,这提供了在流处理平台中进行有状态的连接和聚合操作的能力。以前,这些有状态的转换只能在数据库中进行,这意味着将数据置于静止状态,从而强制进行批处理语义。 此外,请注意,在关系型上下文中进行大规模的历史连接通常在性能和可扩展性方面存在许多问题。通常,这些关系需要从磁盘中进行多次写入和读取,以满足查询输出的要求。而流处理允许用户或开发人员在适当的时间点在流中交集数据。 要实现流式数据网格,保持数据处于运动状态非常重要。这样做可以避免将数据写入数据存储时需要进行额外的数据优化步骤。这将使您能够在不必先将事件存储在数据库中的情况下执行实时的有状态转换(连接和聚合)。 数据中的每个记录都被分配了一个偏移量,以便数据消费者可以记住他们停止消费的位置,或者从特定偏移量重新播放记录。当启用无限存储时,这个提交日志将无限继续下去。在Apache Kafka中,流式数据存储在主题中,而主题由提交日志组成。提交日志用作将数据分区以便在数据处理中实现并行化的一种方式。在本书中,“提交日志”一词在Apache Kafka的上下文中与“分区”一词是同义的。如果使用分层存储,则流处理平台将标记一条记录为冷集(较旧且不经常访问的数据),并将其迁移到较低层,同时将热集数据(较新且需要快速访问的数据)保留在更快的顶层。在图2-6中,我们可以看到分层存储是如何在Confluent服务器中实现的。较低的存储层是对象存储,例如Amazon S3、Google Cloud Storage和Azure Blob Store。而顶层是Kafka代理中的存储。当应用程序请求超出热集范围的数据时,Confluent服务器会从对象存储中获取所请求的数据,并将其返回给应用程序。总结流数据网格的优势对于数据产品工程师和数据产品消费者来说应该是有动力的。例如,数据工程师不再需要维护Lambda架构中的两个数据流水线,不再担心数据湖中的小文件问题,并且可以更一致地构建满足SLA的数据流水线。数据产品消费者不再局限于仅批处理的用例,可以更快地对业务做出反应并更好地服务于业务。在接下来的章节中,我们将讨论如何通过首先构建领域来构建流数据网格。
0
0
0
浏览量2013
攻城狮无远

《流式Data Mesh》第八章:构建一个去中心化的数据团队

第八章:构建一个去中心化的数据团队公司需要一个合理的战略,以从本地架构转变为基于云的架构。大多数公司已经看到角色的发展和专业化程度提高,特别是与数据科学、数据工程和机器学习工程相关的角色。所有这些专业在云生态系统中处理数据方面起着关键作用。此外,过去几年间对数据专业人员的需求急剧增加。加上新一代受过数据基础设施教育的专业人员数量稳步下降,导致了人才缺口,即没有足够的专业人员来满足这些角色的需求。这个缺口导致了对数据基础设施领域中的软件(在第7章中介绍)的持续需求,这些软件可以帮助自动化关键任务,减轻人才短缺的负担。 技术能够解决一些现有的差距,但并非全部。因此,企业需要寻找提高现有技能集生产力的替代方案。由于这些技能集需求量很大,通常不能通过额外的招聘和人员增补来解决。构建一个去中心化的数据团队有助于填补这个缺口,通过在组织内部调动资源,提供支持流数据网格方法所需的技能集。 在本章中,我们将回顾传统的数据方法,以及其中的一些问题,并将其与一种将资源与数据对齐的新方法进行对比。这种新方法将业务和技术专长结合起来,形成支持开发与业务目标相一致的高质量数据产品的结构。传统的数据仓库结构数据工程角色传统上由熟悉数据库端的SQL、大数据生态系统中的MapReduce,并且通常了解如何将数据批量从操作层移动到数据仓库的人员承担。随着机器学习和数据科学在近年来变得更加普遍,数据工程师的角色变得更加繁重。这些角色变得更加庞大,并更加专注于DevOps、MLOps、数据科学和机器学习工程等领域。因此,“数据工程师”这个标准术语现在已经变得非常负荷过重,需要创建新的角色。在构建一个去中心化团队时,我们将介绍这些新角色,并讨论它们对团队的整体影响。图8-1展示了一个典型的数据仓库环境中的组织结构。在C级管理层下面有两个团队:支持日常运营的操作团队负责管理数据平面中数据仓库的团队在典型的数据仓库项目中,目标是将数据从操作源带入数据平面,然后将这些数据重构为另一种形式。行为洞察、数据科学和分析团队(此处未展示)从这个集中式数据仓库构建内容。这种方法非常常见,但存在许多问题。最近的一项研究显示,大约80%的数据仓库未能实现其既定目标。一些原因涉及数据本身(脏数据、数据孤立、数据访问、监管限制等),但其中一个主要因素在于组织结构本身。简单来说,数据仓库组织将数据工程师和ETL开发人员引入,从操作存储中获取数据,并使用新技术将其加载到新的表结构中。这些人经常发现自己处理的是他们不熟悉的数据,最初缺乏了解数据用途的业务背景,以及原始事务系统的工作原理的知识。这导致业务和操作团队之间进行许多会议,以确定将这些数据带入数据仓库的要求。数据工程团队,通常是数据平面团队的一个子集,随后承担起创建能满足所有用户需求的数据结构的负担,往往在孤立环境中做出关键设计决策,改变基数和分布,或者原始数据集的原始含义发生变化。这些数据结构可能过于泛化,不符合业务用户的确切需求。随着数据仓库项目的进行,业务必须如常进行。这意味着操作报告仍然是从事务性操作数据库生成的,希望在数据仓库完成后将这些活动迁移到数据仓库中。数据科学家通常有自己的流程(通常也有自己的数据工程师),以获取满足其需求的数据。一旦数据仓库项目完成,用户常常会发现基数和分布发生了变化,数据形式也不再熟悉。此外,权限也开始发挥作用:曾经可以访问日常任务所需数据的团队由于“安全原因”无法查询数据仓库(或数据仓库中的表)。这些问题以及其他问题导致团队寻找变通方法和替代数据源,以规避数据仓库。组织创建一个管理的、单一的数据真实版本的目标常常变得模糊和分散。实际上,一个组织开始创建许多真实源,每个源都有其自己的“独立”ETL和数据工程步骤。在更严重的情况下,团队还可能建立自己的基础架构来创建和维护事务数据的影子副本,进而引发更多关于安全性和数据质量的问题。引入去中心化团队结构在图8-2中,我们介绍了去中心化团队的概念。这个结构拥有与数据仓库组织相似的资源,但重新组织这些角色,使其更紧密地与三个领域相关联:(1) 数据专业知识,(2) 业务专业知识,以及(3) 基础架构专业知识和与控制平面相关的数据转换。在这种新的网状结构中,整个组织更加紧密地与数据本身联系,使领域内的资源直接与提供给业务用户的数据相一致。在流式数据网格中,领域团队负责领域内数据的所有方面,包括创建、摄取、准备和提供数据。通过领域联合所有权,业务可以保持每个领域的原始上下文,因为团队对其数据非常了解。领域团队的责任从中央基础架构团队转移,并专注于为组织提供有价值的数据产品。去中心化数据团队的一个关键特征是需要从传统的集中式方法转变为端到端的流程,其中数据产品成为多个组织技能集中的焦点,以实现有价值的成果。去中心化组织将为担任传统技术角色(架构师、分析师和数据工程师)的团队成员确定明确定义的能力归属。团队绩效将根据他们作为专注领域团队一部分的工作得到更合适的衡量和奖励。随着数据产品开发团队的形成,团队变得多学科,以满足关键组织需求(并在从一个团队转移到另一个团队时增加价值的能力)。这些去中心化领域团队虽然专注于其领域内的内容,但使业务能够从消费者的角度思考,确保优先考虑质量,并确保数据产品满足客户需求(在这种情况下,是其他领域的数据消费者)。领域所有权原则进一步加强了数据网格的第二个原则:“将数据视为产品”。因为每个领域拥有自己的数据,并负责为其消费者生成和开发数据,所以这些数据应该具有高质量、接近实时和可信赖的特点。最重要的是,数据产品及其领域必须足够可靠和可信赖,以成为创建新数据领域和产品的领域互操作性的源头。组织的思维方式需要从创建一个集中式团队来为所有数据请求提供服务的模式转变为一个领域内生成的数据可以被其他领域使用的模式。让我们来看一下去中心化数据团队带来的四个主要优势领域:赋权人员、工作流程、促进协作和数据驱动的自动化。赋权人员发展一个去中心化的数据团队对组织的几乎所有方面都有重大影响,包括结构、角色、责任、业务词汇、流程、协作模式和技术。产品负责人确保领域消费者处于产品开发的中心位置。他们需要获得权威来做出关键决策,并直接与客户进行互动,避免不必要的障碍。直接与客户互动确保需求和优先事项能清晰地传达到领域开发过程中,进而推动敏捷性、提高生产力和客户满意度。领导者需要适应变化,因为一些角色从项目的前线领导转变为由产品负责人领导的领域开发过程中增加价值的能力管理。团队成员需要了解自己的具体角色,并适应在多个项目中更灵活地工作,发挥核心技能,在所分配的领域内创造价值。当采用去中心化团队方法时,高级领导层需要积极且明显地领导文化变革。一位优秀的领导者必须明确、达成共识并采纳数据聚焦文化所需的价值观和行为准则。领导者需要挑战和解决干扰传统集中化团队协作的行为,确保他们在领域内工作,为组织通过为消费者提供最大价值而服务其客户群体。工作流程在去中心化团队中,新产品和服务是与最终消费者(即客户)密切互动开发的,而想法和原型在开发过程的早期就进行了现场测试,这样团队可以迅速获得改进的反馈意见。这需要参与和建立关系的技能。在各个职能部门内部、跨多个地区,并使用通用的共同语言部署标准化流程,使团队能够清晰地相互沟通,增加了流动性和扩展性。当需求变化需要快速行动和解决方案时,通过将人员虚拟地转移到最需要的地理位置,可以产生全球影响。团队需要可衡量的流程绩效目标,以帮助追踪客户满意度并确定如何改进流程。鼓励团队成员进行实验和迭代,寻找改进流程和客户结果的方法。在整个组织中,鼓励“快速失败”,支持(并奖励)创业精神。促进协作在创建去中心化团队时,从一开始就需要着力促进团队之间的紧密协作。具备强大的产品负责人,能够围绕跨领域的价值主张调动多技能团队的能力是至关重要的。产品负责人需要具备足够的知识,能够与来自IT、销售、市场营销和数据交付等不同团队的成员合作,并理解他们提出的问题。如果要让自主管理的团队能够识别持续改进并在团队之间共享知识,信息和数据的透明性变得尤为重要。于数据驱动的自动化高级分析能够实现基于数据驱动的自动化。通过先进的技术,组织可以自动提取洞察并提供建议。这种技术为改善决策提供智能支持,可应用于业务的许多方面。数字工具可以通过重构将操作数据转化为数据产品的过程来实现。机器人流程自动化(RPA)是一种新兴技术,可在涉及从多个系统汇总数据的流程中取代人力。此外,由数据科学家和机器学习专家构建的模型现在可以实时开发、部署和维护。虽然这些工具可以在传统的数据架构中使用,但分散团队的方法允许域团队在这些主题领域内使用这些工具来改进产品交付。由于分散团队负责其服务的域的范围,自动化工具可以用于解决特定问题,而不是应用于整个数据集的更广泛范围。在解释新角色之前,需要了解数据作为产品的几个方面。以下是对这些原则的简要概述。随着我们介绍新角色,我们将阐述这些作为数据产品的方面:一个域可以拥有一个或多个数据产品。数据产品需要实现互操作性。它们可以使用其他数据产品的输出并生成自己的输出。使用彼此之间的多个数据产品将形成一个数据产品网格。技术实现,例如数据来源、数据建模和ETL,将在数据产品之下进行抽象。数据产品团队本身将有权在域级别设计和实施技术解决方案。一些数据产品将与操作源系统对齐。一些数据产品将消耗来自与源对齐的数据产品以及其他数据产品的输出,以生成增值输出。一些数据产品将与特定的业务需求对齐,例如行为洞察报告或数据科学。数据领域中的新角色在数据领域中,需要创建一些新的角色来适应组织对数据的新思维方式。其中最重要的角色是域产品负责人。该角色的职责包括:(1)制定数据产品的愿景和路线图,(2)确保数据产品在组织内满足消费者需求和实用性,(3)确保可用性、质量和可追溯性,并维护服务水平。在数据网格中,数据工程师的角色是必需的,但与传统意义上的数据工程师不同。对于流式处理的用例,标准SQL和ETL被使用KStream API与Kotlin作为核心,或者使用像ksqlDB这样的工具进行转换。现在,数据工程师需要理解如何在这种范式下正确工作,将数据转换应用于流式数据集而不是关系表。在本节中,我们默认理解数据工程师是指域数据工程师。虽然数据工程师服务的源和目标不同,但角色本质上是相同的:可从流式数据源中消费操作源数据,并将转换后的数据发布为为消费者提供服务的数据产品。这需要具备数据转换技能,以及在设计和创建标准化、有版本控制的API方面的扎实背景,这些API可以作为数据产品发布到中央仓库。数据平面中的新角色数据网格和流数据网格的一个优势是将使数据可用的责任从中央基础架构团队转移到其他团队。这使得基础架构团队可以专注于创建可靠、可用和实用的自助服务资产,以赋予领域团队更多的权力。而领域团队则专注于访问数据及其转换,基础架构和自助服务工具由数据平面团队负责管理。数据平面团队由数据专家和数据平面数据工程师组成,他们熟悉源系统,并能够通过连接器将其发布到流数据网格中进行管理。这个角色负责可靠、高性能和可扩展的连接,从事务源中提取数据,并将其发布到领域中。该角色创建来自源系统的标准输入和输出接口,可以是流或API的形式。网格可靠性工程师的角色专注于网格的整体性能和可靠性。通过监控、维护和响应网格部署的性能和利用率指标来实现这一目标。该角色可能负责扩展或管理分层存储、水平扩展数据产品,并向领域经理和产品负责人报告使用指标。此外,该角色还负责整体部署的稳定性。例如,如果数据产品部署在Kubernetes上,流也可以部署在Kubernetes上。网格可靠性工程师确保处理过程可靠,并能在基于Kubernetes的集群上进行。流数据网格是一个以人和工具为中心的自助服务平台。这引入了应用工程师的角色。需要新的工具,提供简单的用户界面来定义和注册事件和事件模式,提供流平台的配置,提供CLI接口来配置数据流管道,并测试转换脚本。此角色的人员将在一个高技能团队中工作,构建和维护领域团队使用的工具集,以及为支持控制平面的团队提供工具。通过创建这些工具,随着时间的推移,减少了对高技能控制团队的需求。需要指出的是,最终该团队可能主要由通才组成,只有少数专家。此外,应用工程师的角色还负责维护和部署操作和分析应用程序底层的技术。这包括常用工具,如Jupyter笔记本,以及用于模型训练的TensorFlow、XGBoost、Keras和PyTorch等库。数据科学和商业智能中的新角色流数据网格旨在作为流技术和数据科学之间的中间层,以满足分析模型和特征存储的需求。因此,许多角色的出现是为了为数据科学提供支持。在定义了所有这些角色之后,数据科学团队将扮演重要角色,因为他们接触到统计学并能够正确应用统计学。数据科学家和软件工程专家Josh Wills将科学家定义为“在统计学方面比任何软件工程师更擅长,在软件工程方面比任何统计学家更擅长的人”。这个定义很好地捕捉了现代数据科学家日常工作的真实本质。数据科学家通常不是优秀的软件工程师,也不是百分之百的统计学家。然而,数据科学家需要了解整个过程的各个方面,以完成以下任务:整合适当的数据以建立模型将数据转换为构建模型所需的有用格式对数据应用适当的统计预处理步骤选择适当的算法来预测结果理解目标部署平台这是一个艰巨的任务。真正的数据科学家必须能够有效地(而非最佳地)在这些领域应用知识,以便将结果呈现给业务部门进行评估,并理解适用于数据科学这个整体概念下的所有操作组件。数据科学家需要了解如何有效地应用ETL技术来整合数据并将其转换为模型所需的有用格式。 数据科学家执行的许多任务更适合由数据工程师来完成。然而,一位通用的数据工程师可能对数据科学家希望如何组织数据不太熟悉。这就引出了一种新的角色,即数据科学工程师或机器学习工程师。数据科学家需要在首次整合和转换数据时对数据应用适当的统计预处理步骤。这需要一个比数据工程师更专业化但又不完全达到数据科学家水平的角色。这个角色可以由数据科学工程师担任,也可以分为新角色,即特征处理工程师。在数据科学家的指导下,这些特征处理工程师理解所需的数据转换,并在数据网格中具备技术能力来实现它们。 模型的数据准备通常是整个过程中被忽视的一个方面,并且通常被视为一个不需要过多关注的神秘黑盒子。在大多数业务人员的想法中,数据输入模型,结果出现。实际上,这是一个复杂的过程,需要深思熟虑和关注。分类变量通常被盲目地输入到一种独热编码转换中(将分类变量转换为每个组合出现的零和一的向量)。然而,这种方法常常被过度使用,既无法有效地预测结果,又在模型训练和推断中效率低下(有时甚至在计算上不可行)。对于分类变量,通常需要适当的降维处理,有许多可以应用于分类变量的技术,例如:目标编码(Target encoding)使用深度学习框架进行训练的嵌入(Embeddings)主成分分析(PCA)此外,任何类型的降维处理都需要用人类可理解的方式进行解释,以便供业务分析师和最终用户理解。例如,PCA 在解释超过前两个主成分之后变得非常困难。训练的嵌入也很难解释(虽然较为容易)。目标编码在其最简单形式中是一个分类值与已知结果之间的概率关系。虽然能够解释这些转换很重要,但选择适合构建模型的正确方法也同样重要。当数据科学家确定模型需要哪种变量编码类型时,特征处理工程师必须了解如何实施这个解决方案。就像分类变量需要适当处理一样,数值型输入也需要。连续变量中的数据通常需要进行标准化或归一化处理(这两个概念完全不同),以便正确训练模型。一些模型框架会自动处理这一点(例如XGBoost),而其他一些则不会。此外,地理坐标虽然是数值型的,但通常需要特殊处理,因为这些数据点之间存在已知的距离关系。在数据科学(通常还包括统计学)的指导下,可以对数据应用适当的转换,以最好地适应模型。最后,在转换时还需要考虑输出变量,具体取决于模型的期望结果。例如,如果正在训练模型来发现异常的保险理赔,那么因变量(或结果变量)可能需要以标准化术语来表示,以便可以用标准偏差的单位来测量实际值和预测值之间的距离。此外,任何模型中的因变量通常需要代表高斯分布,以使模型不必在计算上努力寻找关系。在数据科学家的指导下,特征处理工程师可以最优地编排和应用适当的工程技术来生成所需的模型输入。虽然数据科学家或机器学习实践者需要能够应用适当的模型,但另一个重要方面是扩展了DevOps的概念,创建了MLOps角色。该角色将模型及其在模型构建阶段产生的所有构件,创建一个流水线,以正确应用所有必需的转换,实现适当的实时推断和批处理推断。虽然由数据科学家和特征处理工程师开发的模型训练过程有责任将其转换与模型本身捆绑在一起(以及适应统计数据和其他相关值),但MLOps必须以与模型训练相等的方式应用这些转换。同时,在数据科学家的指导下,还需要适当处理以前从未见过的值。最近,“流水线”这个术语在MLOps和数据科学中变得非常常见。在模型构建和推断的情况下,存在数据转换流水线和模型部署流水线。部署流水线本身通常可以使用标准的敏捷开发流程构建,并使用常见的工具(如Jenkins或Azure DevOps)进行部署。在部署模型时,其相关转换必须发布到数据血统库,并记录为已知转换。这消除了对已应用的转换及其对结果的潜在影响的猜测。无论如何,了解模型所采取的步骤必须对任何使用模型的用户都是透明和可查看的。业务智能工程师是另一个角色,需要结合来自操作平面的数据源知识以及由数据科学团队生成的分析数据产品的暴露。在这个角色中,需要结合数据科学和通用流数据工程师的技能。随着时间的推移,控制平面团队可能会创建一些自动化集成的工具,从而减少对专业角色的需求。表8-1提供了这些角色的摘要。角色的对齐仅供参考。可以根据个体业务需求进行定制。总之,通过将这些角色嵌入到领域团队中,领域团队能够自给自足地开发、部署和维护其数据产品。工程师、分析师、领域专家和技术专家能够共同合作创建世界级的数据产品和解决方案。一方面是技术专家,另一方面是业务背景的专家。一个赋权的数据产品文化将责任推给那些具有明确目标的赋权的自治团队。业务目标仍然由高级管理层确定,但决策和解决方案的过程变得更加地本地化,由自治团队来完成。
0
0
0
浏览量1874

履历