2. 到底在解决什么问题先看场景再谈架构这两年做数据平台只要摊子铺得够大早晚都会撞上一个尴尬局面不同的业务组上了不同的计算引擎有的用批处理框架有的用流处理引擎有的干脆直接用一套内存计算组件撑实时报表。物理机、容器、云主机混在一起数据源五花八门存储从分布式文件系统到列式仓库再到消息队列谁也不服谁。时间一长平台变得又重又难维护。最要命的问题是——资源利用率一路下滑。批任务在凌晨跑得欢白天闲着实时任务又挤在高峰抢资源互相拖累。那时候我面临一个非常现实的问题能不能把这些计算模式统一起来让同一批资源既能跑批任务也能跑流任务还能灵活应对突发的交互式查询于是我开始研究并实践所谓的“混合计算模式”这也是标题里最核心的四个字。先说结论它不是某个具体工具而是一套架构策略核心目标就是在同一个大数据平台内让不同类型的分布式计算任务共享资源、按需调度、协同运行避免以前那种“一个需求一套集群”的资源孤岛。这套东西适合谁适合已经在用分布式计算框架、数据量大到单机扛不住、又不想继续堆机器造烟囱的团队。也适合那些被离线任务和实时链路折磨得焦头烂额、想换个思路统一治理的技术负责人。3. 混合计算模式的整体设计思路3.1 两种传统模式的痛点先说批处理。批处理框架的设计哲学是“先存后算”。数据落盘任务调度起来一批一批地读一批一批地写天然适合全量扫描和复杂聚合。优点是好控制缺点是延迟高。你要跑一个全量用户画像跑两个小时很正常出结果时业务早凉了。流处理则相反数据边来边算窗口一滑聚合就出。秒级甚至毫秒级延迟都做得到特别适合监控、风控、实时推荐这些场景。但它对复杂事件处理和状态管理的要求极高而且一旦遇到大窗口大状态资源消耗非常吓人稍不注意就是内存爆炸。以前我们很多团队是怎么干的批归批流归流两套集群两套调度两套运维。这带来的不只是双倍机器成本还有更隐蔽的麻烦数据口径不一致。批任务跑出来的用户数和流任务实时统计的用户数对不上业务方天天来找。运维复杂度翻倍。两个集群都要监控、调参、扩容、升级人和精力都不够。资源无法共享。流任务忙的时候批任务闲得发慌批任务跑数据时流任务也借不到资源。所以后来社区里开始强调一体化也就是让批和流在同一个引擎里跑再用统一的调度层把不同计算模型揉在一起。这就是混合计算模式的底座。3.2 混合计算模式的架构分层我把它拆成三层来看第一层是资源管理层。它负责把物理资源切成可以弹性伸缩的单元比如容器、虚拟节点统一交给调度器管理。这一层不关心你是批还是流只负责“管资源”。第二层是计算引擎层。引擎自身要能同时表达批任务和流任务或者至少要能通过统一接口对接不同的执行模型。比如有的引擎用一套统一的编程模型同时搞定有界数据和无界数据这就是非常典型的混合计算能力。第三层是调度编排层。它负责决定某个时刻是优先跑实时作业还是让批处理任务插队要不要给交互式查询预留资源是否需要动态把空闲资源让给另一类任务这一层是混合计算模式能不能落地的关键。我实际构建平台的思路是这样的底层用统一的资源调度上面跑几类计算引擎然后通过调度策略把实时、离线、交互式查询三类负载整合在同一个资源池里再配一个统一的任务管理入口。所有任务都从同一个平台提交、监控、告警不再区分“这是批任务的集群”还是“这是流任务的集群”。3.3 为什么不用“一套引擎打天下”也许你会问既然有的引擎号称一套代码批流一体为什么不干脆全平台只用它说实话我自己也走过这条路。现实是你很难让所有业务方都写同一种模型。有些老业务跑了几年的批处理脚本全靠特定框架的算子你让它全量迁移到新引擎成本极高风险极大。而另一些业务场景对延迟特别敏感它需要轻量级、低开销的流处理也不愿意被大而全的框架拖累。所以混合计算模式更务实的选择是“统一调度多元计算”。说白了就是调度层和资源层统一计算引擎按需共存。而不是在代码层面强求大一统。这与微服务的思想很接近对外看是一个平台对内各司其职。4. 核心细节解析与实操要点4.1 数据不移动计算靠近数据在大数据领域性能的第一杀手是网络传输。数据在节点A代码跑到节点B去读哪怕存储网络再快也不如把计算拉到数据所在节点。混合计算模式下多个引擎共用底层存储时这一点尤其容易踩坑。你可能会把离线计算的结果目录和流式计算的结果目录混在一起导致引擎A读数据时去远端节点寻址数据又拉了一遍白白多了开销。实操中的原则是能本地读就本地读能用列式格式就列式格式能分区裁剪就分区裁剪。我通常会在任务设计阶段就规定好每一类任务只访问自己归属的数据域跨引擎访问时优先通过统一的中间存储而不是直连另一套引擎的内部文件。这样既减少了耦合也避免了不同引擎对同一份数据同时写造成的锁竞争。别小看这点混合模式下任务多、并发高数据访问模式的混乱往往是性能下降的隐形原因。4.2 资源隔离与共享的平衡混合计算模式听起来很美好但如果你不做资源隔离实时任务可能会因为离线任务的一次大扫描直接内存被打满然后连环失败。我常用的方案是给实时任务设置最小资源保障再允许它弹性借用离线任务的闲置资源。具体到实施上离线任务运行在可抢占的资源池里实时任务有高优先级必要时可以抢占离线任务的空闲容器交互式查询使用独立的小资源池防止一条大查询拖垮所有任务。这套机制需要调度器支持优先级、抢占、预留三种能力。我建议初期先别搞太复杂的策略就从“实时优先、离线弹性、查询隔离”三点入手跑稳了再加动态配额。4.3 状态管理与数据一致性流任务如果要和批任务混合跑最大的煎熬在状态管理。流任务为了支持窗口计算需要在内存里维护状态。一旦状态太大就要落到外部存储。而批任务往往要直接读历史结果做回刷两边对同一份状态数据的格式和生命周期理解不一样非常容易冲突。我的做法是明确区分“热状态”和“冷结果”。流任务只负责更新热状态比如最近几小时的实时汇总批任务负责修正冷数据比如对刚才的结果做全量回刷。中间用一个版本号机制来标识数据是否已修正。读取端拿到数据时先看版本实时优先展示热状态批任务跑完后自动切到冷结果业务侧无感知。这个方法听起来简单但在实际操作中非常管用。它避免了两套进程同时更新一份数据导致的覆盖和丢失也让流批结果之间的口径终于能对上了。4.4 混合模式下的编程模型选择我给平台选型时最看重的是“学习成本和迁移成本”。团队里既有老派的批处理工程师也有喜欢新技术的流处理工程师你不可能要求所有人用同一种写法。所以混合计算模式下我倾向于让每一种任务保留自己最熟悉的表达方式——批处理任务继续沿用原来的 SQL 或脚本新写的流任务用流式 API而交互式查询直接走 SQL 接口。在平台层统一封装任务的提交、日志、监控和告警至于内部是哪套引擎执行的对使用者透明。有人可能觉得这叫“缝合怪”。但在真实业务里工程师上手快、业务稳定、问题易排查比架构上的纯粹性重要得多。这就是我常说的“混合模式不是理论最优而是工程最优”。5. 实操过程与核心环节实现5.1 基础环境搭建示范我以一个模拟项目X为例介绍。这个项目不是实际业务但结构很典型一个数据平台需要同时跑三类任务——每小时一次的离线报表、秒级延迟的实时指标、临时写的交互式查询。第一步我先搭一个统一资源池。采用容器化部署每台机器加入同一个资源池标签区分用途node: hostname:># 伪代码示例动态分配任务优先级 def schedule(task): if task.type realtime: task.priority 10 task.preemptible False elif task.type batch: task.priority 5 task.preemptible True elif task.type interactive: task.priority 8 task.max_instances 2 cluster.submit(task)这段逻辑几乎是所有混合调度策略的雏形。优先级解决谁先跑可抢占解决资源不够怎么办并发限制解决交互式查询拖垮平台的问题。5.2 核心环节实现批流数据对齐数据一致性是混合计算模式里最让人头大的环节。我设计过一个非常具体的流程用来对齐批任务和流任务的结果。流任务实时计算每分钟的访问量写入实时结果表字段包括time_minute、uv、pv。批任务每小时根据全量日志回刷一次过去一小时的访问量写入修正结果表字段一样多一个version版本号。在查询端我写了一个合并逻辑SELECT time_minute, SUM(uv) AS uv, SUM(pv) AS pv FROM ( SELECT time_minute, uv, pv FROM realtime_table WHERE time_minute now() - interval 10 minutes UNION ALL SELECT time_minute, uv, pv FROM corrected_table WHERE version (SELECT MAX(version) FROM corrected_table) ) t GROUP BY time_minute;实时部分取最近 10 分钟修正部分取最新版本的一小时数据。中间重叠时段会自动去重查询结果始终是“准实时已修正”的形态。这个方案我跑了很久最大的体会是不要试图让流任务和批任务同时写同一张表而是让它们各写各的最后在读取层合并。这是最省心的做法也是很多混合计算产品底层采用的思路。5.3 参数选择与资源估算混合计算模式下资源估算比单引擎时要更精细。我的经验是先按峰值流量估算各任务独立需求再加总最后除以一个共享因子。举例说明流任务常规状态下需要 8 核 16G批任务夜间高峰需要 32 核 64G交互式查询需要 4 核 8G。如果完全独立集群你需要 44 核 88G。但混合模式下流任务峰值和批任务峰值通常并不同时出现交互式查询也只在上班时段活跃。所以实际资源池预留 60% 的冗余也就是 26 核 52G 左右就能转起来高峰期靠调度器把空闲资源临时借给高优先级任务。当然这个数字不是拍脑袋。我在上线前跑过一段时间的任务画像统计每个任务的 CPU 和内存使用曲线找出错峰空间。没有画像数据之前宁可多留资源也别一刀切。调度器是死的业务流量是活的预留不足比预留过多更容易出事。5.4 监控告警的关键指标混合计算模式下的监控比普通分布式计算更难做因为任务类型混杂你没法用一套固定阈值判断所有任务是否健康。我维护了一套分类指标实时任务重点看处理延迟、状态大小、背压情况批任务重点看耗时、数据倾斜比例、失败重试次数交互式查询重点看 P99 查询耗时、并发数、内存占用。这三个维度的告警阈值完全不一样。实时任务延迟超过 30 秒就要告警批任务跑得慢可能只是资源竞争查询偶尔超时也不用太敏感。如果不区分场景统一告警平台会被噪音淹没最后运维也懒得看了。6. 常见问题与排查技巧实录6.1 实时任务频繁被批量作业挤挂这是混合计算模式最常见的坑。明明设置了优先级离线任务还是把资源吃满导致实时任务容器被驱逐。原因多半是调度器的优先级只在“等待队列”里生效一旦任务已经运行就不会被抢占。很多调度框架默认不开启抢占或抢占粒度太大导致实时任务得不到资源。我的排查步骤是查看实时任务的资源在这种模式下是否有残留的待处理状态查看调度器日志确认是否有抢占事件发生如果确认没有抢占就检查容器资源限制是否设置过小最后检查离线任务是否有多个任务同时启动形成了并发洪峰。解决方法是给离线任务的启动加一个全局限流限制同时运行的最大数量再开启调度器的抢占机制。这样即使离线任务先跑起来实时任务也能通过抢占拿到资源。6.2 批流数据对不齐永远差一点另一个高频问题是实时统计出的数据和批任务修正好后的数据对不上。有时差几万条有时差几个点。我复盘过多次原因通常不在计算逻辑而在数据源。比如消息队列里的延迟重试数据批任务读取时已经包含了重发数据流任务处理时却因为去重键设置不当把同一条数据算了两次。排查方法很简单先固定一个时间窗口把流任务处理前和处理后的数据量对比再看批任务的输入输出对比。只要两边对同一份输入的统计口径完全一致问题就出在数据源或去重逻辑上。解决方法是统一设置消息的唯一键在流任务入口做幂等去重同时让批任务读取时也按唯一键去重。这个动作要放在最前面越往后的算子层去重越容易漏掉。6.3 调度队列堆积查询延迟飙升当离线任务瞬时涌进来很多子任务时交互式查询经常会排队响应变得很慢。用户会疑惑明明集群总 CPU 还有闲置为什么查询还是慢这个现象通常不是因为资源不足而是调度器把子任务一口气全部提交到了同一个队列队列等待时间变长了。我采用的优化方法是把交互式查询的调度队列独立出来并设置一个较小的并发上限。同时给离线批处理的子任务设置分批提交例如每批只提交 20 个跑完再提交下一批。这样既能保证批量任务的高吞吐又不会一股脑堵住交互查询。6.4 状态数据膨胀与重启恢复慢流任务跑久了状态存储越来越大重启一次要恢复很久期间实时指标直接断档。这在混合模式下会被放大因为资源池还要分给其他任务状态恢复的 IO 和网络开销会跟批任务的数据扫描争抢存储带宽。我的两个核心建议一是开启检查点但不要把检查点频率设得太高按业务可容忍的恢复时长来定我一般默认 1 到 2 分钟一次二是把状态数据和普通业务数据放在不同的目录或存储队列避免互相干扰。如果你用的是共享存储务必给状态存储单独设置 IO 优先级。7. 经验总结混合模式真正要跨过的门槛做混合计算模式这一年多我个人体会最深的不是技术选型而是团队思维方式的转变。以前大家习惯性认为“批就是批、流就是流、查询就是查询”一旦混在一起就总觉得资源会被别人抢走。但实际上只要调度策略够清晰、监控体系够细化混合模式带来的资源红利和运维红利都非常明显。同样规模的业务比以前省了差不多 30% 的机器这还不是最关键的。最关键的是新业务上线时不用再纠结到底要建哪种集群直接从平台里按需提交任务就行。最后再分享一个我一直保留的小习惯无论调度策略写得多么完善上线前一定要做一轮“故障演练”。人为把离线任务调成异常并发看一看实时任务是否会被影响人为把某个引擎停掉看看其他任务是否会自动转移。混合计算模式下最怕的不是组件挂了而是挂了之后资源没人接管、任务无人问津。演练过几轮你的平台才算真正敢叫“混合计算模式”。