4、实时计算引擎:Flink/Kafka Streams基础、事件时间与处理时间、状态后端选择、Checkpoint机制

好,咱们进入第四章。这一章聊的是实时计算引擎,说白了就是用户画像系统的心脏。你前面把数据接进来了,标签规则也定好了,但谁来算?怎么算?算到一半挂了怎么办?这些都得靠实时计算引擎来兜底。

我个人习惯把实时计算引擎比作「流水线上的工人」。Flink 和 Kafka Streams 就是两个最靠谱的工人。它们各有脾气,你得摸透了才能用好。

4.1 Flink 与 Kafka Streams:选谁更合适?

先说说 Flink。我最早接触 Flink 是 2018 年,那时候它刚火起来。说实话,第一眼就被它的「精确一次」语义吸引了。你想想看,做用户画像,用户的积分、等级、标签,差一个数都不行。Flink 在这块确实稳。

Flink 的核心优势在于:

  • 真正的流批一体:同一套 API 既能跑流也能跑批。我在项目中遇到过,白天跑实时标签,晚上用同一套代码跑历史数据回溯,省了不少事。
  • 强大的状态管理:用户画像需要维护大量用户状态,比如「过去7天浏览次数」、「最近一次登录时间」。Flink 的状态后端把这些管得明明白白。
  • 生态丰富:跟 Hive、HBase、Elasticsearch 都能无缝对接。

再聊聊 Kafka Streams。它轻量,真的轻。不需要单独部署集群,嵌在你的应用里就能跑。我有个小项目,用户量不大,就几十万,用 Kafka Streams 搭了个实时标签计算,两周就上线了。

但要注意,Kafka Streams 的短板也很明显:

  • 状态管理相对简陋,复杂窗口操作不如 Flink 顺手
  • 没有 Flink 那样的 Checkpoint 机制,故障恢复得靠 Kafka 自身的 offset 管理
我的建议: 用户画像系统,用户量过百万、标签逻辑复杂,直接上 Flink。如果只是简单的实时统计、用户量不大,Kafka Streams 够用还省资源。

4.2 事件时间与处理时间:别搞混了

嗯,这里要注意。很多新手在这块栽过跟头。我刚开始做实时画像时,就吃过一次亏。

什么叫事件时间?就是用户真正发生行为的时间。比如用户凌晨1点在 App 上点了个赞,这个时间就是事件时间。什么叫处理时间?就是你的 Flink 任务收到这条数据的时间。网络延迟、系统拥堵,处理时间可能比事件时间晚好几个小时。

为什么会这样?你想想看,用户手机信号不好,数据在本地缓存了3小时才发出来。这时候你用处理时间算「今日活跃用户」,那这个用户就被漏掉了。画像就不准了。

核心原则: 做用户画像,永远用事件时间。处理时间只适合做监控告警这类对时间精度不敏感的场景。

Flink 里怎么配?看这段代码:

// 设置事件时间语义
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

// 定义 Watermark 策略
DataStream<UserEvent> stream = env
    .addSource(kafkaConsumer)
    .assignTimestampsAndWatermarks(
        WatermarkStrategy.<UserEvent>forBoundedOutOfOrderness(Duration.ofSeconds(10))
            .withTimestampAssigner((event, timestamp) -> event.getEventTime())
    );

这里有个坑。Watermark 的延迟时间设多少?设短了,乱序数据丢得多;设长了,实时性变差。我一般先设 10 秒,上线后观察数据延迟情况再调。记住,没有完美的值,只有适合你业务的值。

4.3 状态后端选择:内存、文件系统还是 RocksDB?

状态后端,说白了就是你的用户状态存哪儿。Flink 给了三个选项:

状态后端 存储位置 适用场景 我的经验
MemoryStateBackend TaskManager 内存 小状态、调试用 千万别上生产,一挂全丢
FsStateBackend TaskManager 内存 + 文件系统 中等状态、高吞吐 适合状态量在 GB 级别
RocksDBStateBackend 本地磁盘 + 文件系统 超大状态、TB 级别 用户画像首选,稳

我个人习惯,用户画像系统一律用 RocksDBStateBackend。为什么?因为用户画像的状态太大了。每个用户存几十个标签,百万用户就是几千万个键值对。内存根本扛不住。

我曾经踩过的坑: 有次图省事用了 FsStateBackend,结果状态涨到 50GB,GC 频繁导致任务频繁重启。换成 RocksDB 后,问题迎刃而解。RocksDB 虽然读写慢一点,但胜在稳定。

配置 RocksDB 也很简单:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStateBackend(new RocksDBStateBackend("hdfs://namenode:8020/flink/checkpoints", true));

注意第二个参数,true 表示增量 Checkpoint。这个一定要开,不然每次全量备份,大状态能把你 HDFS 写爆。

4.4 Checkpoint 机制:你的救命稻草

做实时计算,最怕什么?任务挂了。挂了不可怕,可怕的是挂了之后状态丢了,用户标签全乱套。

Flink 的 Checkpoint 机制就是干这个的。它定期把状态快照保存下来。任务重启时,从最近的 Checkpoint 恢复,数据一条不丢,状态一个不错。

Checkpoint 的核心参数就几个:

  • 间隔时间:默认 10 秒。我一般设 30 秒到 1 分钟。太频繁影响性能,太稀疏恢复时丢数据多。
  • 最小间隔:两次 Checkpoint 之间至少隔多久。防止 Checkpoint 风暴。
  • 超时时间:一次 Checkpoint 超过这个时间就算失败。默认 10 分钟,大状态可以调大。

配置示例:

StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.enableCheckpointing(30000); // 30 秒一次
env.getCheckpointConfig().setMinPauseBetweenCheckpoints(10000); // 最小间隔 10 秒
env.getCheckpointConfig().setCheckpointTimeout(600000); // 超时 10 分钟
env.getCheckpointConfig().setTolerableCheckpointFailureNumber(3); // 容忍 3 次失败
一个小技巧: 我习惯把 Checkpoint 存到 HDFS 上,并且保留最近 5 个。这样即使最新的 Checkpoint 坏了,还能回退到上一个。别问我怎么知道的,都是血的教训。

最后说一句,Checkpoint 不是万能的。它保证的是「精确一次」语义,但代价是性能损耗。如果你的业务允许丢少量数据(比如实时大屏展示),可以调大间隔甚至关闭 Checkpoint。但做用户画像,我建议你老老实实开着。

好了,这一章就到这儿。下一章咱们聊聊标签计算的具体实现,包括怎么用 Flink SQL 写标签规则、怎么处理迟到的数据。到时候见。