4、实时特征系统架构:典型实时特征计算流程、Flink/Kafka Streams特征管道、在线特征存储(Redis/HBase)

实时特征系统是离线与实时一致性校验的核心执行环境。与离线批处理不同,实时特征系统需要处理无界数据流,在毫秒到秒级延迟内完成特征计算、状态维护与结果输出。本章深入剖析实时特征系统的三大支柱:典型计算流程、流处理管道(Flink / Kafka Streams)以及在线特征存储(Redis / HBase)。

4.1 典型实时特征计算流程

一个通用的实时特征计算流程通常包含以下五个阶段,每个阶段都直接影响特征的一致性与时效性。

阶段 核心动作 一致性关注点
1. 数据接入 从 Kafka / Pulsar 等消息队列消费原始事件 确保 Exactly-Once 语义,避免重复或丢失事件
2. 事件解析与清洗 反序列化(Avro / Protobuf)、过滤无效字段、时间戳标准化 时间戳对齐(Event Time vs Processing Time)
3. 窗口与状态计算 滑动窗口聚合、Session 窗口、计数/均值/比率等统计量 窗口边界与离线对齐;状态后端选择(RocksDB / Heap)
4. 特征加工与转换 特征交叉、分箱、归一化、Lookup 外部维度表 特征函数版本与离线一致;维表变更感知
5. 特征输出与存储 写入在线特征存储(Redis / HBase),或直接推送到下游推理服务 写入原子性;TTL 管理;特征版本标记
关键实践: 在阶段 3 和阶段 4 之间,建议插入一个“特征快照”环节,将当前窗口的中间状态序列化为与离线特征表结构一致的格式,用于后续的离线-实时比对。

4.2 Flink / Kafka Streams 特征管道

Flink 和 Kafka Streams 是当前实时特征管道的主流选择。两者在架构哲学、状态管理和时间语义上存在显著差异,直接影响特征一致性的实现难度。

4.2.1 Flink 特征管道

Flink 提供真正的有状态流处理,支持精确一次语义(Exactly-Once Semantics)和事件时间(Event Time)处理,非常适合需要复杂窗口和状态一致性的特征计算场景。

  • 状态后端: 支持 MemoryStateBackend、FsStateBackend、RocksDBStateBackend。生产环境推荐 RocksDB,支持大状态且容错。
  • 时间语义: 原生支持 Event Time,通过 Watermark 机制处理乱序事件,与离线批处理的时间窗口对齐更自然。
  • 维表 Join: 支持 Async I/O 和 Lookup Join,可实时关联 HBase / Redis 中的维度特征。
// Flink SQL 示例:实时滑动窗口特征计算
CREATE TABLE click_events (
  user_id BIGINT,
  item_id BIGINT,
  event_time TIMESTAMP(3),
  WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'click_topic',
  'properties.bootstrap.servers' = 'localhost:9092',
  'format' = 'avro'
);

CREATE TABLE user_features (
  user_id BIGINT,
  click_count_1h BIGINT,
  window_start TIMESTAMP(3),
  window_end TIMESTAMP(3)
) WITH (
  'connector' = 'upsert-kafka',
  'topic' = 'feature_output',
  'key.format' = 'json',
  'value.format' = 'json'
);

INSERT INTO user_features
SELECT
  user_id,
  COUNT(*) AS click_count_1h,
  TUMBLE_START(event_time, INTERVAL '1' HOUR) AS window_start,
  TUMBLE_END(event_time, INTERVAL '1' HOUR) AS window_end
FROM click_events
GROUP BY
  user_id,
  TUMBLE(event_time, INTERVAL '1' HOUR);

4.2.2 Kafka Streams 特征管道

Kafka Streams 作为轻量级流处理库,直接嵌入应用内部,无需独立集群。其状态存储基于 RocksDB 或内存,通过 Kafka 主题的变更日志(Changelog)实现容错。

  • 状态存储: 使用 KeyValueStore 或 WindowStore,状态通过 Kafka 的 changelog 主题备份。
  • 时间语义: 主要依赖 Record 中的时间戳(可自定义),对 Event Time 的支持不如 Flink 原生,需要手动处理乱序。
  • 部署形态: 作为 Java 应用的一部分运行,适合微服务架构中的轻量特征计算。
// Kafka Streams 示例:滑动窗口计数特征
KStream<String, ClickEvent> clicks = builder.stream("click_topic",
    Consumed.with(Serdes.String(), clickEventSerde));

KGroupedStream<Long, ClickEvent> groupedByUser = clicks
    .map((key, value) -> KeyValue.pair(value.getUserId(), value))
    .groupByKey(Grouped.with(Serdes.Long(), clickEventSerde));

TimeWindowedKStream<Long, ClickEvent> windowed = groupedByUser
    .windowedBy(TimeWindows.of(Duration.ofHours(1)).advanceBy(Duration.ofMinutes(5)));

KTable<Windowed<Long>, Long> clickCounts = windowed.count();

clickCounts.toStream().map((windowedKey, count) -> {
    Feature feature = new Feature(
        windowedKey.key(),
        "click_count_1h",
        count,
        windowedKey.window().start(),
        windowedKey.window().end()
    );
    return KeyValue.pair(feature.getUserId().toString(), feature);
}).to("feature_output", Produced.with(Serdes.String(), featureSerde));
选型建议: 如果特征计算涉及复杂窗口(如 Session 窗口)、需要精确一次语义且状态规模较大,优先选择 Flink。如果特征管道需要与现有微服务深度集成,且状态规模可控,Kafka Streams 是更轻量的选择。

