DataPipeline丨构建实时数据集成平台时,在技术选型上的考量点
文 | 陈肃 DataPipeline CTO
随着企业应用复杂性的上升和微服务架构的流行,数据正变得越来越以应用为中心。
服务之间仅在必要时以接口或者消息队列方式进行数据交互,从而避免了构建单一数据库集群来支撑不断增长的业务需要。以应用为中心的数据持久化架构,在带来可伸缩性好处的同时,也给数据的融合计算带来了障碍。
由于数据散落在不同的数据库、消息队列、文件系统中,计算平台如果直接访问这些数据,会遇到可访问性和数据传输延迟等问题。在一些场景下,计算平台直接访问应用系统数据库会对系统吞吐造成显著影响,通常也是不被允许的。
因此,在进行跨应用的数据融合计算时,首先需要将数据从孤立的数据源中采集出来,汇集到可被计算平台高效访问的目的地,此过程被称为ETL,即数据的抽取(Extract)、转换(Transform)和加载(Load)。
ETL并不是什么新鲜事物。
该领域的传统公司,例如Informatica,早在1993年就已经成立,并且提供了成熟的商业化解决方案。开源工具,例如Kettle、DataX等,在很多企业中也得到了广泛的应用。
传统上,ETL是通过批量作业完成的。即定期从数据源加载(增量)数据,按照转换逻辑进行处理,并写入目的地。根据业务需要和计算能力的不同,批量处理的延时通常从天到分钟级不等。在一些应用场景下,例如电子商务网站的商品索引更新,ETL需要尽可能短的延迟,这就出现了实时ETL的需求。
在实时ETL中,数据源和数据目的地之间仿佛由管道连接在一起。数据从源端产生后,以极低的延迟被采集、加工,并写入目的地,整个过程没有明显的处理批次边界。
实时ETL,又被称为Data Pipeline模式。
阿里提出了“数据中台”的概念。即数据被统一采集,规范数据语义和业务口径形成企业基础数据模型,提供统一的分析查询和新业务的数据对接能力。
数据中台并不是新的颠覆式技术,而是一种企业数据资产管理和应用方法学,涵盖了数据集成、数据质量管理、元数据+主数据管理、数仓建模、支持高并发访问的数据服务接口层开发等内容。
在数据中台建设中,结合企业自身的业务需求特点,架构和功能可能各不相同,但其中一个最基本的需求是数据采集的实时性和完整性。数据从源端产生,到被采集到数据汇集层的时间要尽可能短,至少应做到秒级延迟,这样中台的数据模型更新才可能做到近实时,构建在中台之上依赖实时数据流驱动的应用(例如商品推荐、欺诈检测等)才能够满足业务的需求。
以阿里双十一为例,在极高的并发情况下,订单产生到大屏统计数据更新延迟不能超过5s,一般在2s内。
中台对外提供的数据应该是完整的,源端数据的Create、Update和Delete都要能够被捕获,不能少也不能多,即数据需要有端到端一致性的能力(Exactly Once Semantic,EOS)。
当然,EOS并非在任何业务场景下都需要,但从平台角度必须具备这种能力,并且允许用户根据业务需求灵活开启和关闭。
在构建实时数据集成平台时,就一些技术选型问题,建议做以下考量:
一、数据源变化捕获
源数据变化捕获是数据集成的起点,获取数据源变化主要有三种方式:
-
基于日志的解析模式;
-
基于增量条件查询模式;
-
数据源主动Push模式。
基于日志的解析模式常用于各种类型的数据库,例如MySQL的Binlog、Oracle的Redo&Achieve Log、SQL Server Change Tracking & CDC等。
不同数据库日志解析的原理差别很大,以MySQL Binlog模式为例,解析程序本身是一个Slave,能够实时收到MySQL Master的数据流推送,并解析还原成DDL和DML操作。而SQL Server的CT模式下,增量是通过定期查询Change Tracking表实现的。
基于增量条件的查询模式不依赖于源端开启日志记录,但对于数据源通常有额外的格式要求。例如,数据库表或文档对象需要有标志更新时间的字段,这在一些业务系统中是无法满足的。
数据源主动Push模式的常见形式为业务插码,即应用系统通过打点或者配置切面的方式,将数据变化封装为事件,额外发送一份给数据集成平台。这种方式一般需要对源端系统代码进行一定程度的修改。
通常而言,基于数据库的日志进行增量捕获应当被优先考虑。其具备以下几个显著优点:
-
能够完整获取数据变化的操作类型,尤其是Delete操作,这是增量条件查询模式很难做到的;
-
不依赖特别的数据字段语义,例如更新时间;
-
多数情况下具备较强的实时性。
当然,事物都具有两面性。开启数据库日志通常会对源库性能产生一定的影响,需要额外的存储空间,甚至一些解析方法也会对源库资源造成额外消耗。因此,实施过程中需要在DBA的配合下,根据数据库特点和解析原理进行DB部署规划。
推荐使用数据库的复制和灾备能力,在独立服务器对从库进行日志解析。此外,当数据库产生批量更新时,会在短时间内产生大量日志堆积,如果日志留存策略设置不当,容易出现数据丢失。这些都需要根据具体的业务数据增长特点,在前期做好规划,并在上线后根据业务变化定期进行评估和调整。
数据源主动push模式下,由于事件发送和业务处理很难做到事务一致性,所以当出现异常时,数据一致性就无从保证,比较适合对于数据一致性要求不高的场景,例如用户行为分析。
二、运行环境
无论采用何种数据变化捕获技术,程序必须在一个可靠的平台运行。该平台需要解决分布式系统的一些共性问题,主要包括:水平扩展、容错、进度管理等。
1. 水平扩展
程序必须能够以分布式job的形式在集群中运行,从而允许在业务增长时通过增加运行时节点的方式实现扩展。
因为在一个规模化的企业中,通常要同时运行成百上千的job。随着业务的增长,job的数量以及job的负载还有可能持续增长。
2. 容错
分布式运行环境的执行节点可能因为过载、网络连通性等原因无法正常工作。
当节点出现问题时,运行环境需要能够及时监测到,并将问题节点上的job分配给健康的节点继续运行。
3. 进度管理
job需要记录自身处理的进度,避免重复处理数据。另外,job会因为上下游系统的问题、网络连通性、程序bug等各种原因异常中止,当job重启后,必须能够从上次记录的正常进度位置开始处理后继的数据。
有许多优秀的开源框架都可以满足上述要求,包括Kafka Connect、Spark、Flink等。
Kafka Connect是一个专注数据进出Kafka的数据集成框架。Spark和Flink则更为通用,既可以用于数据集成,也适用于更加复杂的应用场景,例如机器学习的模型训练和流式计算。
就数据集成这一应用场景而言,不同框架的概念是非常类似的。
首先,框架提供Source Connector接口封装对数据源的访问。应用开发者基于这一接口开发适配特定数据源的Connector,实现数据抽取逻辑和进度(offset)更新逻辑。
其次,框架提供一个分布式的Connector运行环境,处理任务的分发、容错和进度更新等问题。
不同之处在于,Kafka Connect总是将数据抽取到Kafka,而对于Spark和Flink,Source Connector是将数据抽取到内存中构建对象,写入目的地是由程序逻辑定义的,包括但不限于消息队列。
但无论采用何种框架,都建议首先将数据写入一个汇集层,通常是Kafka这样的消息队列。
单就数据源采集而言,Kafka Connect这样专注于数据集成的框架是有一定优势的,这主要体现在两方面:
首先是Connector的丰富程度,几乎所有较为流行的数据库、对象存储、文件系统都有开源的Connector实现。
尤其在数据库的CDC方面,有Debezium这样优秀的开源项目存在,降低了应用的成本。
其次是开发的便捷性,专有框架的设计相较于通用框架更为简洁,开发新的Connector门槛较低。Kafka Connect的runtime实现也较为轻量,出现框架级别问题时debug也比较便捷。
尽管目前版本的Kafka Connect还不支持数据采集后进入Kafka的EOS保证,但通过对runtime的修改,利用Kafka事务消息也能够实现这一点。相信Kafka Connect未来的版本也会很快提供官方的支持。
三、数据汇集层
当各类数据从源端抽取后,首先应当被写入一个数据汇集层,然后再进行后继的转换处理,直至将最终结果写入目的地。数据汇集层的作用主要有两点:
首先,数据汇集层将异构的数据源数据存储为统一的格式,并且为后继的处理提供一致的访问接口。这就将处理逻辑和数据源解耦开来,同时屏蔽了数据抽取过程中可能发生的异常对后继作业的影响。
其次,数据汇集层独立于数据源,可被多次访问,亦可根据业务需要缓存全部或一定期限的原始数据,这为转换分析提供了更高的灵活度。当业务需求发生变化时,无需重复读取源端数据,直接基于数据汇集层就可以开发新的模型和应用。数据汇集层可基于任意支持海量/高可用的文件系统、数据仓库或者消息队列构建,常见的方案包括HDFS、Hbase、Kafka等。
针对实时ETL场景,推荐使用Kafka或类似具有海量数据持久化能力的消息队列来做数据汇集层,这会为后继的流式处理提供便捷。同时,利用Kafka的数据回收机制,可以根据业务需要自动保留一定时间或大小的原始数据。
四、数据转换
数据转换是一个业务性很强的处理步骤。
当数据进入汇集层后,一般会用于两个典型的后继处理场景:数仓构建和数据流服务。
数仓构建包括模型定义和预计算两部分。数据工程师根据业务分析需要,使用星型或雪花模型设计数据仓库结构,利用数据仓库中间件完成模型构建和更新。
开源领域,Apache Kylin是预聚合模式OLAP代表,支持从HIVE、Kafka、HDFS等数据源加载原始表数据,并通过Spark/MR来完成CUBE构建和更新。
Druid则是另一类预聚合OLAP的代表。在Druid的表结构模型中,分为时间列、维度列和指标列,允许对任意指标列进行聚合计算而无需定义维度数量。Druid 在数据存储时便可对数据进行聚合操作,这使得其更新延迟可以做到很低。在这些方面,Baidu开源的Palo和Druid有类似之处。
一个普遍的共识是,没有一个OLAP引擎能同时在数据量,灵活性和性能这三个方面做到完美,用户需要基于自己的需求进行取舍和选型。预计算模式的OLAP引擎在查询响应时间上相较于MPP引擎(Impala、SparkSQL、Presto等)有一定优势,但相对限制了灵活性。
如前文所述,源端采集的数据建议放入一个汇集层,优选是类似Kafka这样的消息队列。包括Kylin和Druid在内的数据仓库可以直接以流式的方式消费数据进行更新。
一种常见的情形为:原始采集的数据格式、粒度不一定满足数据仓库中表结构的需要,而数仓提供的配置灵活度可能又不足够。这种情况下需要在进入数仓前对数据做额外的处理。
常见的处理包括过滤、字段替换、嵌套结构一拆多、维度填充等,以上皆为无状态的转换。有状态的转换,例如SUM、COUNT等,在此过程中较少被使用,因为数仓本身就提供了这些聚合能力。
数据流服务的构建则是基于流式计算引擎,对汇集层的数据进一步加工计算,并将结果实时输出给下游应用系统。这涉及到流式计算引擎的选择:Spark Streaming、Flink、还是Kafka Streams?
关于三个引擎的对比,网上有很多资料,在此不再赘述。
选型过程中有几点值得特别关注:
1. 延迟性
Spark对流的支持是MicroBatch,提供的是亚秒级的延迟,相较于Flink和Kafka Streams在实时性上要差一些。
2. 应用模式
Spark和Flink都是将作业提交到计算集群上运行,需要搭建专属的运行环境。
Kafka Streams的作业是以普通Java程序方式运行,本质上是一个调用Kafka Streaming API的Kafka Consumer,可以方便地嵌入各种应用。
但相应的,用户需要自己解决作业程序在不同服务器上的分发问题,例如通过K8s集群方案进行应用的容器化部署。如果使用KSQL,还需要部署KSQL的集群。
3. SQL支持
三者都提供Streaming SQL,但Flink的SQL支持要更为强大些,可以运行更加复杂的分组聚合操作。
4. EOS
Flink对于数据进出计算集群提供了框架级别的支持,这是通过结合CheckPoint机制和Sink Connector接口封装的二阶段提交协议实现的。
Kafka Streams利用Kafka事务性消息,可以实现“消费-计算-写入Kafka“的EOS,但当结果需要输出到Kafka以外的目的地时,还需要利用Kafka Connect的Sink Connector。
遗憾的是,Kafka Connect不提供Kafka到其它类型Sink的EOS保证,需要用户自己实现。
Spark Streaming与Kafka Streams类似,在读取和计算过程中可以保证EOS,但将结果输出到外部时,依然需要额外做一些工作来确保数据一致性。常见的方式包括:利用数据库的事务写入机制将Offset持久化到外部、利用主键保证幂等写入、参考二阶段提交协议做分布式事务等。
小结
本文简要讨论了一些构建面向实时数据的集成平台在技术选型方面的考量点。
数据源变化捕获是数据集成的起点,结合日志的解析、增量条件查询模式和数据源主动Push模式,最终构建出一个数据汇集层。在这个阶段,推荐考虑Kafka Connect这类面向数据集成的专有框架,可以有效缩短研发周期和成本。
数据汇集层建议构建在消息队列之上,为后继的加工处理提供便利。如果需要全量持久化长期保存,建议结合使用消息队列和分布式文件系统分别做实时数据和全量数据的存储。
流式处理能力是实时数据集成平台必要的组件。结合企业技术栈特点,选用包括Flink、Spark Streaming、Kafka Streams等流行的引擎在多数情况下都能够满足要求。
端到端数据的EOS是数据集成中的一个难题,需要用户根据业务实际需求、数据本身的特性、目的地特点case by case去解决。