起源

大数据起源于个人 PC 普及,互联网爆发性增长,Google、雅虎等头部公司数据量级远超单机可处理上线,传统的数据库基本无法处理,因此开始探索新型的数据存储和计算技术。在此背景下 Google 发布了内部研发成果的论文,即 Google 三架马车的 GFS、MapReduce 和 Bigtable 论文,后来雅虎基于 GFS/MapReduce 论文建立了开源的 Hadoop 项目,奠定了后续十多年大数据发展的基础,同时大数据一词被广泛被用于描述这类数据量过大或过于复杂而无法通过传统单机技术处理的系统。

虽然以 MapReduce 为代表的数据存储计算框架在搜索引擎场景获得巨大成功,但是在数据库社区看来,MapReduce 反而是一次巨大的倒退:

  • 缺乏高级数据访问语言,由 SQL 语言变成 Java,编程模型的巨大倒退
  • 与 DBMS 系统不兼容,缺乏 schema、数据更新、事务能力
  • 实现落后,基本是暴力遍历而不是使用索引

一直到今天,这依然是大数据系统的不足,时至今日依然具有指导意义,指明大数据系统的后续发展方向:

  • hive/spark 提供高级编程语言
  • parquet/orc 存储格式为数据文件添加了索引
  • 数据湖格式提供 ACID 事务、数据更新

由于大数据系统特性上的种种不足和技术栈的独立性,大数据虽然发展迅猛,各种项目百花齐放,但是多定位在某些细分场景,不是通用场景解决方案,导致大数据生态圈的复杂,组件众多,大数据具有较高的应用门槛。

  • hadoop。map-reduce/hdfs/yarn
  • datax
  • airflow
  • hive/spark/flink
  • hbase

随着发展,统一存储,统一计算,在离线服务混部,HTAP:

  • 统一存储。kafka、hive
    • 实时。以 kafka 为代表,基于消息队列,具有高吞吐、低延迟,但是数据存储时效短、不可查询的特征
    • 离线。以 hive 为代表,基于文件,具有海量存储、查询慢、不支持更新只能覆盖的特征
    • 流批一体。以 Iceberg/Hudi/DeltaLake/Paimon 为代表的数据湖格式基于文件来实现队列特性,以 Kafka/Pulsar 为代表的消息队列基于队列实现文件特性
  • 统一计算。
    • 实时。以 flink 为代表
    • 离线。以 hive/spark 为代表
    • 流批一体。2015 年 Google Dataflow Model 论文的发布厘清了流处理和批处理的对立统一的关系,即批处理是流处理的特例,这为流批一体的大趋势奠定了基础。

在数据湖等基于文件的存储中,流式读取通常以监听 Changelog 的方式实现;而在基于队列的存储中,批处理要重算更新结果,则无法直接删除或覆盖之前已经写入队列的结果,要么转为 Changelog 要么重建一个新队列

版本控制。由于更新方式的不同,文件中的数据是可变的,而队列中的数据是不可变的

文件表示某个时间点的状态,因此数据湖需要版本控制以增加回溯的功能;而相对地,队列则表示一段时间内状态变化的事件,本来有 Event Sourcing 的能力,因此不需要版本控制

并行写入。文件有唯一的写锁,只允许单个进程写入。数据湖通常以整个目录作为一个表暴露给用户,如果有多并行写入,则在该目录下为每个并行进程新增基于文件的快照进行隔离(MVCC)。而相对地,队列本来就支持并行写入,因此无需快照隔离

相信不少读者已经隐约感觉到:基于文件的存储类似流表二象性中的表,适合用于保存可以被查询的可变状态(计算的最终结果或中间结果),而基于队列的存储类似表示流表二象性中的流,适合用于保存被流计算引擎读取的事件流(Changelog 数据)。

虽然流表二象性能使得两者可以交替使用,但若使用不当会导致数据在流表两种状态间进行不必要的转换,并给下游业务造成额外的麻烦。具体来讲,如果文件系统中存的是 Changelog 数据,那么下游进行流式读取(监听)时,读到的是 Changelog 的 Changelog,完全不合理。相对地,如果消息队列存的是非 Changelog 数据,那么该队列则丢失了更新的能力,任何更新都会导致消息不同版本的同时存在。由于目前 Changelog 类型一般由 CDC 或者流计算的聚合、Join 产生,还未推广到一般的 MQ 使用场景,所以后一种问题更常发生。但笔者认为,Changelog 是更加流原生的格式,未来大概会标准化并普及到队列存储中,目前非 Changelog 的数据则可以被看作是 Append-Only 业务的特例。

上述的结论可以被应用到当前热门的实时数仓建设中。除了 Lambda 架构,当前实时数仓架构主要有 Kappa 架构和实时 OLAP 变体两种[9],无论哪种通常都使用 Kafka/Pulsar 等 MQ 作为 ODS/DWD/DWS 等中间层的存储,OLAP 数据库或 OLTP 数据库作为 ADS 应用层的储存。这样的架构主要问题在于不够灵活,比如若想直接基于 DWD 层做一些 Ad-hoc 分析,那么常要将 DWD 层 MQ 中的数据再导出到数据库再做查询。

如果使用 Flink 直接读 MQ 数据来算呢?其实是可以的,因为像 Pulsar 也提供了无限期的存储,但效率会比较低,主要原因是 MQ 无法提供索引来实现谓词下推等优化[10],另外经过聚合或者 Join 的数据是 Changelog 格式,数据流中会包含旧版本的冗余数据。因此业界有新的趋势是用 Iceberg 等数据湖来代替 MQ 作为数仓中间层的存储,这样的优点是能比较好地对接离线数仓及其长久以来的业务模式,而代价则是数据延迟可能变为近实时。以本文 “文件适合存储状态” 的观点来讲,实时数仓中需要被业务查询的表的确更适合用文件存储,因为业务需要的是状态,而不关心变更历史。