4.3 在线特征存储(Redis / HBase)

在线特征存储是实时特征系统的最后一环,负责以低延迟(通常 < 10ms)为在线推理服务提供特征数据。Redis 和 HBase 是两种最主流的方案,它们在数据模型、一致性保证和扩展性上各有侧重。

4.3.1 Redis 特征存储

Redis 基于内存,提供极低的读写延迟(微秒级),适合存储维度特征、实时聚合结果和短期特征快照。

特性 说明 一致性影响
数据结构 String、Hash、Sorted Set、Stream Hash 适合存储多字段特征;Sorted Set 可用于时间窗口排序
持久化 RDB 快照 + AOF 日志 AOF 可配置 everysec,但极端情况下可能丢失 1 秒数据
TTL 管理 支持 Key 级别过期 必须为特征设置合理的 TTL,避免过期特征影响一致性校验
原子操作 INCR、HSETNX、LUA 脚本 可用于实现特征更新的原子性,防止并发写覆盖
// Redis 特征写入示例(使用 Hash 结构)
// 特征 Key: feature:user:{userId}:click_count_1h
// 特征 Field: window_start, window_end, count, version

HSET feature:user:12345:click_count_1h \
  window_start "2025-04-01T10:00:00Z" \
  window_end "2025-04-01T11:00:00Z" \
  count "42" \
  version "v2.1"

EXPIRE feature:user:12345:click_count_1h 7200

4.3.2 HBase 特征存储

HBase 基于列族存储,支持海量数据(PB 级)和强一致性(CP 系统),适合存储历史特征序列、长周期聚合特征以及需要范围扫描的场景。

特性 说明 一致性影响
数据模型 RowKey + 列族 + 列限定符 + 时间戳 RowKey 设计直接影响特征查询效率;时间戳可用于特征版本管理
一致性 强一致性(CP),写入后立即可见 适合需要严格一致性的特征校验场景
TTL 管理 列族级别 TTL,自动清理过期数据 需与离线特征表的保留周期对齐
批量读取 支持 Scan 操作,可批量获取多个用户特征 可用于离线-实时批量比对任务
// HBase 特征表设计示例
// 表名: user_features
// 列族: cf (包含 click_count_1h, purchase_amount_1d, ...)

// RowKey 设计: userId_reverse + featureType + windowEndTimestamp
// 例如: 54321_click_count_1h_1711929600

// 写入特征
Put put = new Put(Bytes.toBytes("54321_click_count_1h_1711929600"));
put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("value"), Bytes.toBytes("42"));
put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes("version"), Bytes.toBytes("v2.1"));
table.put(put);

// 批量扫描(用于一致性校验)
Scan scan = new Scan();
scan.setStartRow(Bytes.toBytes("54321_click_count_1h_1711929600"));
scan.setStopRow(Bytes.toBytes("54321_click_count_1h_1711933200"));
ResultScanner scanner = table.getScanner(scan);
一致性陷阱: 使用 Redis 时,如果特征更新不是原子操作(例如先读后写),并发场景下可能导致特征值被旧值覆盖。建议使用 Lua 脚本或 Redis 的 WATCH/MULTI/EXEC 事务机制保证原子性。HBase 在 Region 分裂期间可能出现短暂的不可用,特征管道需要设计重试机制。

4.4 实时特征系统架构总览

下图展示了从数据源到在线特征存储的完整实时特征管道架构,以及各组件在一致性校验中的角色。

实时特征系统架构图 实时特征系统架构总览 数据源层 Kafka Topic / Pulsar / 日志采集 Agent 流处理层(特征管道) Flink 特征管道 窗口聚合 / 维表 Join / CEP Kafka Streams 特征管道 轻量状态 / 微服务集成 一致性校验锚点 特征快照 / 版本标记 在线特征存储层 Redis(内存 / 低延迟 / TTL) HBase(海量 / 强一致 / 列族) 在线推理服务层 实时推荐 / 风控 / 搜索排序 图例:数据流方向 | 一致性校验锚点位于流处理层,负责生成可回溯的特征快照

4.5 实时特征管道与存储的协同策略

为了实现高效的离线-实时一致性校验,特征管道与在线存储需要协同设计以下策略:

  • 特征版本标记: 在写入 Redis / HBase 时,同时写入特征计算所使用的算法版本号(如 feature_version: v2.1),便于离线回溯时匹配正确的计算逻辑。
  • 时间戳对齐: 实时特征的时间窗口边界必须与离线特征表的窗口边界严格一致。建议使用 UTC 时间,并统一窗口起始时间的对齐方式(如整点对齐)。
  • 双写校验: 在特征管道中增加一个“影子输出”通道,将特征同时写入一个独立的校验存储(如 Kafka 的校验主题),供离线比对任务消费。
  • TTL 与生命周期管理: 在线特征的 TTL 应覆盖离线校验的最大延迟窗口。例如,如果离线任务可能延迟 2 小时,则在线特征的 TTL 应至少设置为 3 小时。
架构决策要点: 选择 Redis 还是 HBase 作为在线特征存储,取决于特征数据的规模、延迟要求和一致性模型。对于需要频繁更新且延迟敏感的特征(如实时点击计数),Redis 是首选。对于需要存储大量历史特征序列且要求强一致性的场景(如用户长期行为画像),HBase 更合适。在实际生产环境中,两者经常混合使用,形成“热-温”分层存储架构。