首页 / 资讯中心 / 文章详情

Storm、Spark Streaming、Kafka Streams、Flink:流处理框架选型深度对比与实战指南

Storm、Spark Streaming、Kafka Streams、Flink:流处理框架选型深度对比与实战指南 ★ FEATURED ARTICLE
流式处理这个领域我差不多是从 Storm 还是绝对主流的时候就开始跟了中间经历过 Kafka Streams 刚发布时大家观望的阶段也踩过 Spark Streaming 把批次调小结果资源反而更紧张的坑再到后来 Flink 逐渐成为很多团队的首选。这几年被问得最多的问题就是这四个框架到底该选哪个网上很多对比文章停留在Storm 低延迟、Spark 高吞吐这种一句话结论上实际做选型的时候根本不够用。我打算把自己这些年做过的几个实时项目经验摊开来讲从架构模型、状态管理、容错机制、运维成本几个维度把这四个框架拆一遍再给出不同场景下的选型建议。不管你是刚接触流处理的新手还是正在做技术选型的负责人应该都能从里面找到能直接用的判断依据。1. 四个框架的架构模型到底差在哪选流处理框架第一步不是看性能跑分而是搞清楚它的计算模型。模型决定了你能表达什么逻辑、状态怎么管、出问题怎么恢复。这四个框架在这件事上的分歧其实非常大很多人选型踩坑就是因为没先把这层想明白。1.1 Storm最纯粹的记录级流处理Storm 的核心抽象是 Spout 和 Bolt。Spout 负责从数据源拉数据往下游发Bolt 负责处理中间用 Stream Grouping 决定一条消息发给哪个 Bolt 实例。它的模型是记录级的——来一条消息处理一条没有批次或者窗口这种强制概念窗口得自己用 Bolt 去实现。这个模型的好处是延迟极低单条消息的处理延迟可以做到毫秒级甚至亚毫秒级因为没有任何缓冲等待。坏处也很明显状态管理基本靠自己。Storm 本身不提供托管状态你要做聚合、去重、会话窗口这类有状态计算得自己接 Redis、HBase 之类的存储或者用 TridentStorm 的高层抽象来做。Trident 提供了状态和 exactly-once 语义但代价是延迟上去了而且 API 用起来比较别扭。我早期做过一个实时告警系统用 Storm 做规则匹配每条日志进来匹配一遍规则命中就发告警。这种无状态或者轻状态的场景 Storm 非常合适逻辑简单、延迟低、部署也直接。但后来要加5 分钟内同一 IP 失败超过 10 次才告警这种需求就得自己维护计数窗口代码量一下就上来了。1.2 Spark Streaming微批次的世界观Spark Streaming 的模型是微批次micro-batch。它把流切成一个个小批次比如每 1 秒切一批然后每一批当成一个 RDD 去跑 Spark 作业。所以它本质上是把流当成一系列小批处理。这个模型最大的优势是复用 Spark 生态。你团队如果本来就在用 Spark 做批处理那 Spark Streaming 的学习成本几乎为零DataFrame、Dataset、SQL 这些都能直接用批流代码可以共享很多逻辑。状态管理上它提供updateStateByKey和mapWithState窗口操作也是原生支持。但微批次的代价是延迟。最小批次间隔虽然可以调到几百毫秒但实际生产里为了稳定一般不会低于 1 秒端到端延迟通常在秒级。而且批次是齐步走的——这一批必须全部处理完才能开始下一批如果某批数据倾斜严重整个流水线都会被拖住。我见过一个案例某批数据里有个超大 key处理时间从 200ms 飙到 8 秒后面所有批次全堵住了。1.3 Kafka Streams把流处理做成库Kafka Streams 的定位很特别它不是一个独立的计算集群而是一个 Java 库。你把它嵌到自己的应用里应用启动就是流处理任务启动应用挂了任务就停了。它直接读写 Kafka topic状态存在本地的 RocksDB 里通过 Kafka 的 changelog topic 做备份。这个设计的好处是运维极简。你不需要维护一个独立的流处理集群不需要操心资源调度就是一个普通 Java 应用用你熟悉的部署方式容器、systemd、whatever跑起来就行。对于已经在用 Kafka 做消息总线的团队Kafka Streams 几乎是零额外基础设施成本。它的模型是记录级 状态存储提供 KTable、GlobalKTable、窗口聚合这些抽象exactly-once 语义靠 Kafka 事务实现。缺点是它和 Kafka 绑得太死——数据源和 sink 基本都得是 Kafka topic想接别的数据源就得自己写 connector。另外它的扩展性受限于 Kafka 分区数一个任务实例最多处理对应分区数的数据分区数定死了并行度上限就定死了。1.4 Flink真正的流优先架构Flink 从设计之初就是流优先的批处理被当成有界流的特例。它的运行时是真正的流式引擎记录一条条在算子间流动没有微批次的齐步走问题。延迟可以做到毫秒级同时吞吐也不差。Flink 最核心的能力是托管状态 事件时间 精确一次这套组合拳。状态可以是 keyed state 或 operator state存在本地内存或 RocksDB通过 checkpoint 机制定期快照到分布式存储。事件时间语义配合 watermark 机制能正确处理乱序数据——这是很多实时场景的刚需比如按事件发生时间做窗口聚合数据晚到几分钟也能正确归位。它的容错靠 checkpoint 可重放的数据源。checkpoint 是异步的做 checkpoint 的时候不阻塞数据处理barrier 对齐阶段会有短暂阻塞但整体影响可控。恢复时从最近一次成功的 checkpoint 拉起状态回滚数据源从对应 offset 重放。下面这张表把四个框架的模型差异拉平了看维度StormSpark StreamingKafka StreamsFlink计算模型记录级微批次记录级记录级流优先延迟量级亚毫秒~毫秒秒级毫秒级毫秒级状态管理需外部存储/Trident原生内存本地RocksDBchangelog托管状态内存/RocksDB事件时间需自己实现支持支持原生支持watermark精确一次Trident支持支持支持事务支持checkpoint部署形态独立集群独立集群嵌入式库独立集群生态绑定通用Spark生态Kafka强绑定通用2. 状态管理与容错选型时最容易忽略的深水区很多人选型只看延迟和吞吐结果上线后才发现状态管理和容错才是真正决定项目成败的地方。有状态计算在实时场景里太常见了——去重、聚合、会话分析、CEP哪个都离不开状态。这四个框架在这块的差异比性能差异重要得多。2.1 状态存储的三种流派外部存储派StormStorm 本身不管状态你要么把状态放 Redis/HBase要么用 Trident 的状态抽象。放外部存储的问题是每次状态读写都是一次网络往返延迟上去了而且外部存储的可用性直接决定你的作业可用性。Trident 的状态虽然做了封装但底层还是外部存储本质没变。本地内存派Spark StreamingSpark Streaming 的状态默认放 executor 内存里靠 lineage血统做恢复。问题是 lineage 恢复在大状态场景下会非常慢——它要重算所有依赖的 RDD。所以生产里一般会配合 checkpoint 用但 checkpoint 又会截断 lineage两者配合起来调优挺麻烦。本地存储 远程备份派Kafka Streams / Flink这两个都是状态放本地RocksDB 或内存定期把状态变更同步到远程做备份。Kafka Streams 用 changelog topicFlink 用 checkpoint。恢复时从远程拉回本地。这个模式兼顾了性能和可靠性是目前主流做法。2.2 容错机制的实现差异容错这块核心问题是作业挂了之后状态怎么恢复数据会不会重复或丢失。Storm 的容错靠 ack 机制。每条消息有个 ack 树Spout 发出去的消息被完整处理后会收到 ack超时没收到就重发。这个机制保证 at-least-once但 exactly-once 得靠 Trident。ack 机制本身有开销高吞吐场景下 ack 树的管理会成为瓶颈。Spark Streaming 的容错靠 RDD lineage 和 checkpoint。每个批次是个 RDD挂了就重算。配合 WALWrite Ahead Log可以做到不丢数据但 WAL 会带来额外写开销。exactly-once 需要输出端支持幂等写或者事务写。Kafka Streams 的容错靠 Kafka 的消费 offset 和 changelog topic。状态变更实时写到 changelog恢复时从 changelog 重建本地状态。exactly-once 靠 Kafka 事务把消费 offset 和输出写到同一个事务里要么都成功要么都失败。Flink 的容错靠分布式快照基于 Chandy-Lamport 算法。JobManager 定期触发 checkpoint往数据流里注入 barrier算子收到 barrier 后把当前状态快照到状态后端比如 HDFS/S3完成后通知 JobManager。所有算子都完成就是一个成功的 checkpoint。恢复时从最近成功的 checkpoint 拉起数据源从对应 offset 重放。这个机制的好处是异步、不阻塞barrier 对齐阶段有短暂阻塞而且状态大小不影响恢复速度增量 checkpoint 只传变更部分。2.3 状态大小与扩展性状态能长多大直接决定你能做多复杂的计算。Storm 的状态在外部存储理论上无限大但每次读写都是网络操作状态越大延迟越高。Spark Streaming 的状态在内存受 executor 内存限制。状态大了要么加内存要么用mapWithState做超时清理。我见过一个做用户会话分析的作业状态里存了所有活跃会话跑几天后内存爆了最后加了会话超时清理才稳住。Kafka Streams 的状态在本地 RocksDB可以超过内存大小RocksDB 会 spill 到磁盘。但状态大小受限于单机磁盘而且分区数决定了状态分片数扩分区才能扩状态容量。Flink 的状态可以在内存或 RocksDBRocksDB 状态后端支持超大状态TB 级。状态按 key 分组可以 rescale调整并行度时重新分配 key group。这是四个里状态扩展性最好的。实操心得状态大小估算有个经验公式——状态大小 ≈ key 数量 × 单 key 状态大小 × 副本系数。做容量规划时按峰值 key 数的 1.5 倍算留够余量。RocksDB 状态后端下单并行度状态建议不超过 50GB超了就加并行度。3. 延迟与吞吐的实测权衡性能这块我不想只给结论想把背后的原因讲清楚这样你自己遇到具体场景能判断。3.1 延迟的构成拆解端到端延迟 数据源读取延迟 处理延迟 输出延迟。Storm 和 Flink 是记录级处理数据一到就处理处理延迟就是单条记录的处理时间通常亚毫秒到几毫秒。Kafka Streams 也是记录级但它的处理线程模型是单线程消费分区可以配多线程延迟和 Flink 接近。Spark Streaming 的延迟下限是批次间隔因为数据要攒够一批才处理所以最小延迟就是批次间隔一般 1 秒起。但实际延迟往往不是框架决定的而是你的处理逻辑和外部依赖决定的。比如你在处理里查一次外部数据库那延迟就是数据库查询延迟框架再快也没用。我见过有人抱怨 Flink 延迟高一查发现是每条记录都同步写一次 MySQL那延迟能不高吗。3.2 吞吐的瓶颈在哪吞吐瓶颈通常在三个地方序列化、状态访问、网络 shuffle。Storm 的吞吐瓶颈在 ack 机制和序列化。关掉 ack用TopologyMessageTimeoutSecs配合不 ack能提升不少但就失去 at-least-once 了。Spark Streaming 的吞吐优势在于批处理优化——一批数据一起处理序列化和网络开销被摊薄了。所以它的吞吐通常比记录级框架高代价是延迟。Kafka Streams 的吞吐受限于分区数和线程数。每个分区一个消费线程线程数上去了吞吐就上去了但线程间状态不共享跨分区聚合要靠 repartition。Flink 的吞吐靠流水线并行 异步 checkpoint 高效序列化。它的网络栈做了很多优化比如 credit-based 流控吞吐在记录级框架里是第一梯队。实测下来同样的硬件Flink 的吞吐通常是 Storm 的 3-5 倍。3.3 反压机制对比反压backpressure是流处理里特别重要但容易被忽略的机制。当下游处理不过来时上游要能感知并降速否则要么丢数据要么 OOM。Storm 的反压靠监控 Bolt 队列长度超过阈值就往 Spout 发反压信号。这个机制比较粗糙调优靠经验。Spark Streaming 的反压靠动态调整批次大小——如果上一批处理时间超过批次间隔就减小下一批的数据量。这个机制比较优雅但调整有滞后。Kafka Streams 的反压靠 Kafka 消费端的max.poll.records和fetch.max.bytes控制本质是控制每次拉多少。它没有主动反压机制靠消费速率自然调节。Flink 的反压是基于 credit 的流控下游告诉上游自己还有多少缓冲空间上游按 credit 发数据。这个机制最精细能精确控制每个通道的流量不会出现某个算子拖垮全局的情况。框架反压机制精细度调优难度Storm队列长度监控粗中Spark Streaming动态批次调整中低Kafka Streams消费端限流粗低Flinkcredit-based流控细中4. 不同场景下的选型决策讲了这么多原理落到实际选型上我一般按这几个维度来判断。4.1 按团队技术栈选如果团队已经在用 Kafka 做消息总线数据源和 sink 都是 Kafka topic那Kafka Streams 是首选。零额外基础设施运维成本最低开发效率也高。我做过一个实时指标计算的项目数据从 Kafka 来算完写回 Kafka用 Kafka Streams 两天就上线了换成 Flink 光搭集群和调 checkpoint 就得一周。如果团队是 Spark 重度用户批处理作业一大堆那Spark Streaming 最省心。批流代码可以共享团队不用学新东西。虽然延迟高一点但很多场景秒级延迟完全够用。如果团队没有历史包袱要做的是低延迟、有状态、复杂的实时计算那Flink 是当前最优解。它的状态管理、事件时间、容错机制都是为复杂实时场景设计的长期看维护成本反而低。Storm 现在新项目基本不推荐了除非你有非常特殊的低延迟需求而且逻辑简单到不需要状态管理。它的生态和维护活跃度都不如其他三个。4.2 按延迟要求选延迟要求是硬指标直接排除掉不满足的。亚毫秒~毫秒级Storm、Flink、Kafka Streams秒级可接受四个都行Spark Streaming 性价比最高分钟级其实用微批次甚至定时批处理更划算这里有个反直觉的点很多号称要毫秒级的场景实际业务根本不需要。比如实时风控你以为要毫秒级但实际业务容忍度是几百毫秒到秒级。为了追求极致延迟上 Storm结果状态管理复杂、运维成本高得不偿失。我建议先跟业务方确认清楚延迟的真实要求别自己加戏。4.3 按状态复杂度选状态复杂度是选型的隐藏关键。无状态或轻状态过滤、简单转换四个都行选团队最熟的中等状态窗口聚合、去重Kafka Streams、Flink、Spark Streaming大状态 复杂事件时间CEP、乱序处理Flink 明显占优我做过一个 CEP 场景要检测用户 10 分钟内连续 3 次异常操作这种模式还要求按事件时间处理乱序。用 Flink 的 CEP 库几十行代码搞定用其他框架得自己维护状态机代码量和出错概率都高一个量级。4.4 选型决策速查表场景特征推荐框架理由Kafka 生态 简单状态Kafka Streams零额外基础设施运维最简Spark 团队 秒级延迟Spark Streaming复用生态学习成本低低延迟 大状态 事件时间Flink状态管理和容错最强极简逻辑 极致低延迟Storm记录级处理延迟最低复杂 CEP 乱序处理FlinkCEP 库 watermark 原生支持快速原型 小数据量Kafka Streams开发部署最快5. 实操中的坑与排查技巧这部分是我这些年踩坑攒下来的文档里基本不会写但实际项目里经常遇到。5.1 状态相关的坑坑一状态无限增长。这是最常见的。窗口聚合忘了设 TTL或者去重场景的 key 基数太大状态越跑越大最后 OOM 或者 checkpoint 超时。解决办法是给状态设 TTLFlink 里用StateTtlConfigKafka Streams 里用窗口的retentionPeriod。我一般会加个监控状态大小超过阈值就告警。坑二checkpoint 越来越大。Flink 里如果状态一直增长checkpoint 会越来越大做一次 checkpoint 的时间越来越长最后超过间隔时间checkpoint 一直失败。解决办法是开增量 checkpointRocksDB 状态后端支持同时排查状态增长原因。坑三状态恢复慢。作业挂了重启从 checkpoint 恢复状态要很久。如果状态在 RocksDB恢复时要重建本地 RocksDB状态越大越慢。缓解办法是加大本地磁盘 IO或者用本地 SSD。Kafka Streams 恢复时要从 changelog topic 重放如果 changelog 积压多恢复会很慢可以调num.standby.replicas做热备。5.2 数据倾斜的坑数据倾斜在流处理里比批处理更麻烦因为它是持续的。某个 key 的数据量特别大对应的算子实例一直忙不过来其他实例闲着。排查方法看 Flink 的 Web UI每个算子的各并行度实例的 records 数差异大就是倾斜。或者看 Kafka 各分区的 lag某个分区 lag 一直高就是倾斜。解决办法预聚合在倾斜 key 前加一层本地聚合减少下游数据量加盐给倾斜 key 加随机前缀打散聚合后再去掉前缀两阶段聚合先局部聚合再全局聚合我做过一个实时统计某个热门商品的点击量是其他商品的几百倍导致那个 key 所在的算子实例 CPU 打满。后来用了加盐 两阶段聚合把负载摊平了。5.3 时间语义的坑事件时间和处理时间的混淆是新手最容易犯的错。用处理时间做窗口数据晚到就归到错误的窗口里了。用事件时间又得处理 watermark 和乱序。Flink 里 watermark 的设置很关键。设太紧晚到的数据被丢弃设太松窗口触发延迟高。我一般会先统计数据的乱序程度比如 P99 的延迟然后 watermark 延迟设成这个值的 1.5 倍。注意watermark 是单调递增的如果数据源有多个分区每个分区的 watermark 要取最小值作为全局 watermark否则会漏数据。5.4 常见问题速查表现象可能原因排查方向解决思路延迟突然升高反压/数据倾斜/GC看反压指标、各实例负载、GC日志定位瓶颈算子加资源或打散数据checkpoint 失败状态太大/超时/存储慢看 checkpoint 大小和耗时开增量checkpoint加超时时间状态恢复慢状态大/changelog积压看状态大小和恢复日志加本地IO做热备OOM状态无限增长/内存泄漏看状态大小和堆内存设TTL排查泄漏数据重复容错机制配置不当看输出端是否幂等开exactly-once输出端做幂等数据丢失反压丢数据/offset提交错误看offset提交和反压日志检查offset提交策略开WAL5.5 运维层面的经验监控要到位。流处理作业的监控比批处理重要得多因为它一直在跑。关键指标延迟、吞吐、反压、checkpoint 成功率、状态大小、GC 时间。我一般用 Prometheus Grafana 搭一套Flink 和 Kafka Streams 都原生支持 Prometheus 指标。资源规划留余量。流处理作业的资源使用是持续的不像批处理有波峰波谷。CPU 和内存都要留 30% 余量应对流量突增。磁盘 IO 也要关注RocksDB 状态后端对磁盘 IO 很敏感。升级要灰度。流处理作业升级不能直接停要么做 savepointFlink要么做滚动升级。Flink 的 savepoint 机制很成熟升级前打个 savepoint新版本从 savepoint 恢复状态不丢。Kafka Streams 升级相对简单因为状态在本地和 changelog重启就行但要注意兼容性。压测要真实。流处理的压测不能只测吞吐要测延迟分布、反压恢复、故障恢复。我一般会做几个场景正常流量、流量突增 3 倍、杀掉一个 TaskManager、模拟数据源抖动。这些场景过了上线才放心。6. 我个人的选型体会说了这么多最后分享几个我自己的判断习惯。别为了技术而技术。我见过太多团队为了用 Flink 而用 Flink结果业务场景其实用 Kafka Streams 就够了白白增加了运维复杂度。选型的第一原则是匹配业务需求不是追新。运维成本要算进去。一个框架的开发效率高但运维成本高长期看未必划算。Kafka Streams 开发效率可能不如 Flink但它运维成本极低对于小团队来说这个优势很大。我一般会算一笔账开发省的时间 vs 运维多花的时间哪个多选哪个。状态管理是分水岭。如果你的场景有状态而且状态不小那 Flink 基本是唯一选择。如果无状态或轻状态那选择就多了按团队熟悉度选就行。先跑通再优化。别一上来就纠结性能调优先用最简单的配置把逻辑跑通再根据监控数据优化。我见过有人花两周调 checkpoint 参数结果业务逻辑还没跑通。社区活跃度要看。Storm 现在社区活跃度明显不如其他三个遇到问题搜不到答案这是个隐性成本。Flink 和 Kafka Streams 社区都很活跃Spark 更不用说。选型时看看 GitHub 的 commit 频率和 issue 响应速度能反映很多问题。流处理这个领域还在快速演进Flink 和 Kafka Streams 的边界在模糊Flink 也在做 Kafka 集成Kafka Streams 也在扩展能力未来可能会有新的选择。但不管怎么变理解清楚计算模型、状态管理、容错机制这三件事你就能自己判断该选什么。
阅读完成 · 觉得有帮助?
咨询建站