底层拆解:离线计算本质上是“时间换确定性”
离线计算的本质并不是“慢”。而是通过批量处理和持久化存储,换来对数据的绝对掌控。类比:就像做饭,实时计算是炒菜,锅热就下;离线计算是炖汤,所有食材先放在锅里,咕嘟咕嘟几小时,最后烂熟。代码架构上,核心是数据本地性(data locality)——计算移动到数据所在位置,而不是反过来。MapReduce就是因为这个设计,才让TB级数据能在普通机器集群上跑完。HDFS上每个block被分割成128MB,计算任务被调度到持有该block的节点,减少网络IO。原理可以用这张图说清楚。
数据论证:离线计算在吞吐量上吊打实时,不服来看压测
我们自己搞过一组对比。相同集群(10台 8核32G)跑同一个统计任务:100亿条日志,约2TB。Flink流处理(窗口1小时)实际吞吐:120万条/秒。而Spark SQL离线批处理:850万条/秒。延迟呢?Flink秒级,Spark需要22分钟。但关键是资源消耗:Flink常驻任务占满内存,Spark按需使用,任务结束资源即释放。算下来,同等数据量,离线任务资源费用是实时的五分之一。这个数字,让技术总监沉默了很久。 还测过一个更小的:100GB数据做GroupBy,Hive on MR用时41分钟,Spark SQL只用了6.2分钟,快6.6倍。可我们为什么还常骂离线慢?因为它在等待和调度上浪费太多时间。比如YARN队列资源不足,任务排队等半小时。这又是另一个坑了。 但千万别以为离线就无敌。离线也有死角,而且致命。实践指南:离线计算落地中的三个致命陷阱
第一个坑:小文件问题。大量小文件会压垮NameNode和TaskScheduler。我见过一个任务,500万个小文件,每个1KB,直接导致NameNode内存溢出。解决方案:要么在写入时控制文件数(比如通过coalesce减小分区),要么在读取时使用CombineTextInputFormat做合并。但最根本的是架构层面的约束——上游定期做小文件合并。 第二个坑:数据倾斜。这个太经典了。一个reduce节点处理了99%的数据,其他节点在空闲。我们曾经有个join任务,因为用户表里一个超级用户占了大头,导致任务跑了7小时。解决办法就是加盐(salting)——把热点key打散成多个随机key,先做局部聚合,再二次聚合。注意,加盐粒度必须合适,随机数范围不能太小,否则倾斜依旧;也不能太大,否则额外开销量会很夸张。我们一般取200以内的随机数,再根据数据分布调整。
作者|大讲堂
排版|大讲堂
审核|乐乐
大讲堂