第三章:Spark Streaming架构与原理
好,咱们今天聊聊Spark Streaming的核心架构。说实话,我刚接触流计算那会儿,总觉得流处理就该是“来一条处理一条”,像Storm那样。但Spark Streaming偏偏不走寻常路——它搞了个微批次。我当时心里直犯嘀咕:这能行吗?
后来真在项目里用上了,才发现这设计其实挺妙的。你想想看,批处理引擎那么成熟,直接拿来处理流数据,稳定性自然有保障。我有个朋友在金融公司,他们就用Spark Streaming做实时风控,跑了一年多没出过大问题。
3.1 DStream:离散化流的核心概念
DStream,全称是Discretized Stream,翻译过来就是“离散化流”。说白了,就是把连续不断的数据流,按时间切成一小段一小段的RDD。
我习惯这么理解:DStream = RDD序列。每个时间窗口里,数据被封装成一个RDD。你操作DStream,本质上就是在操作一个个RDD。
核心要点:
- DStream是逻辑概念,RDD是物理存储
- 每个批次间隔生成一个RDD
- DStream的转换操作最终映射到RDD操作
举个例子,你写dstream.map(func),Spark并不会立即执行。它只是把这个操作记录下来,等每个批次生成RDD后,再对那个RDD执行map。嗯,这里要注意:DStream的操作也是惰性的,跟RDD一样。
// 创建一个DStream
val lines = ssc.socketTextStream("localhost", 9999)
// 对DStream进行转换
val words = lines.flatMap(_.split(" "))
val pairs = words.map(word => (word, 1))
val wordCounts = pairs.reduceByKey(_ + _)
// 输出结果
wordCounts.print()
这段代码看着简单,但背后发生的事情可不少。每个批次间隔(比如1秒),Spark都会生成一个新的RDD,然后执行flatMap、map、reduceByKey这一串操作。我在项目中调试过这种代码,发现每个批次的执行计划都是独立的,互不干扰。
3.2 Receiver与数据源
Receiver,直译就是“接收器”。它的工作很简单:从外部数据源拉数据,存到Spark的Executor内存里。
我记得第一次用Receiver时踩过一个坑。当时用Kafka作为数据源,默认的Receiver方式会把数据存到Executor的内存中。结果数据量一大,内存直接爆了。后来我才知道,Receiver方式有数据丢失的风险,除非你启用WAL(Write Ahead Log)。
避坑指南:
我曾经在生产环境中遇到过Receiver导致的数据丢失问题。当时Kafka的offset提交了,但数据还没处理完,Executor挂了。从那以后,我强烈建议使用Direct方式(无Receiver)连接Kafka。
Spark Streaming支持的数据源分两类:
| 数据源类型 | 示例 | 说明 |
|---|---|---|
| 基础数据源 | Socket、文件系统 | 直接通过StreamingContext创建 |
| 高级数据源 | Kafka、Flume、Kinesis | 需要引入额外的依赖包 |
我个人习惯用Kafka作为主要数据源。为什么?因为它吞吐量高、持久化好、还能回溯消费。你想想看,如果数据源挂了,Kafka还能帮你缓存数据,等恢复后再继续消费,多省心。
小技巧:
用Direct方式连接Kafka时,记得把offset存到外部存储(比如ZooKeeper或数据库)。这样即使任务重启,也能从上次的位置继续消费,不会重复也不会丢失。
3.3 微批次处理模型
微批次,这是Spark Streaming最核心的设计思想。它把流数据切成一个个小批次,每个批次用Spark引擎处理。
我刚开始觉得这设计有点“笨”——明明数据是实时来的,你非要攒一批再处理,延迟能低吗?后来做了性能测试才发现,批次大小调到100ms左右,延迟完全能接受。而且批处理的好处是:一次处理一批数据,吞吐量比逐条处理高得多。
微批次的工作流程是这样的:
- 数据接收:Receiver或Direct方式拉取数据
- 数据分片:按批次间隔切分成RDD
- 任务调度:Spark引擎调度执行
- 结果输出:写入外部存储或打印
这里有个关键参数:batchDuration(批次间隔)。我建议你根据业务需求来调:
- 对延迟敏感(如实时风控):设1-2秒
- 对吞吐量要求高(如日志聚合):设5-10秒
- 一般场景:设2-5秒
性能调优经验:
我曾经优化过一个实时ETL任务,批次间隔从5秒调到2秒后,延迟降了60%,但吞吐量也降了30%。后来发现是资源没跟上——批次越小,调度开销越大。最终我加了两个Executor,才平衡了延迟和吞吐量。
微批次模型还有个好处:容错性极强。因为每个批次都是独立的RDD,如果某个批次处理失败,只需要重新计算那个RDD就行。不像纯流处理,一旦出错可能要回滚整个状态。
嗯,说到这里,我想起一个经典问题:微批次和纯流处理到底选哪个?我的建议是:
- 如果你需要毫秒级延迟,选Flink或Storm
- 如果你能接受秒级延迟,但追求高吞吐和稳定性,选Spark Streaming
- 如果你团队里Spark技术栈已经很成熟,那更没必要换
说白了,技术选型没有绝对的好坏,只有合不合适。我在好几个项目里都用Spark Streaming做实时数仓,配合Kafka和HBase,跑得稳稳当当的。
最后总结一下:DStream是逻辑抽象,Receiver是数据入口,微批次是处理模型。这三者配合起来,构成了Spark Streaming的完整架构。你只要理解了“流数据变批次RDD”这个核心思想,后面的API和调优就都好办了。