第 34 章 / 共 57 章 · 约 4 分钟
关键技术:Flink 与流处理
流处理引擎(以 Flink 为代表)持续地读取事件流,在内存或本地磁盘里维护状态,边到边算,把结果写到下游。和“每条消息一个消费者处理”相比,它多了三样东西:按 key 分布的大状态、基于事件时间的窗口,以及故障后状态不丢、不重复计算的检查点机制。实时计数、Top-K、实时特征、风控规则,常常需要它。
1. 核心模型
- 数据流图:source(通常是 Kafka)→ 一串算子(map、filter、keyBy、window、join)→ sink(数据库、缓存、另一个 topic)。
- 按 key 分区的状态:
keyBy(userId)之后,同一个 key 的事件总由同一个并行实例处理,它的状态(计数、最近 10 次交易)就放在那个实例本地。状态可以放在堆内存,也可以用 RocksDB 放到本地磁盘,大到 TB 级。 - 事件时间和水位线:按事件发生的时间而不是到达的时间计算。水位线(watermark)表示“时间早于 T 的事件应该都到了”,窗口在水位线越过窗口末尾时触发。允许迟到(allowed lateness)之后才到的事件,可以丢弃,也可以送到旁路输出单独处理。
- 窗口:滚动窗口(每分钟一个)、滑动窗口(最近 5 分钟、每分钟更新一次)、会话窗口(不活跃超过 30 分钟就切开)。
- 检查点(checkpoint):定期在数据流里插入屏障,各算子把状态快照存到持久存储,同时记下 source 的 offset。故障后从最近的检查点恢复状态,并把 source 倒回对应的 offset 重放,所以状态是“恰好一次”。要让外部结果也恰好一次,sink 必须是事务性的(两阶段提交写 Kafka)或者幂等的(按主键 upsert)。
2. 什么时候用
- 实时聚合:广告每分钟点击数、视频实时播放量、按地区的订单量。
- 实时 Top-K 和热点检测:最近一小时的热搜、异常流量的 IP。
- 实时特征和风控:用户最近 10 分钟的交易次数、金额、设备变化,喂给模型或规则,见支付欺诈检测案例。
- 流式 ETL 和物化视图:清洗、打宽、关联维表后写入数仓或搜索索引。
- 复杂事件处理:“同一账户 5 分钟内在三个国家登录”这类模式匹配。
3. 什么时候不适合
- 逐条独立处理:发通知、调接口、写一条记录,普通的 Kafka 消费者服务就够了,不需要一个流处理集群。
- 离线批处理:每天跑一次的报表,用 Spark 或数仓 SQL 更简单。
- 分钟级延迟就可以:微批(每几分钟跑一次小批任务)通常更容易维护。
- 团队没有运维能力:状态、检查点、版本升级、反压都需要经验,托管服务能减轻不少负担。
4. 关键限制和数量级
| 维度 | 要记住的事 |
|---|---|
| 延迟 | 毫秒到秒级;窗口结果要等水位线,延迟至少是窗口长度加允许的乱序时间 |
| 吞吐 | 随并行度水平扩展,集群每秒处理百万级事件很常见 |
| 状态 | RocksDB 状态后端可以到 TB 级;状态越大,检查点和恢复越慢 |
| 检查点间隔 | 常见几十秒到几分钟;间隔越长,故障后要重放的数据越多 |
| 热点 key | 一个 key 的所有事件都落在同一个并行实例上,热门 key 会让这个实例成为瓶颈 |
5. 面试中常被追问的点
- 迟到的事件怎么处理? 水位线留一点乱序容忍(例如 10 秒),再加允许迟到时间,窗口结果可以更新;超过的事件进旁路输出,事后修正,或者接受少量误差。
- 故障恢复会不会重复计数? 内部状态靠检查点恢复,不会重复;写到外部时,用事务性 sink 或者按“窗口 + key”做主键 upsert,重放时覆盖而不是累加。
- 热门 key 怎么办? 两阶段聚合:先按
key + 随机后缀做局部聚合,再按 key 合并。 - 代码改了,历史数据要重算? 从 Kafka 的旧 offset 重放(Kappa 架构);保留期不够时,从数据湖里的原始事件回填。Lambda 架构(批和流两套代码)现在用得少了,因为两套逻辑很难保持一致。
- 为什么不在每个服务里自己维护计数? 多实例并发、故障恢复、乱序和窗口语义都要自己实现,很容易出错。
6. 和其他技术的取舍
| 选项 | 适合 | 代价 |
|---|---|---|
| Flink | 低延迟、大状态、事件时间语义、恰好一次 | 集群和状态的运维成本 |
| Spark Structured Streaming | 已经在用 Spark、能接受微批延迟 | 延迟通常是秒级以上 |
| Kafka Streams | 输入输出都是 Kafka,作为库嵌入服务 | 和 Kafka 绑定,分布式能力有限 |
| 普通消费者服务 | 无状态或状态很小的逐条处理 | 窗口、乱序、恢复都要自己写 |
| 批处理(Spark、数仓 SQL) | 每小时、每天的离线计算 | 结果有延迟 |
7. 本站案例
- ML 案例 3:广告点击率预估:曝光和点击日志按曝光 id 做流式 join,等待窗口内没有点击就记为负样本。
- ML 案例 4:支付欺诈检测:用流式滑动窗口计数器维护实时的频次特征。
- 案例 11:搜索自动补全:查询日志的流式热词检测。