3. 实时特征生产流程:实时流处理、状态后端与在线存储
实时特征的生产流程与离线批量处理有本质区别。数据以无界流的形式持续到达,系统需要在毫秒至秒级延迟内完成计算、状态维护与结果存储。本章拆解实时特征生产的三个核心环节:实时流处理引擎(Flink / Spark Streaming)、状态后端(State Backend)以及特征在线存储(Redis / HBase)。
3.1 实时流处理引擎:Flink 与 Spark Streaming 的架构对比
实时特征的计算引擎目前以 Apache Flink 和 Apache Spark Streaming 为主流。两者在运行时模型、时间语义、状态管理上存在显著差异,直接影响特征生产的准确性与延迟。
| 对比维度 | Apache Flink | Apache Spark Streaming |
|---|---|---|
| 运行时模型 | 真正的逐记录流处理(DataStream API) | 微批次处理(Micro-Batch),将流切分为小批次 |
| 事件时间语义 | 原生支持,内置 Watermark 机制 | 需额外配置,延迟较高 |
| 状态管理 | Keyed State + Operator State,支持增量 checkpoint | 基于 DStream 的 transform 或 Structured Streaming 的 StateStore |
| 端到端延迟 | 亚秒级(通常 100ms ~ 1s) | 秒级(通常 1s ~ 10s,取决于批次间隔) |
| 容错机制 | 分布式快照(Chandy-Lamport) | 基于 RDD 血缘 + WAL(Write Ahead Log) |
| 适用场景 | 高实时性、精确一次语义、复杂事件处理 | 准实时、与批处理统一架构、生态兼容性好 |
选型建议:如果特征要求严格的事件时间准确性(如交易风控中的时序特征),优先选择 Flink;如果团队已有成熟的 Spark 批处理链路,且特征延迟容忍度在 5 秒以上,Spark Structured Streaming 是更经济的方案。
3.2 实时特征生产流程总览
下图展示了一条典型的实时特征生产流水线:数据源通过消息队列接入,经流处理引擎计算后,将特征结果写入在线存储,供在线推理服务低延迟读取。
3.3 状态后端:实时特征计算的“记忆”载体
状态后端是流处理引擎维护中间计算结果的存储层。在特征生产中,滑动窗口聚合、用户画像累积、序列特征拼接等操作都依赖状态后端。Flink 提供三种内置状态后端:
- MemoryStateBackend:状态存储在 TaskManager 堆内存中,适合小状态、调试环境,生产环境不推荐。
- FsStateBackend:状态存储在本地文件系统或 HDFS,checkpoint 持久化到远程文件系统,适合中等规模状态。
- RocksDBStateBackend:基于 RocksDB(LSM-Tree 嵌入式 KV 存储),状态存储在本地磁盘,支持超大状态(TB 级),生产环境首选。
关键注意点:状态后端的选择直接影响特征回放的准确性。如果使用 RocksDB,不同版本的 RocksDB 或配置参数(如 block cache 大小、write buffer 数量)可能导致状态恢复时的微小差异,进而造成实时特征与离线回放特征不一致。建议在离线回放环境中使用完全相同的状态后端配置。
3.4 特征在线存储:Redis 与 HBase 的选型与设计
实时特征计算完成后,需要写入低延迟的在线存储系统,供推理服务在毫秒级内获取。Redis 和 HBase 是两种最常用的方案,它们在数据模型、延迟、容量上各有侧重。
| 对比维度 | Redis | HBase |
|---|---|---|
| 数据模型 | Key-Value,支持多种数据结构(String、Hash、List、Sorted Set) | 宽表模型,RowKey + 列族 + 时间戳 |
| 读写延迟 | 微秒级(纯内存操作) | 毫秒级(依赖 HDFS 和 RegionServer 缓存) |
| 容量扩展 | 受限于内存,集群模式可扩展但成本高 | 基于 HDFS,可扩展到 PB 级 |
| TTL 过期 | 原生支持 Key 过期,精确到秒 | 通过列族 TTL 或自定义清理策略 |
| 特征存储典型用法 | 用户实时画像、短窗口聚合特征(如最近 5 分钟点击次数) | 用户长期行为序列、跨天累积特征、需要范围扫描的场景 |
| 一致性模型 | 最终一致性(集群模式下可能丢失写操作) | 强一致性(单行事务) |
3.5 实时特征写入在线存储的最佳实践
以下是一个 Flink 写入 Redis 的典型代码片段,使用 Flink Redis Connector 将特征结果以 Hash 结构存储:
// Flink 实时特征写入 Redis 示例
DataStream<FeatureResult> featureStream = ...;
// 定义 Redis 映射函数
featureStream.addSink(new RedisSink<>(
new FlinkJedisPoolConfig.Builder()
.setHost("redis-cache.cluster.local")
.setPort(6379)
.setMaxTotal(200)
.build(),
new RedisMapper<FeatureResult>() {
@Override
public String getKeyFromData(FeatureResult data) {
return "feature:user:" + data.getUserId();
}
@Override
public RedisCommandDescription getCommandDescription() {
// 使用 HSET 存储特征字段
return new RedisCommandDescription(RedisCommand.HSET);
}
@Override
public String getValueFromData(FeatureResult data) {
// 将特征值序列化为 JSON 字符串
return data.getFeatureJson();
}
}
));
设计要点:
- Key 设计:采用“特征域:实体类型:实体ID”的命名规范,如
feature:user:12345,便于管理和避免冲突。 - TTL 策略:实时特征通常有时效性,建议设置合理的过期时间(如 24 小时或 7 天),避免存储膨胀。
- 批量写入:使用 Pipeline 或批量 Sink 减少网络开销,但需注意原子性要求。
- 容错处理:在线存储写入失败时,应启用重试机制并记录失败特征,用于后续离线回放校验。
3.6 实时特征生产流程的常见问题与避坑
- 状态膨胀导致 OOM:未合理设置状态 TTL 或状态后端配置不当,导致状态无限增长。解决方案:使用 RocksDB 状态后端,并配置
state.ttl和state.backend.rocksdb.memory.managed。 - 事件时间乱序导致特征偏差:未正确设置 Watermark 或 allowed lateness,导致迟到数据被丢弃或重复计算。建议在离线回放时使用相同的 Watermark 策略。
- 在线存储热点 Key:高并发场景下,某些热门用户或物品的特征 Key 成为读写热点。解决方案:对 Key 进行散列(Hash Tag)或使用本地缓存降级。
- 实时与离线特征不一致:这是最常见的问题,根源往往在于状态后端配置、时间窗口边界、或存储写入时序的差异。下一章将专门讲解回放校验方法。