4. 实时特征系统架构
实时特征系统是支撑毫秒级特征计算与在线推理的核心基础设施。与离线批处理不同,实时特征要求数据从产生到特征就绪的端到端延迟控制在秒级甚至毫秒级,同时保证高可用与数据一致性。本节从流处理架构选型、实时特征存储、实时特征计算流程三个维度展开。
4.1 流处理架构:Lambda 与 Kappa
实时特征系统的流处理层通常采用两种经典架构:Lambda 架构与Kappa 架构。两者在数据路径、复杂度与一致性保障上存在显著差异。
4.1.1 Lambda 架构的流处理层
Lambda 架构将数据管道分为批处理层(Batch Layer)与流处理层(Speed Layer)。在实时特征场景中,流处理层负责处理近实时的增量数据,提供低延迟特征输出;批处理层则定期重算全量历史数据,修正流处理层可能产生的偏差。
| 维度 | 批处理层 | 流处理层 |
|---|---|---|
| 数据范围 | 全量历史数据 | 最近窗口增量数据 |
| 延迟要求 | 分钟级~小时级 | 秒级~毫秒级 |
| 计算引擎 | Spark / Hive / MapReduce | Flink / Kafka Streams / Storm |
| 特征准确性 | 精确(全量重算) | 近似(可能因乱序或延迟丢失) |
| 存储输出 | 离线特征表(Hive / HBase) | 实时特征存储(Redis / 内存网格) |
| 典型场景 | 日级特征回溯、模型训练样本生成 | 在线推理、实时风控、实时推荐 |
Lambda 架构的流处理层通常采用 Flink 或 Kafka Streams 作为核心引擎,消费 Kafka 中的实时事件流,执行窗口聚合、状态计算、特征变换后,将结果写入实时特征存储。批处理层则定期(如每 6 小时或每天)重跑全量特征计算,覆盖流处理层可能出现的误差。
4.1.2 Kappa 架构
Kappa 架构由 LinkedIn 的 Jay Kreps 提出,核心思想是只用一套流处理管道处理所有数据。所有数据(包括历史数据)都被视为持续到达的事件流,通过重放 Kafka 等消息队列中的历史消息,实现与批处理等效的全量计算。
- 统一数据入口: 所有数据(实时事件 + 历史数据)都写入 Kafka,流处理引擎统一消费。
- 重放机制: 当需要修正特征逻辑或回溯历史特征时,只需重置消费偏移量,从指定时间点重新消费并计算。
- 状态管理: 依赖 Flink 的 Checkpoint / Savepoint 机制或 Kafka Streams 的状态存储,保证计算状态的可恢复性。
- 存储层解耦: 实时特征结果写入 Redis / 内存网格,历史特征结果写入 HBase / Cassandra,但计算逻辑完全复用。
下表对比 Lambda 与 Kappa 在实时特征场景下的关键差异:
| 对比维度 | Lambda 架构 | Kappa 架构 |
|---|---|---|
| 管道数量 | 2 条(批 + 流) | 1 条(纯流) |
| 代码维护 | 两套代码,需对齐逻辑 | 一套代码,复用性强 |
| 历史回溯 | 批处理层重算 | Kafka 重放 + 状态恢复 |
| 数据一致性 | 批处理层最终修正 | 重放保证最终一致 |
| 运维复杂度 | 高(双管道监控、双部署) | 中(单管道,依赖 Kafka 容量) |
| 实时特征延迟 | 秒级(流处理层) | 秒级(纯流处理) |
| 典型选型场景 | 金融风控、广告计费(强一致性) | 实时推荐、用户画像(可容忍短暂偏差) |
4.2 实时特征存储:Redis 与内存网格
实时特征存储是连接流处理引擎与在线推理服务的桥梁。特征计算完成后,需要以极低延迟(微秒~毫秒级)被在线服务读取。传统关系型数据库无法满足这一性能要求,业界主流方案是 Redis 与内存网格(如 Hazelcast、Apache Ignite)。
4.2.1 Redis 作为实时特征存储
Redis 是实时特征存储的首选方案,其全内存数据结构、丰富的数据类型、主从复制与集群模式,使其天然适合高频读写、低延迟的特征存取场景。
| 特征类型 | 推荐 Redis 数据结构 | Key 设计示例 | 说明 |
|---|---|---|---|
| 用户实时统计特征 | Hash | feature:user:{userId} |
存储用户近 1 小时/1 天的点击、曝光、转化计数 |
| 物品实时属性 | String / Hash | item:{itemId}:price |
存储物品实时价格、库存、状态 |
| 滑动窗口聚合 | Sorted Set | window:click:{userId}:1h |
以时间戳为 score,存储窗口内事件,支持范围查询 |
| 实时 TopK 特征 | Sorted Set | topk:hot_items |
存储实时热门物品排名,score 为热度值 |
| 布隆过滤器(去重) | Bloom Filter(Redis 模块) | bloom:impression:{userId} |
判断用户是否已曝光某物品,避免重复推荐 |
| 特征版本控制 | String + 过期时间 | feature:version:user_click |
存储特征计算版本号,配合 TTL 自动过期 |
- 为 Key 添加业务前缀与命名空间,如
feature:user:{userId}:click_cnt_1h,避免 Key 冲突。 - 合理设置 TTL(过期时间),防止无效特征长期占用内存。实时特征通常设置 1 小时~24 小时 TTL。
- 使用 Pipeline 或 Lua 脚本批量读写,减少网络往返次数。
- 对于高并发场景,启用 Redis Cluster 或 Proxy 分片,避免单点瓶颈。
4.2.2 内存网格(Hazelcast / Apache Ignite)
当特征存储需要更强的计算推送能力(如分布式聚合、SQL 查询、ACID 事务)时,内存网格是 Redis 的有力补充。内存网格将数据分布在集群各节点的内存中,支持在数据所在节点直接执行计算逻辑,避免数据移动。
| 对比维度 | Redis | 内存网格(Hazelcast / Ignite) |
|---|---|---|
| 数据模型 | Key-Value + 数据结构 | Key-Value + SQL + 计算 |
| 读写延迟 | 微秒级(单机) | 微秒~毫秒级(分布式) |
| 计算能力 | 弱(Lua 脚本有限) | 强(分布式计算、SQL、机器学习推理) |
| 事务支持 | 有限(MULTI/EXEC) | 完整 ACID(Ignite) |
| 持久化 | RDB / AOF | 原生持久化 + 第三方集成 |
| 典型特征场景 | 实时计数、滑动窗口、缓存 | 实时特征关联查询、特征聚合、在线特征工程 |
| 运维复杂度 | 低(成熟社区) | 中(需管理 JVM 堆外内存) |
4.3 实时特征计算流程
实时特征计算流程从数据源接入到特征写入存储,通常包含以下 6 个核心步骤。每个步骤都可能成为延迟瓶颈,需要针对性优化。
- 数据源接入: 实时事件通过 Kafka / Pulsar / RocketMQ 等消息队列接入。建议对事件进行 schema 校验与字段标准化,避免脏数据进入计算管道。
- 事件解析与过滤: 流处理引擎(Flink / Kafka Streams)反序列化事件,根据业务规则过滤无效事件(如爬虫流量、测试流量)。
- 特征计算: 执行窗口聚合(滑动窗口、滚动窗口)、状态计算(用户累计行为、物品实时热度)、特征变换(归一化、离散化、交叉特征)。
- 特征存储写入: 将计算结果写入 Redis / 内存网格。写入时注意设置 TTL、使用批量写入减少网络开销。
- 特征版本管理: 为每个特征打上时间戳或版本号,便于在线服务识别特征时效性,也便于离线回放时对齐时间窗口。
- 监控与告警: 监控特征计算延迟、写入成功率、存储容量。当延迟超过阈值(如 > 5 秒)或写入失败率升高时,触发告警。
- 状态膨胀: 滑动窗口或用户行为累计特征会随着时间推移占用大量内存。建议设置状态 TTL 或使用 RocksDB 状态后端将冷数据 spill 到磁盘。
- 数据乱序: 实时事件可能因网络延迟或重试而乱序到达。使用 Flink 的 Event Time + Watermark 机制处理乱序,避免窗口聚合结果偏差。
- 特征存储热点: 高活跃用户或热门物品的 Key 可能成为 Redis 热点,导致单节点 CPU 飙高。建议对热点 Key 做本地缓存(如 Guava Cache)或使用 Redis 读写分离架构。
- 特征一致性问题: 实时特征与离线特征可能因计算逻辑差异产生不一致。建议在实时特征管道中保留原始事件日志,便于离线回放时对齐。
4.4 实时特征系统架构总览
综合以上内容,一个完整的实时特征系统架构通常包含数据接入层、流处理层、特征存储层、在线服务层与监控层。下图展示各层之间的协作关系: