架构设计原则:实时特征平台的骨架

做实时特征平台,说白了就是在跟时间赛跑。数据从产生到被消费,中间每一毫秒的延迟都可能让特征失效。我见过不少团队,一上来就堆组件,Kafka、Flink、Redis、HBase 全上,结果跑起来发现要么延迟爆炸,要么数据对不上。嗯,这里的关键不是用了什么技术,而是架构设计有没有想清楚。

我个人习惯,在画第一版架构图之前,先问自己三个问题:

  • 数据从哪里来,到哪里去?
  • 每一层承担什么职责?
  • 挂了怎么办?

想明白这三点,架构就不会跑偏。

总体架构:四层各司其职

实时特征平台的架构,我把它拆成四层:接入层、计算层、存储层、服务层。每一层只做自己的事,层与层之间通过标准接口通信。这样做的好处是,哪一层出了问题,替换起来不伤筋动骨。

核心原则:层间解耦,数据不丢,延迟可控。

下面这张图是我在项目中常用的分层结构,你可以先有个整体印象:

实时特征平台四层架构图 实时特征平台四层架构 接入层 Kafka / Pulsar / HTTP Gateway 计算层 Flink / Spark Streaming / 自定义算子 存储层 Redis / HBase / ClickHouse / 对象存储 服务层 gRPC / RESTful API / 特征聚合服务 数据流从上到下,每一层只依赖下一层的接口,不跨层调用

接入层:数据的第一道关卡

接入层是数据的入口。我建议用消息队列做缓冲,而不是直接让业务系统往计算层怼数据。为什么?因为业务系统的流量是突发的,计算层扛不住毛刺。

我在项目中遇到过这样的情况:双十一大促,业务方直接把 HTTP 请求打到 Flink 上,结果 Flink 背压飙到 100%,整个作业挂了。后来改成 Kafka 接入,流量削峰填谷,稳得很。

我的经验:接入层至少要做到三点——数据不丢(ACK 机制)、顺序保障(分区键设计)、流量控制(限流/降级)。

接入层常用的技术选型:

组件 适用场景 注意事项
Kafka 高吞吐、持久化、多消费者 分区数要提前规划,后期改分区很麻烦
Pulsar 多租户、延迟敏感、存储计算分离 运维成本比 Kafka 高一些
HTTP Gateway 低频写入、调试、管理操作 不适合高并发实时写入

计算层:特征加工的核心

计算层是实时特征平台的大脑。数据进来之后,要在这里完成清洗、聚合、关联、窗口计算等一系列操作。

我个人习惯把计算逻辑分成三类:

  • 无状态计算:比如字段映射、格式转换,每条数据独立处理。
  • 有状态计算:比如滑动窗口内的均值、计数,需要维护中间状态。
  • 多流关联:比如把用户点击流和订单流关联起来,生成完整特征。

你想想看,如果所有逻辑都揉在一个 Flink 作业里,调试和维护会多痛苦。我建议按业务域拆分作业,每个作业只做一件事。比如用户画像一个作业,商品特征一个作业,互不干扰。

避坑指南:我曾经把一个复杂的多流关联逻辑写在一个大作业里,结果状态后端频繁 OOM。后来拆成三个小作业,每个作业只维护自己的状态,问题就解决了。记住,状态是计算层的命门,一定要控制好状态大小。

存储层:特征数据的归宿

计算层产出的特征,最终要落到存储层。存储层的设计直接影响特征查询的延迟和吞吐。

我常用的存储选型策略:

存储类型 典型场景 延迟要求
Redis 在线特征查询,毫秒级响应 < 5ms
HBase 大吞吐写入,历史特征回溯 < 50ms
ClickHouse OLAP 分析,批量特征计算 < 200ms
对象存储 冷数据归档,离线训练 秒级

这里有个关键点:特征数据是有时效性的。比如用户最近 5 分钟的点击行为,过了这个窗口就没用了。我建议在存储层设置 TTL(生存时间),自动清理过期数据,避免存储爆炸。

核心原则:热数据用内存存储(Redis),温数据用 SSD 存储(HBase),冷数据用廉价存储(对象存储)。分层存储能大幅降低成本。

服务层:对外提供特征

服务层是特征平台的出口。模型推理服务、在线推荐系统、实时风控引擎,都通过服务层获取特征。

我建议服务层提供两种接口:

  • 单特征查询:根据实体 ID 和特征名,返回最新值。延迟要求 < 10ms。
  • 批量特征查询:一次请求获取多个实体的多个特征。延迟要求 < 50ms。

服务层还要做好缓存。我见过一个项目,每次请求都穿透到 Redis,结果 Redis 连接数被打满。后来加了一层本地缓存(Caffeine),命中率 80% 以上,Redis 的压力瞬间降下来了。

我的经验:服务层一定要做降级和熔断。如果存储层挂了,服务层可以返回缓存中的旧特征,或者返回默认值。宁可给一个旧值,也不能让上游服务超时。

高可用与容灾设计

实时特征平台最怕什么?最怕数据断流。一旦特征数据中断,模型推理结果就是错的,业务损失不可估量。

我总结了几条高可用设计原则:

  • 接入层高可用:Kafka 集群至少 3 个 Broker,副本因子设为 3。这样坏掉一台,数据不丢。
  • 计算层高可用:Flink 开启 Checkpoint,状态后端用 RocksDB,存储到 HDFS 或 S3。作业挂了可以从 Checkpoint 恢复。
  • 存储层高可用:Redis 用哨兵或集群模式,HBase 用 Region 副本。主从切换要自动完成。
  • 服务层高可用:多副本部署,前面挂负载均衡。健康检查要检测特征查询的延迟,而不是只检测端口是否存活。

容灾设计方面,我建议做异地多活。比如主集群在北京,备集群在上海。正常情况下流量走北京,北京挂了自动切到上海。数据同步用 Kafka MirrorMaker 或者 Flink 双写。

避坑指南:我曾经做过一个容灾方案,只做了存储层的异地备份,没考虑计算层的状态同步。结果主集群挂了,备集群启动后,特征数据从备份恢复,但 Flink 的状态是空的,导致窗口计算全部出错。后来我改成计算层状态也做异地备份,才彻底解决问题。

最后,容灾方案一定要定期演练。我见过不少团队,容灾方案写得漂漂亮亮,但从来没演练过。真到出问题的时候,才发现切换脚本有 bug,或者权限没配好。嗯,这里要记住:不演练的容灾方案,等于没有。

总结一下:实时特征平台的架构设计,核心是分层、解耦、容灾。每一层做好自己的事,层与层之间通过标准接口通信。高可用不是靠一个组件,而是靠整个链路的设计。你想想看,如果每一层都做了冗余和容错,平台还会轻易挂掉吗?