资讯动态

实时流处理选型:Kafka+Spark Streaming与ETL+Kudu的技术差异

发布时间:2026/10/20 15:45:46 来源:尧图企业网站定制
1. 两种流处理路线的定位差异一个像中转站一个像仓储先说一个我前段时间的真实经历。当时某公司要上一套实时大屏加实时明细查询开发群里直接吵起来了。A同学说用Kafka接Spark Streaming把计算结果写Redis和ES链路短、见效快B同学说既然有实时明细查询这种事干脆Kafka直接进ETL管道落到Kudu里后面让Impala跑SQL想怎么查怎么查。两边谁也说服不了谁最后架构评审的时候我发现一个核心问题大家争论的根本不在一个维度上。KafkaSpark Streaming和ETLKudu这两套东西看着都是大数据常用实时流处理方式名字也经常出现在同一个项目的技术选型文档里但它们的侧重点完全不一样。前者解决的是数据从产生到算完后者解决的是数据从产生到能用。一个偏重计算流转一个偏重存储服务。拆开看整个实时链路的通用骨架其实就三层数据接入层负责把业务日志、埋点、binlog这类源源不断产生的数据搬到消息中间件里计算层负责把原始数据按业务规则加工成指标、宽表或者清洗后的明细服务层负责把计算结果暴露给报表、大屏、算法、下游应用去用。KafkaSpark Streaming这套组合的典型结构是Kafka当中转站Spark Streaming不断从中拉数据算完之后推到业务能用的存储里可能是Redis、ES、MySQL、ClickHouse也可能是HBase。整个过程强调算数据在Kafka和计算引擎之间流动最终落到哪取决于下游要什么。ETLKudu这套组合的典型结构则是Kafka照样当入口但计算引擎通常是Spark Streaming或Structured Streaming干完清洗和转换之后把数据按主键合并进Kudu表。Kudu不只是一个落地的地方它本身是列式存储具备主键更新能力还能配合Impala或Spark SQL提供近实时的分析查询。数据在这里不是算完就走而是落下来随时能用。我打个比方吧。KafkaSpark Streaming像是航空货运中转站航班到了立刻开箱分拣、装车转运效率很高但货物在中转站只是过个手并不负责存放。ETLKudu像带冷藏仓库的配送中心货物到了先入库、上架、贴标之后不管是门店要补货还是顾客要查单随时都能翻出来。这个定位差异不是谁好谁坏而是决定了后面所有的技术取舍。如果只做实时指标计算你不一定需要Kudu如果还要做实时数仓、要查明细、要改数据那光有Spark Streaming是不够的。这也是为什么总有人问实时流处理到底用哪种方式因为答案本来就不在单纯的技术栈里而在业务场景里。我先把两条路线各自的原理和落地细节讲透最后再给出一套可以照着做的选型判断逻辑。这样无论你是刚开始接触实时计算还是已经在生产环境里被这两种方案折腾过都能有所参考。2. KafkaSpark Streaming微批架构为什么能在生产环境站稳脚跟2.1 所谓实时其实是攒够一批再算很多刚接触实时计算的人有个误解觉得Spark Streaming和Flink一样是一条数据进来算一条。不是的。Spark Streaming的核心思想叫微批Micro-Batch把持续不断的数据流按时间窗口切成一个个小块每块做成一个RDD然后交给Spark的批处理引擎去跑。举个例子你设置batch interval为5秒那么Spark Streaming每5秒把Kafka里新到的数据打包一次触发一次Spark任务。这5秒就是延迟的下限数据产生到可见至少要等一个批次攒完。生产环境里我见过把间隔压到500毫秒的但少见因为Spark的任务调度本身有开销批间隔太小光调度就吃掉大量资源处理能力反而下降。所以Spark Streaming的实时性通常用准实时来描述不是毫秒级而是秒级。但微批有个天然的好处它借用了成熟稳定的Spark批处理框架。Spark的容错、内存计算、资源调度、SQL支持全部可以平移过来。相比纯流引擎这套东西更容易上手也更容易在已有的Spark离线数仓团队里推广。现在很多公司的实时链路其实就是离线团队顺手把Spark Streaming也接了这也是它至今仍有大量生产环境用户的原因。2.2 Direct方式与背压机制Kafka和Spark到底怎么配合Kafka和Spark Streaming对接早期版本流行Receiver方式Spark里起一个Receiver常驻线程不断从Kafka拉数据先存到executor的内存里再由Spark Streaming去处理。这种方式有个毛病——Receiver写入WAL预写日志才保险否则executor挂掉会丢数据而且Receiver接收入口容易成为瓶颈。后来生产环境主流都切换到Direct方式逻辑上很简单不再有ReceiverSpark Streaming的每个batch直接以Kafka分区为单位创建RDD分区主动从Kafka拉取对应offset范围的数据。这样有几个直接好处数据不经过executor内存中转天然少一层故障风险Spark的并行度能直接对齐Kafka分区数offset可以通过checkpoint持久化容错时能恢复到上次的位置。Direct方式下Kafka在这条链路里的角色就非常清晰了——削峰填谷的缓冲池。业务高峰时下游一时算不过来Kafka先扛住积压等服务恢复后再追赶进度不会直接压垮后端。而且Kafka可以保留一段时间的消息下游可以随时从任意offset重新消费做数据回溯和修复非常顺手。生产环境配置里有两点值得特别注意。第一是务必开启背压backpressure机制让Spark Streaming根据实际处理能力动态调节消费速率而不是一股脑把上游数据全拉进来。第二是合理设置spark.streaming.kafka.maxRatePerPartition这个是单分区每秒最多消费的消息数上限防止个别批次拉取过多数据导致处理超时。拿一个真实项目的参数举例spark-submit \ --master yarn \ --deploy-mode cluster \ --executor-memory 8G \ --executor-cores 4 \ --num-executors 20 \ --conf spark.streaming.backpressure.enabledtrue \ --conf spark.streaming.kafka.maxRatePerPartition800 \ --conf spark.streaming.backpressure.rateEstimatorpid \ --class com.example.RealtimeIndicatorJob \ realtime-job.jar这个配置里maxRatePerPartition800不是拍脑袋定的。当时线上Kafka的topic是24个分区峰值每秒进入的消息总量约1.2万条平均每条日志在1KB以内。1.2万除以24每个分区每秒约500条再留出50%左右的余量应对突刺于是取800。如果设得太小消费能力受限积压会越来越严重设得太大背压机制又失去了保护意义。通常的做法是拿峰值估算值乘以1.2到1.5先跑几天监控再看积压和延迟曲线微调。2.3 恰好一次语义不是简单靠一个配置就能实现需要提醒的是生产级实时链路绕不开数据会不会重复、会不会丢这个终极问题。很多人在PPT里写exactly once以为是某个参数打开就自动生效其实它是一条链上多个环节配合出来的结果。在KafkaSpark Streaming这套链路里至少要同时满足三个条件。第一源端offset要可管理也就是上面提到的Direct方式自己维护Kafka offset。第二计算状态要可恢复借助Spark Streaming的checkpoint机制把当前处理进度和状态保存到HDFS或S3。第三也是容易被忽略的——输出端要幂等。比如写Redis可以设置带时间戳的key写HBase可以用业务唯一键做主键写MySQL可以在写入时做先查再插或更新。我在实际项目里见过不止一次前面两台机器都做好了输出端是普通的消息队列或API一旦重试就直接重复推送下游数据翻倍。最后没办法只能在下游加去重表兜底。所以我在设计这类链路时都会先问一句最下游能不能容忍重复如果不能那输出端必须选支持幂等写入的系统否则不要宣称自己做到了exactly once。Kafka加Spark Streaming这套方案适合结果以追加型指标为主、不需要频繁修改已有数据、下推算完就能用的场景。告警计算、流量统计、行为分析、简单清洗转发这些用起来非常顺手。但要涉及明细数据的更新、删除、回查它就不是最佳答案了这时候该轮到Kudu登场。3. ETLKudu从算完丢掉到落得下、查得快的实时数仓方案3.1 Kudu到底解决什么问题先说Kudu是个什么角色。大数据存储里过去有个尴尬的断层HDFS能存海量数据跑批量分析很稳但随机读写和按主键更新基本做不了HBase支持随机读写但分析场景不擅长Scan大范围数据时性能差。你要建一张实时订单表既要不断按订单号更新状态又要支持按时间范围跑聚合查询传统方案得同时维护HDFS上的数仓和HBase里的实时表数据双写不说两边的口径还容易对不上。Kudu就是想把这个断层抹平。它是一种分布式列式存储引擎底层用的不是HDFS而是自己的Tablet存储格式按列存储天然适合分析查询同时又支持行级的主键更新、插入、删除随机读的性能比HDFS加Parquet好得多批量分析能力又比HBase强。放在实时的语境里理解它就是一个能边写边查、按主键更新的分析型存储。所以要理解标题里的ETLKudu关键不是纠结ETL这个词是不是离线用的而是要看清楚这个技术栈里Kafka负责攒数据Spark系引擎负责清洗和转换Kudu负责把结果以可更新、可查询的形式落下来最后再用Impala或Spark SQL直面报表和分析应用。整个链路本质上是不可断的实时ETL管道跟传统离线ETL最大的区别是它持续在跑、持续在更新数据而且更新后的结果几乎立即可查。3.2 流式写入Kudu的工程化细节从Kafka到Kudu中间的计算引擎既可以用老牌的Spark Streaming也可以直接用Structured Streaming。两者的核心逻辑差别不大——消费Kafka的topic做必要的数据解析、格式规范化、字段过滤、维表关联然后按主键写KuduKudu内部会自动执行upsert主键命中就更新不命中就插入。Kudu有一类坑是HDFS上没有的。比如每个Tablet的分区数在建表时就固定了之后不能改所以建表前得认真估算数据量主键也是建表后不可变业务主键一旦选错后面很难收拾写入侧如果不做批量提交一条一条往里塞吞吐惨不忍睹。我实际用下来的参数是Spark任务里用KuduContext配合mode(append)但这种方式无法更新已有主键要走upsert必须用mode(upsert)这种语义把一组DataFrame按主键入库。生产环境我曾经在订单回流场景里这么建表CREATE TABLE realtime_order ( order_id STRING PRIMARY KEY, user_id STRING, status STRING, pay_status STRING, total_amount DECIMAL(12, 2), province_id INT, update_time BIGINT, create_time BIGINT ) PARTITION BY HASH (order_id) PARTITIONS 16;这个表每天大概新增5000万条订单状态会多次变更下游要按省份、支付状态、金额区间做实时聚合。这里必须用哈希分区把order_id打散避免写入热点集中在少数Tablet上。分区数16是按照当时集群有8个Tablet Server单个Tablet承担日均300万以上数据量来估的。别贪多分区数设成64甚至128看着很分布式实际上是让每个Tablet都在空转反而浪费内存和CPU。写实时表的时候还有一个关键点字段类型和精度先定义好。Kudu的列类型调整成本很高一个DECIMAL精度改小会导致数据写入失败改大要重组表生产环境基本只能重建。所以主键选型、枚举字段长度、金额单位这类决策一定要在上线前跟业务确认清楚否则后患无穷。3.3 近实时查询与分析体验Kudu落地之后数据服务能力才真正体现出来。Kudu本身不带SQL引擎但Impala和Spark SQL都能直接读它。日常使用中报表查询基本都走Impala因为Impala是常驻进程查询启动开销小跑几百毫秒到几秒的分析SQL都很常见。用户要查实时订单明细直接拼一条SQL时间范围过滤加上订单状态、省份等条件一秒内出结果体验跟查一个普通数据库差别不大。这一步对业务的意义非常大。KafkaSpark Streaming直接算指标出来的结果是一个固定的汇总值业务想换个维度看得改代码、重新上线。而Kudu存的是明细业务想怎么看就怎么写SQL自助分析完全放开。这就是算完丢掉和落得下、查得快的本质差别前者把答案给你后者把问问题的能力给你。我这几年负责的实时数仓项目只要牵扯到实时明细可回溯宽表实时更新的需求最终基本都落到Kudu这一层。相比曾经用HBase扛类似业务的方案Kudu让我最满意的一点是——查询引擎和存储引擎之间不需要自己写复杂的rowkey设计SQL直接上开发效率不是一个量级的。4. 选型决策什么时候用KafkaSpark Streaming什么时候用ETLKudu4.1 先别急着比组件先对业务做五个排查很多人做技术选型喜欢把Kafka、Spark Streaming、Kudu、ETL这几个名词放在一起堆然后争论哪个框架更强。以我实际评审项目的经验这种比法经常争不出来结果。真正高效的姿势是把业务需求拆成下面五个问题逐一核对。第一结果形态是追加型还是更新型实时告警、访问量统计这类数据只会不断追加不会回头改老数据用Kafka加Spark Streaming就够了。订单状态、库存余量、用户画像标签这类老数据会反复更新就一定要有一个支持主键变更的存储Kudu的upsert天然契合。第二下游要的是固定指标还是任意切片查询如果报表的维度都是提前定死的Spark Streaming算好指标直接投喂链路短且稳定。如果业务说我还没想好要怎么看反正先存着那必须有明细层Kudu是更稳妥的选择。第三延迟能不能接受秒级两条链路整体都是秒级延迟但KafkaSpark Streaming如果只算指标端到端可以更快些走了Kudu之后写入和合并会有少量额外开销。如果你的业务要毫秒级延迟这两条路线都不合适那是别的水系统的事了。第四团队已有的基础设施是什么如果公司已经有了一套成熟的Spark集群那两种方案的边际成本都比较低。如果还在早期搭建阶段多引入一个Kudu集群意味着运维上多一套东西要盯tablet滚动重启、compaction压力、master高可用都是新增的日常工作。第五是否要求数据服务能力Kudu最明显的优势就是查询开放性好面对临时分析需求不需要每次都发版。如果实时数据的消费方式非常固定都是下游程序语言里直接调接口那Kudu的SQL能力对你来说就不是第一优先级。4.2 两张表看清两条链路的边界把这些考察维度列成表格会直观很多对比维度Kafka Spark StreamingETL Kudu端到端延迟秒级指标链路可压到秒内秒级需算上写入合并时间结果更新能力无原生更新能力需外接数据库原生按主键upsert明细数据回溯基本不保留明细长期保留支持按条件查即席查询不支持需二次加工配合Impala/Spark SQL直接查下游接入方式写固定指标或消息接口简单提供SQL接口业务自助分析运维复杂度组件较少调优点集中在Spark多一套Kudu集群需管理tablet典型适用场景实时告警、指标计算、清洗转发实时数仓、订单状态、实时宽表看完这张表可以得出一个简单的倾向性判断如果业务只要求实时算出一个数优先KafkaSpark Streaming不让存储复杂度掺和进来如果业务要求实时数据既要算也能查还要按主键改那就别硬挺了直接上ETLKudu。不过这里要说一句公道话这两条路在实际生产里不是二选一的互斥关系。我参与过的实时数仓项目里很多最终是组合拳——Kafka接Spark Streaming先把高价值指标实时算出来供大屏使用同时把加工后的明细按主键upsert进Kudu供业务做自助查询和分析。这样既保住了实时链路的计算速率又满足了数据服务的灵活性。所谓架构这事本来就是哪块缺不了就往哪块补没有谁规定一个项目只能用一种路线。5. 生产踩坑实录两条链路各自容易翻车的细节5.1 Spark Streaming侧积压、恢复和速率控制第一个坑是批间隔设置拍脑袋。有的同学为了追求更实时把batch interval从5秒调成1秒本来以为延迟会降低结果运行半小时后每个batch的处理时间反而超过批间隔Spark作业永远在追赶上一批数据积压越滚越大。从Spark UI上看processing time一直大于batch durationlag持续上涨。正确做法是先看单批数据处理耗时留出30%以上的余量再定间隔。比如单批处理耗时在1.5秒批间隔至少设到3秒而不是1秒。第二个坑是checkpoint目录的兼容性问题。Spark Streaming的checkpoint会把当前应用的序列化对象存下来恢复时直接反序列化。一旦你的作业代码改了类结构或者升级了Spark版本老checkpoint根本加载不了任务一启动就反序列化失败。而且有些错误的恢复逻辑还会导致从老状态继续跑数据错乱。这件事我踩过不止一次新逻辑要上线我把checkpoint目录换掉了丢了一次状态结果从Kafka重新消费老数据中间一段指标就重算了一遍。所以我现在做这类改造时会把checkpoint当成状态灾备来看待能保留就保留黄金副本不能保留就必须接受上游数据重放带来的后果。第三个坑是背压参数和maxRatePerPartition的关系没理清。开背压是为了在积压时自动降低消费速率设maxRatePerPartition是为了限制单个分区消费上限。两者可以一起开但不能互相替代。有一次我把maxRatePerPartition设得很小以为能靠它挡住流量尖峰结果背压一启动消费速率一降再降直接降到个位数积压了快一个小时的量。调整思路应该是maxRatePerPartition按峰值估算留余量背压负责在突发异常时把速率往回收两者一个管上限、一个管动态调节。5.2 Kudu侧分区、主键和写入吞吐Kudu这边最常见的坑就是分区数估算失误。哈希分区在建表时就锁死了后面不能动态调整。分区数少了单个Tablet数据量和写入压力过高会被拆得七零八落分区数多了每个Tablet都要占内存和CPU整条写链路性能直线下降。我见过有人把一个预计每天2000万行的表建了128个分区结果每个Tablet的数据量小得可怜查询时Task还要在各个Tablet之间穿梭性能比不分区的HDFS还要差。主键设计则是最隐蔽的坑。Kudu的主键有个天然约束建表后不能改写入时也不支持修改主键本身。有些人以为upsert和HBase类似主键变了也能覆盖其实Kudu的更新只能改主键以外的列主键变了就是一条新数据旧数据还在。更麻烦的是主键选得太长比如拿整个JSON字符串当主键写入时每条数据的索引开销大得吓人跑的写入任务慢到怀疑人生。所以建实时大表前务必把主键选短、选业务稳定、选不会反复变化的字段。第三个事是写入吞吐上不去的排查。最开始用Kudu的Java客户端往里面灌数run起来发现每秒只能写几千行后来翻官方文档才发现没做批量flush——客户端默认要攒到一定大小或者达到设定的超时时间才真正提交。调了flush阈值、调大了worker线程数、把tablet数量按数据量重估后吞吐才正常起来。Kudu的单写路径限制不算宽很多性能问题都出在表设计和客户端参数上别一上来就怀疑集群配置。5.3 一个组合场景的完整排查链路说一说让我印象最深的一次事故因为那个场景恰好把两条链路的坑串到了一起。某项目实时订单表原先跑得好好的有一天Kafka积压突然飙升到几百万条。我第一时间看了Spark Streaming的batch处理时间正常再看Kudu的写入日志发现Tablet Server频繁刷写磁盘IO已经冲到接近饱和。再深入查发现这个表的分区数当时建少了赶上业务做了一场大促订单量翻了三倍单区块写入压力指数级上升Kudu来不及合并Spark端往Kudu直插的吞吐被拖到极低积压就堆到了Kafka上。排查的顺序说白了就是先看积压在哪再看阻塞在哪最后看源头为什么慢。当时我们从Kafka的lag往前推发现消费端offset完全没动就怀疑是下游写入阻塞然后切到Kudu的监控页看Tablet写入延迟和刷写次数才定位到分区数不足这个根因。缓解措施是把临近的大促域数据先分流到另一张临时表缩减单表压力中期方案是重新设计分区按更细的维度打散后再上线。这套过程里最值得说的经验是实时链路出问题不要只看一个组件。Kafka、Spark Streaming、Kudu之间是串联的任何一个环节的积压都会层层传导。排查的时候一定从消息积压点开始逐级往下游摸看数据卡在哪个环节再回头分析那个环节的瓶颈参数。6. 我的个人判断与一个实用小技巧做了这么多实时项目之后我个人对这两种流处理方式的体会是它们不是竞争对手而是不同需求层次对应的不同工具。如果你只需要实时算指标、告警触发、快速清洗转发KafkaSpark Streaming链路短、上手快、逻辑直观。微批架构虽然不够纯实时但它的工程稳定性远大于理论上的毫秒级噱头而且这套技术栈在很多团队里已经有成熟的运维经验。如果你要做实时数仓、要保留可更新的明细、要让业务自己写SQL查数据那Kudu这一层几乎是绕不开的。ETL进Kudu再借助Impala/Spark SQL输出把实时和分析粘在了一起是今天做实时数仓选型时性价比很高的一条路。至于延迟只要不追求毫秒级这条路非常够用。最后分享一个实用小技巧。日常维护这两类链路时我习惯在Kafka的consumer里多打印一条消费进度日志当前消费到的offset、最近5分钟的消费速率、topic当前最新的offset。这样在排查积压时不用每次都翻监控面板命令行里看一眼日志就能判断是生产端突增还是消费端变慢。进Kudu的任务我在写入前还会用REFRESH TABLE强制刷新一下元数据缓存避免新表或新分区在长时间运行后查不到数据这种小习惯能在关键时刻省下不少排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