📊 第5章 大数据的收集
📄 一句话概括

批是攒够再算,流是来一条算一条。差别不在总量,在延迟。 演示里首条结果:批 1.20 秒、流 0.002 秒,差 600 倍。 但批的总算力只要 2 秒、流要 10 秒。低延迟是拿算力换的。

📝 本章和前面几章的关系

第 4 章讲模型。这一章讲数据怎么进来——这是整条管道的第一段。 教材 5.1 节:批数据收集和泛数据收集,也就是批与流两种收法。 这一篇只讲概念差别,具体怎么取在 5.2、5.3;流怎么落地在 5.4。

一、两种收法,两种节奏

批处理是攒够一批再处理。比如每小时跑一次,把这一小时的数据一起算。 流处理是来一条处理一条,几乎不停。 两者干的总活是一样的。演示里同样一万条数据,批分 10 批算,流分一万次算。

差别只有一个字:多久能看到结果。 演示里批的首条结果要 1.20 秒——得等满 1000 条(1 秒)再算(0.2 秒)。流只要 0.002 秒。 所以「流更快」这个说法不准确。准确的说法是:流的延迟低。

二、低延迟的代价是算力

看第三段那张表:批总共只花 2.00 秒,流要 10.00 秒。 因为批一次算 1000 条,每次调用的固定开销(连数据库、开文件、初始化)被摊薄了;流每条都要付一次。 演示里设的是批每批 0.2 秒、流每条 0.001 秒,看起来流更便宜,但乘上条数就反过来了: 10 批 × 0.2 = 2 秒,10000 条 × 0.001 = 10 秒。

所以这是一笔交易:批省算力但延迟高,流延迟低但算力贵。 怎么选?看业务能不能等。日报表能等到明天早上,批就够了;风控要在刷卡那一瞬间判断,只能上流。

三、流的四个麻烦

批处理有个巨大的优势:可以重来。 算错了,把那一批重新跑一遍就行。 流不行。数据已经流走了,你没法「回到三分钟前」。所以流系统必须自己解决四件事。 第一,迟到数据。一条数据本该 10:00 到,结果 10:05 才来。批下次跑批能算进去; 流必须开个窗口多等一会儿。

第二,重复数据。网络抖动导致重发。批整批覆盖就行;流得靠去重键。 第三,顺序错乱。A 先发生 B 后发生,但 B 先到。批无所谓; 流必须按「事件时间」而不是「到达时间」处理。 第四,故障恢复。程序崩了重启,从哪儿接着读?批重跑那一批;流要有检查点。

这四条就是流处理全部的复杂度来源。任何流框架,讲来讲去都在解这四件事。

四、两种语义:至少一次、恰好一次

接着第四点。程序崩了重启,那条正在处理的数据算不算处理过? 如果算「没处理过」,重发一遍——可能重复。这叫至少一次。 如果算「处理过」,跳过——可能丢。这叫至多一次。

想既不重也不丢,需要恰好一次。 做法是:把「处理结果」和「读到哪儿了」放在同一个事务里提交,两者要么都成功要么都失败。 听起来简单,实现起来要在存储层做文章。所以很多流系统默认只保证「至少一次」,然后要求下游自己去重。 这一条在选型时一定要问清:这套系统承诺的是哪种语义?不问清楚,重复数据迟早找上门。

五、动手 Demo:同样一万条,批和流的首条结果差多少

设一个场景:数据每毫秒来一条,共一万条,持续十秒。 批处理攒一千条算一次,流处理来一条算一条。 算首条结果延迟、总耗时,再看流要额外对付哪四件事。

N, gap, BATCH = 10000, 0.001, 1000
t_batch, t_row = 0.2, 0.001

# 首条结果什么时候出
first_batch  = BATCH * gap + t_batch     # 等满一批 + 算一批
first_stream = gap + t_row               # 来一条 + 算一条

