做一个实时数仓项目,最深的体会是:网上Flink的教程铺天盖地,WordCount、Kafka到ES的Hello World一搜一大把,但一旦把它丢进真实的业务环境,接上公司自己的Kafka集群、自己的运维平台、自己的JDBC连接池,各种问题会从四面八方涌出来。我今年完整负责了一套实时数仓从零到上线的全过程,这篇文章不打算复述官方文档,而是把项目落地过程中真正让人头疼的、以及最终跑通之后值得沉淀的经验,挑重点拆开来讲。涉及的内容包括实时链路选型、Flink集群搭建、Flink SQL消费Kafka写入Elasticsearch、JDBC连接器异常排查、SASL认证异常排查,以及并行度与资源成本控制的思路。
整套系统现在的状态是:每天稳定处理上亿条业务消息,端到端延迟控制在秒级,大屏指标、实时报表、异常监控都跑在它上面。中间踩过的坑不少,但每一个坑背后都有清晰的根因。这篇文章就是把这些根因和排查路径完整记录下来。
1. 为什么最终选了Flink这套链路
1.1 离线数仓的痛点和实时化的真实诉求
项目背景是一家电商零售公司,业务方每天上午十点开晨会,晨会第一件事就是看昨天的销售数据。离线数仓凌晨跑批,早上九点出结果,勉强能赶上。但2023年下半年开始,运营团队提出了新需求:大促期间要实时看GMV、实时看各区域订单量、实时监控库存水位和支付成功率。这些需求一出来,离线数仓就彻底顶不住了。
这里需要先厘清一个概念:实时数仓并不是要把离线数仓推翻重来,而是把离线链路中延迟最高的那一层,从T+1改成秒级或分钟级。离线数仓处理的是海量历史数据,实时数仓处理的是正在发生的数据,两者的技术栈可以完全不同。
我们最初考虑过几条技术路线:
- 单纯用Kafka Streams做实时计算。优点是轻量、不需要额外引入计算引擎;缺点是状态管理能力弱,复杂事件处理、窗口计算、流表关联写起来非常痛苦。
- 用Spark Structured Streaming。优点是生态成熟、与离线批处理共用一套代码;缺点是微批模式带来的延迟比较高,而且对事件时间语义的支持不如Flink成熟。
- 用Flink。计算模型天然就是流式的,状态后端、精确一次语义、Checkpoint机制都是为流处理设计的,加上Flink SQL的成熟度这几年提升非常快,周报、报表、大屏这类场景写SQL就能搞定。
选型时还有一个关键考虑:团队后续要做实时特征工程、实时风控,这些场景对延迟和状态管理的要求极高,Flink在这方面的优势是不可替代的。
1.2 链路选型:从数据源到OLAP引擎的完整拼图
实时数仓不等于Flink一个组件,Flink只是中间的实时计算引擎。完整链路需要考虑数据采集、消息队列、计算引擎、存储引擎、查询引擎五个层面。我们最终定下来的链路是这样的:
| 环节 | 选型 | 理由 |
|---|---|---|
| 数据采集 | Canal + Debezium | Canal成熟稳定,Debezium适合跨数据库版本兼容 |
| 消息队列 | Kafka | 吞吐量高、生态兼容最好,Flink与Kafka的集成度最高 |
| 实时计算 | Flink SQL为主、DataStream API为辅 | SQL开发效率高,复杂场景用DataStream兜底 |
| 实时存储 | Elasticsearch + Doris | ES支撑明细查询和大屏,Doris支撑OLAP分析 |
| 调度运维 | DataSophon + 自研告警 | 可视化管控Flink集群,便于权限与资源管理 |
这套链路里最容易忽视的是入口:实时数仓的入口数据往往不止业务库的binlog,还有客户端埋点日志(通过Nginx打到Kafka)、第三方回调数据。所以Kafka topic的规范设计非常重要,我们把topic按业务域.数据层级.事件名称的规则来命名,比如order.dwd.payment_success,这样Flink SQL建表时一目了然,也方便后续做权限管控。
为什么实时存储选了ES和Doris两个?因为它们解决的是不同问题。ES擅长全文检索和明细倒排查询,适合大屏展示、日志检索、订单明细查询;Doris擅长大规模聚合分析,适合给BI报表提供秒级响应的OLAP查询。Flink计算结果双写两份,虽然会多一些资源开销,但换来了查询灵活性和稳定性的双保险。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Flink集群装配与资源基线的确定
2.1 集群搭建时最容易踩的内存坑
Flink集群搭建本身不复杂,难的是把内存参数调对。我见过太多人把taskmanager.memory.process.size设置得很大,结果集群一启动,TaskManager进程占满物理内存,直接把操作系统拖垮。还有人是把JobManager和TaskManager部署在同一批机器上,内存分配又没隔离开,导致OOM、GC频繁,作业状态一直不健康。
我先说我们最终采用的Standalone模式集群配置,三台物理机,每台16核64G内存,部署方式是:两台跑JobManager做HA,三台同时跑TaskManager。flink-conf.yaml里关键参数如下:
yaml复制jobmanager.memory.process.size: 4096m
taskmanager.memory.process.size: 16384m
taskmanager.memory.managed.fraction: 0.4
taskmanager.numberOfTaskSlots: 4
parallelism.default: 8
这里有两个细节容易踩坑:
第一,taskmanager.numberOfTaskSlots到底是设多少。每核一个slot是最朴素的经验,但实际要看任务类型。如果任务里有很多大状态的窗口计算,CPU其实不是瓶颈,堆内存才是,slot设4个比设8个更稳妥,因为每个TaskManager的堆内存是固定拆分的,slot越多每个slot分到的内存越少,大状态作业容易OOM。如果任务是纯ETL、无状态或轻状态,slot可以适当增加,比如8个。
第二,taskmanager.memory.managed.fraction是Flink用于RocksDB状态后端的内存比例。这个值我见过有人设0.6、甚至0.7,结果作业一跑,RocksDB疯狂占用堆外内存,最
