订阅更新

Spark Streaming作业丢数据了——Exactly-Once怎么保证?

Spark手册 第 11 题 约 4 分钟读完

本文目录
  1. 先说结论
  2. 为什么会丢数据?
  3. 如何保证Exactly-Once?
  4. 实战经验
  5. 总结

先说结论

三种处理语义:

语义含义数据风险
At-Most-Once最多处理一次,可能丢可能丢数据
At-Least-Once至少处理一次,可能重复可能重复数据
Exactly-Once恰好处理一次,不丢不重无风险(理想状态)

Exactly-Once实际上分两层:引擎层面的Exactly-Once(Spark内部保证不丢不重) + 端到端的Exactly-Once(从数据源到数据汇,需要Source + Sink配合)。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

实时作业丢数据是生产环境最严重的问题之一。尤其是涉及钱的场景——广告花费、订单金额——丢一条都不行。这道题考察的是你对实时处理语义的理解深度。今天把三种语义、丢数据的原因、以及如何保证Exactly-Once彻底讲清楚。

为什么会丢数据?

常见的丢数据场景:

① Kafka Offset提交太早: 还没处理完就把Offset提交了,作业崩溃后从新Offset开始,中间的数据就丢了。

② Executor崩溃: 正在处理的数据还没写出去,Executor就挂了。如果没有Checkpoint,这批数据就丢了。

③ 写入Sink失败: 处理完了但写入目标存储时失败(网络闪断、目标库挂了),如果Offset已经提交,就丢了。

如何保证Exactly-Once?

方案一:Structured Streaming + Checkpoint(推荐)

Structured Streaming的Checkpoint机制可以保证引擎层面的Exactly-Once:

val query = spark.readStream
  .format("kafka")
  .option("kafka.bootstrap.servers", "kafka:9092")
  .option("subscribe", "user_events")
  .load()
  .writeStream
  .format("parquet")
  .option("checkpointLocation", "hdfs:///checkpoint/user_events")
  .option("path", "hdfs:///data/user_events")
  .start()
 
// Checkpoint记录了:
// 1. 已经消费到的Kafka Offset
// 2. 已经写出的数据批次
// 崩溃重启后从Checkpoint恢复,不丢不重

方案二:幂等写入 + At-Least-Once

如果Sink不支持事务(比如写文件系统),可以用At-Least-Once + 下游去重来实现“效果上的Exactly-Once”。比如写入时带唯一键,重复写入也不会产生重复数据。

实战经验

我在元宇宙社交App做实时数据平台时,用的是Flink而不是Spark Streaming,因为Flink的Exactly-Once支持更成熟。但在出海硬件公司,有些场景用的是Structured Streaming(因为Databricks生态),Checkpoint + 幂等写入Delta Lake,效果也很好。

关键经验:Checkpoint目录一定要放在可靠存储上(HDFS/S3),不能放在本地磁盘。我见过有人把Checkpoint放在/tmp目录,机器重启后Checkpoint丢了,作业重新从Kafka最早的Offset开始消费,导致大量重复数据。

总结

Exactly-Once分引擎层和端到端两层。Structured Streaming + Checkpoint保证引擎层Exactly-Once。端到端还需要Sink支持事务或幂等写入。Checkpoint必须放可靠存储。面试时能区分三种语义、说清楚丢数据的原因和解决方案,就很有深度。

下一篇聊一个很实用的选型问题:Structured Streaming和Spark Streaming到底用哪个?新项目该怎么选?

你们生产环境的实时作业丢过数据吗?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器