# 总共要花多少算力
total_batch  = (N // BATCH) * t_batch
total_stream = N * t_row

print('首条 %.2f 秒 vs %.3f 秒' % (first_batch, first_stream))
print('总算力 %.2f 秒 vs %.2f 秒' % (total_batch, total_stream))

实跑输出:

--------------------------------------------------------------------
批与流:首条结果差了六百倍
--------------------------------------------------------------------

  ① 先设一个场景
  --------------------------------------------------------------------
  数据每 1 毫秒来一条,一共 10000 条,也就是持续 10 秒。
  批处理攒 1000 条算一次,算一次要 0.2 秒
  流处理来一条算一条,算一条要 0.001 秒

  ② 第一条结果什么时候出来
  --------------------------------------------------------------------
  批:等满 1000 条要 1.0 秒,再算 0.2 秒 -> 首条结果 1.20 秒
  流:来一条就处理 -> 首条结果 0.002 秒
  差了 600 倍
  这是批和流最本质的差别:批的延迟由「攒多久」决定,流的延迟由「算多快」决定。
  要秒级响应就必须用流;能等到明天早上看报表,批就够了。

  ③ 总耗时和总吞吐
  --------------------------------------------------------------------

| 方式 | 批次或条数 | 处理总耗时 | 最后一条完成于 |
|---|---:|---:|---:|
| 批 | 10 批 | 2.00 秒 | 10.20 秒 |
| 流 | 10000 条 | 10.00 秒 | 10.00 秒 |

  这次反过来了:批处理总共只花 2.00 秒,流式要 10.00 秒。
  因为批一次算 1000 条,每次调用的固定开销被摊薄了;流每条都要付一次开销。
  所以这是一笔交易,不是谁更先进:批省算力但延迟高,流延迟低但算力贵。选哪个看你的业务能不能等。

  ④ 流的麻烦在哪
  --------------------------------------------------------------------

| 问题 | 批怎么对付 | 流怎么对付 |
|---|---|---|
| 迟到数据 | 下次跑批再算进去 | 要开窗口等一会儿 |
| 重复数据 | 整批重跑覆盖 | 靠去重键 |
| 顺序错乱 | 无所谓,批内排序 | 按事件时间而不是到达时间 |
| 故障恢复 | 重跑那一批 | 要从检查点接着读 |

  这四行就是流处理的全部难点。批处理可以「重来一遍」,流处理不行——数据已经流走了。
  所以流系统必须有「记到哪儿了」的机制(检查点),以及「怎么算一次」的语义(恰好一次、至少一次)。
  第 5.4 篇还会碰这个。

这一段的数字全是算出来的,不是等出来的——模拟一个场景比真等十秒快得多,而且参数可以随便改。 第二段那个「首条差 600 倍」是最该记的数。 它不是巧合:批的延迟约等于「批大小 × 到达间隔」,流约等于「单条处理时间」。 第三段那张表反过来说明流不便宜。 很多人以为流是「更高级的方案」,其实它是「更贵的方案」,只在延迟要求高的时候才值。 最后那四行表看着像凑数的,但它是选流框架时的检查清单:迟到、重复、乱序、恢复,一个都不能少。

30 秒实验

  1. 把 BATCH 从 1000 改成 100,看首条延迟降到多少、总算力涨到多少。
  2. 把 t_row 从 0.001 改成 0.0001(流式处理很快),看总算力还比不比批贵。
  3. 想一想:如果数据是每 10 秒来一条,流速率的场景还成立吗? (不成立,这时流每条都要付一次开销,亏得很。)

常见误区

  1. 「流处理是批处理的升级版。」不是升级,是另一个取舍。批省算力、流低延迟,各有各的适用场景。
  2. 「上了 Kafka 就是流处理。」Kafka 是队列,不是流计算。要不要真流式处理,看你的延迟要求。
  3. 「流处理不会丢数据。」要看它承诺什么语义。很多系统默认「至少一次」,意味着可能重复。
自测三问(点开对答案) 1. 同样一批数据,批和流的首条结果延迟差在哪? —— 批要等满一整批才开算,延迟 ≈ 批大小 × 到达间隔;流来一条算一条,延迟 ≈ 单条处理时间。 2. 为什么批处理的总算力通常比流便宜? —— 因为固定开销被摊薄了。批一次处理一千条,只付一次连接和初始化的钱;流每条都要付一次。 3. 「恰好一次」是怎么做到的? —— 把「处理结果」和「消费进度」放在同一个事务里提交,两者一起成功或一起失败。 这样重放时不会重复生效。

下一篇讲具体怎么取数:文件、JSON、数据库三种方式各要多久。

相关笔记