大数据系统
Big Data Systems
2003 年,Google 内部工程师在每天爬取、索引数十亿网页,每周要处理数 PB 数据时,意识到已有的工具完全无法应对:单台服务器不够,商业数据库太贵且扩展性有限。
他们需要一种全新的计算范式,能够把普通廉价服务器组成集群,分布式地处理这些数据。他们发明了MapReduce,并把底层存储叫做 GFS(Google File System)。2006 年的 Bigtable 再把稀疏表铺在文件层上。两篇论文在 2003-2004 年发表,触发了整个"大数据"行业的爆炸。
破除误解:"大数据"不只是"很多数据"
"大数据"在营销语境中泛滥成灾——"数据驱动"、"AI 赋能"……实际上,大数据系统工程解决的是具体的技术挑战:
3V 经典定义(分析师 Doug Laney 在 META Group,2001 年提出): - Volume(体量):数据量超出单台机器的处理能力(TB、PB 级别) - Velocity(速度):数据生成和处理的速度要求(实时流处理 vs. 批处理) - Variety(多样性):结构化数据(SQL 表)、半结构化数据(JSON、日志)、非结构化数据(图片、视频)
后来有人加了第 4 个 V:Veracity(真实性)——数据质量和可信度的挑战。
这里要澄清一个常被以讹传讹的细节。Laney 那份 2001 年 2 月的备忘录标题是《3D 数据管理》,通篇没有出现"大数据"一词——它谈的是数据管理被这三个维度逼到了极限。
"3V" 是后来"大数据"成了热词,才被反向追认为它的定义。换句话说,先有了营销概念,人们才回头给它配上一个看起来严谨的框架。
"大数据系统"在工程上意味着:如何在有限预算、有限时间内,对超出单机容量的数据进行可靠、高效的处理和分析。
MapReduce:分而治之的计算范式
Google 的 MapReduce(Dean & Ghemawat,2004)把大规模数据处理抽象为两个函数:
Map 阶段:对每个输入记录独立应用 Map 函数,输出 (key, value) 对:
Map(input_key, input_value) -> list of (intermediate_key, intermediate_value)Reduce 阶段:对所有具有相同 intermediate_key 的值进行聚合:
Reduce(intermediate_key, list of values) -> output values经典示例:单词计数
# Map: 每个词输出 (word, 1)
def map(document):
for word in document.split():
emit(word, 1)
# Reduce: 对每个词的所有 1 求和
def reduce(word, counts):
emit(word, sum(counts))MapReduce 的关键:Map 任务可以并行运行在数百台机器上(数据本地性:任务调度到数据所在机器,减少网络传输);Shuffle 阶段(框架自动完成)按 key 排序和分组;Reduce 任务可以并行处理不同的 key 分组。
Hadoop:Yahoo 工程师基于 Google 的论文实现的开源版本(HDFS + Hadoop MapReduce),2006 年开源,成为大数据时代的基础设施。
真正决定 MapReduce 能不能撑住生产负载的,往往不是 Map 与 Reduce 那两段用户代码,而是中间的 Shuffle:框架要把所有中间键值按 key 分区、排序、跨机传输。网络带宽、磁盘溢写、倾斜的热 key,都会在这里放大成小时级延迟。工程上常见的对策是combiner(在 Map 端先局部聚合)、自定义分区、以及把超级热 key 拆成盐化副本——这些细节不写进教科书公式,却决定了作业是否在凌晨前跑完。
故障模型也很务实:某个 Map 或 Reduce 挂了,调度器只重跑那一个任务;若某台机器持续变慢,则启动推测执行(speculative execution)在另一台机器上再开一份同样的任务,谁先完成用谁。这种"用冗余换尾延迟"的思路,后来被几乎所有批处理引擎继承。
存储:分布式文件系统与列存储
HDFS(Hadoop Distributed File System): - 文件被切分为固定大小的块(HDFS 默认 128MB,其原型 Google GFS 用 64MB),分布存储在不同节点。这个块远大于普通文件系统的 4–8KB——块越大,主节点要维护的元数据就越少,这是用"主节点少操心"换取可扩展性的典型取舍 - 每块保存 3 个副本(数据冗余),节点失效时自动恢复 - NameNode(主节点)管理文件系统元数据;DataNode(数据节点)存储实际数据块 - 适合大文件顺序读写,不适合小文件或随机访问
列存储(Columnar Storage):传统行存数据库把一行的所有字段存在一起;列存把同一列的所有值存在一起:
行存:[id=1, name="Alice", age=25, city="Beijing"], [id=2, name="Bob", age=30, city="Shanghai"], ...
列存:[id: 1, 2, 3...], [name: "Alice", "Bob"...], [age: 25, 30...], [city: "Beijing", "Shanghai"...]分析查询通常只访问少数列(SELECT SUM(sales) FROM orders WHERE year=2024),列存只读取需要的列,I/O 大幅减少。同一列的数据类型相同,压缩率更高(如整数列可以用 delta encoding)。
主要列存格式:ORC(Hive 使用)、Parquet(Spark、Flink 的标准格式)——已成为大数据生态的标准。Parquet 由 Twitter 与 Cloudera 在 2013 年联合开发,处理嵌套数据的列式编码思路直接借鉴自 Google 的 Dremel。
Spark:MapReduce 的继任者
Hadoop MapReduce 的主要问题:每次 Map-Reduce 阶段的中间结果都要写磁盘,多轮迭代(机器学习算法通常需要多次遍历数据)极慢。
Apache Spark(Berkeley AMPLab,2009-2012 年,Matei Zaharia 等开发): - 在内存中保存中间结果(RDD,Resilient Distributed Dataset) - 对迭代式计算(机器学习反复遍历同一份数据)大幅加速 - 统一了批处理(Spark Core)、SQL(Spark SQL)、机器学习(MLlib)、图计算(GraphX)、流处理(Structured Streaming)
Spark 的关键抽象是DAG(有向无环图)执行:把一系列变换操作表示为 DAG,惰性求值(Lazy Evaluation,只有触发 action 才真正执行),优化器可以在执行前整体优化 DAG。
顺带给"比 Hadoop 快 100 倍"祛魅。这是 Spark 官网在最理想条件下(数据完全装进内存)给出的宣传数字,官网同时注明落盘时"约快 10 倍"。
独立基准测试给出的数字温和得多——在 Word Count、k-means、PageRank 等任务上大约只快 2.5 到 5 倍;一旦内存装不下、中间结果开始溢写磁盘,这点优势会迅速缩水。真正的道理不是"内存天生比磁盘快 100 倍",而是"省掉了每一轮都要把中间结果写回磁盘的开销"——加速幅度取决于工作负载是否真的需要多轮迭代、数据是否真的装得进内存。
Spark 后来把重心从 RDD API 挪到 DataFrame / Spark SQL:Catalyst 优化器可以做谓词下推、列裁剪与整段 DAG 重写,Tungsten 则用堆外内存与向量化执行减少 JVM 对象开销。对用户而言,这意味着"手写分布式算法"越来越让位于"写声明式查询,让引擎决定怎么切分"。谁拥有物理计划的决定权,往往比谁会写 map/reduce 更影响吞吐。
流处理:处理实时数据
批处理(Batch Processing)适合离线分析,不适合需要实时响应的场景。流处理(Stream Processing)处理持续产生的数据流:
Apache Kafka(LinkedIn,2011 年开源): - 分布式消息队列/事件流平台 - 数据持久化到磁盘(不是传统消息队列的内存暂存),可以重放历史消息 - 每秒处理数百万事件,延迟毫秒级 - 解耦生产者和消费者,成为现代数据管道的核心组件
Apache Flink(2014 年开源): - 真正的有状态流处理,天然支持事件时间(Event Time,数据自带时间戳) - Exactly-once 语义(每条消息恰好处理一次,即使故障恢复) - Windowing(窗口操作):对时间窗口内的数据做聚合(最近 5 分钟的点击次数)
批流一体(Lambda Architecture / Kappa Architecture): - Lambda 架构:批处理层(准确但慢)+ 速度层(快但近似)+ 服务层(合并结果) - Kappa 架构(LinkedIn,Jay Kreps,2014):用统一的流处理系统处理历史和实时数据,简化架构
流处理里还有两个常被忽略、却决定正确性的细节。第一是事件时间与处理时间的错位:手机离线半小时后再上报的点击,按"到达服务器的钟"统计会落进错误窗口;Flink 一类系统用 watermark 声明"某时间戳之前的数据可以认为到齐了",迟到数据要么进侧输出,要么触发窗口修正。第二是背压(backpressure):下游算子跟不上时,不能无限在内存里排队,而要把压力沿管道向上游传导,否则内存被打爆后整条作业一起崩。Kafka 做缓冲、Flink 做有状态计算,两者的契约是否匹配,往往比算法本身更影响线上稳定性。
存储与计算分离:云时代的范式反转
MapReduce 立身的一条信条是"数据本地性":网络很贵,所以把计算调度到数据所在的机器,让数据尽量不动。HDFS 把存储和计算放在同一批机器上,正是为这条信条服务。
云时代把这条信条翻了过来。当对象存储(如 Amazon S3)变得极其廉价、数据中心内部的网络带宽又足够大时,"把数据搬到计算那边"重新变得划算,于是有了存储与计算分离(disaggregated storage and compute):数据静静躺在便宜的对象存储里,计算集群按需启动、用完即关,两者各自独立伸缩。
Google 的 Dremel(Melnik 等,2010)是最早把这套原则——存算分离、列式存储、就地查询——组合在一起的系统之一,它正是 BigQuery 的内核。Snowflake(Dageville 等,SIGMOD 2016)则把"多集群共享数据"架构产品化:同一份数据能被多个互不干扰的计算集群同时查询,查询跑着也能扩容,无需重新分布数据。
这是个实打实的取舍。存算分离放弃了数据本地性带来的极致性能,换来弹性与成本:你不必为了存 1PB 数据而常年养着一大片昂贵的计算节点。它也解释了为什么自建 Hadoop 集群会在 2010 年代末迅速失宠——在云上存储与计算解耦之后,多数公司没有理由再把两者绑死在自己机房里。
分离之后,性能工程的主战场挪到了缓存与局部性的再发明:计算节点把热点列块拉进本地 SSD 或内存,元数据服务缓存清单,查询规划器尽量让连续扫描命中同一批文件。表面上"数据随便躺在对象存储",实际上成功的云数仓都在用一层透明缓存,把最热的那一截重新变成近似本地的。弹性不是免费的,它把一次磁盘寻道,换成了一次可能跨可用区的 GET。
数据仓库与湖仓一体
数据仓库(Data Warehouse):为分析查询优化的专用数据库,数据经过 ETL(Extract-Transform-Load)清洗整合后存入,以列存格式组织。典型产品:Snowflake、Google BigQuery、Amazon Redshift、阿里云 MaxCompute。
数据湖(Data Lake):以原始格式存储所有数据(结构化、半结构化、非结构化),延迟 Schema(Schema-on-Read,读取时再解析结构)。
湖仓一体(Lakehouse,Databricks,2020 年提出):在数据湖(低成本存储)之上添加数据仓库的功能(事务、Schema 管理、查询优化)。代表格式:Delta Lake、Apache Iceberg、Apache Hudi——支持数据湖上的 ACID 事务。
湖仓之所以能落地,靠的不只是营销口号,而是几项可核对的表格式能力:时间旅行(按版本或时间戳读旧快照,方便审计与回滚)、Schema 演进(加列、扩类型而不必重写整表)、以及隐藏分区与统计信息让查询规划器跳过无关文件。代价是元数据本身变成热点——Iceberg 的清单文件、Delta 的事务日志,都要在对象存储上被频繁读取;表越大,"列出有哪些文件"就越不能靠朴素目录遍历。于是大数据栈再次回到老问题:不是字节存不下,而是关于字节的元数据能不能被可靠、低延迟地管住。
代价与争议
大数据的"退烧":2010 年代"大数据"话题在商业界极度火热,但许多企业投入大量资源建设 Hadoop 集群,最终发现大多数查询用普通数据库加合理索引就能解决。"我们真的需要大数据系统吗?"成为越来越多工程师的反思。简单的 PostgreSQL + 物化视图,有时胜过复杂的 Spark 集群。
这种反思有冷冰冰的市场数据背书。曾共同主导 Hadoop 商业化的 Cloudera 与 Hortonworks,在 2019 年 1 月以约 52 亿美元的换股交易合并,业界普遍视之为"Hadoop 的讣告";同年 8 月,第三家主要厂商 MapR 资金链断裂,资产被惠普企业(HPE)以传闻不足 5000 万美元的价格接手。
2023 年,BigQuery 创始工程师之一 Jordan Tigani 干脆写了一篇《大数据已死》("Big Data is Dead")。他在 BigQuery 任内观察到,绝大多数客户的总存储量不到 1TB,而且即便存了很多数据,分析查询真正触及的往往也只是最近一周或一个月的一小片——数据增长的速度,远远追不上单机硬件变大的速度。
数据治理与隐私:大数据系统的能力使大规模用户数据分析成为可能,也使隐私风险急剧上升。GDPR(欧盟)、CCPA(加州)等法规要求企业能够追踪、删除特定用户的数据——而 HDFS 不变式(数据只追加)使"删除"本身成为技术挑战。
AI 与大数据的融合:大语言模型的训练依赖 PB 级别的文本语料,推理需要实时处理用户请求——大数据系统(数据处理管道、分布式存储)是 AI 基础设施的核心组成部分,两个领域的边界日益模糊。
另一条较少被宣传的线,是小数据的回归。列存、向量化执行、更大内存与更快 SSD,让单机或小集群就能吞下许多曾经被视为"必须上 Hadoop"的工作负载。DuckDB、Polars 一类嵌入式分析引擎把"把文件读进进程内直接算"重新变成默认选项;对中位数团队而言,先把 Parquet 放进对象存储再在本地扫一遍,往往比养常驻集群更省钱。大数据系统并未消失,但它的合理使用边界被重新画了一次:真正需要它的,是持续写入、多租户并发、以及跨天级回放的管道;一次性分析和中等规模报表,越来越不该默认占用一台常年开机的集群。
跨域连接
- 数据库索引与查询优化:列存省下 I/O 的前提,是分析查询的选择性作用在列而不是行;只读需要的列,同列同型还能压得更紧。代价是对称的:一次点更新要触碰多个列块,所以列存对写密集负载反而更贵。推论是:同一份数据常要按访问模式存两份,而不是指望一种布局通吃。
- 边际分析:存算分离的判据,是两条边际成本能否分别定价。当对象存储足够便宜、机房内带宽足够宽,"把数据搬到计算那边"重新划算,存与算的最优容量随之解耦。自建集群失宠是相对价格变了,不是技术输了。同一条逻辑也解释了弹性计算:算力按需启停,闲置时的边际成本才真正归零。
- 计算社会科学:样本量再大也只压缩随机误差,不压缩偏差。平台数据的生成机制本身有选择性——谁上网、谁被记录、谁被删号,所以"全量数据"并不等于总体,这是大数据分析最常被跳过的一步。推论是:花钱扩大样本量,解决不了抽样框本身的缺口。
- 传染病建模与监测:公共卫生监测同样要在快与准之间选:实时上报给出快而会被上修的估计,回溯核对给出慢而稳定的数字。报告延迟修正与批流双轨是同一种架构,只是一个写在流行病学教材里。
- 人工智能治理与监控:删除权要求定点抹掉某个人的数据,而只追加的日志与多副本天生对抗定点删除。可行的答案是存密文、删密钥,这把一条法律义务翻译成了密钥管理问题——也意味着备份策略本身变成合规风险。同理,匿名化若不能抵抗跨表关联,删除承诺就只是形式上的。
参考文献
- Dean, J. & Ghemawat, S. MapReduce: Simplified Data Processing on Large Clusters. OSDI, 2004. (MapReduce 原论文)
- Ghemawat, S. et al. The Google File System. SOSP, 2003. (GFS 原论文,HDFS 的前身)
- Zaharia, M. et al. Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing. NSDI, 2012. (Spark 原论文)
- Melnik, S. et al. Dremel: Interactive Analysis of Web-Scale Datasets. Proc. VLDB Endowment, 2010. (存算分离 + 列式分析的奠基论文,BigQuery 的内核)
- Dageville, B. et al. The Snowflake Elastic Data Warehouse. SIGMOD, 2016. (多集群共享数据 / 存算分离架构的产品化)
- Laney, D. 3D Data Management: Controlling Data Volume, Velocity and Variety. META Group Research Note, 2001. ("3V" 框架的原始出处,原文并未使用"大数据"一词)
- Tigani, J. Big Data is Dead. MotherDuck Blog, 2023. (来自 BigQuery 创始工程师对"大数据退烧"的一手论述)
延伸阅读
- Kleppmann, M. Designing Data-Intensive Applications. O'Reilly, 2017. (大数据系统设计的最佳现代综合参考)