2、核心架构总览:实时特征计算的整体架构图、数据流转路径、核心组件

聊实时特征计算,很多人一上来就扎进代码细节。我个人习惯是,先退一步,把整个架构图在脑子里画清楚。你想想看,如果连数据从哪来、经过谁、存到哪、怎么被服务调用都说不清,那后面写再多代码也是白搭。

这一章,我就带你把这个架构图拆开揉碎了讲。我会把每个核心组件、每条数据流转路径都讲透。嗯,这里要注意,我不会给你画那种花里胡哨的“万能架构图”,而是基于我在生产环境中反复验证过的一套标准分层架构。

2.1 整体架构图:四层模型

实时特征计算架构,说白了就是四个层次:采集层、计算层、存储层、服务层。数据从业务系统流出,经过这四层加工,最终变成高可用的特征服务。

下面这张图,是我在项目里经常用来跟团队对齐的架构总览图。你可以把它当作整个课程的地图。

实时特征计算四层架构总览图 实时特征计算四层架构总览 数据源层 业务数据库(MySQL/Oracle) | 消息队列(Kafka/Pulsar) | 日志文件(Flume/Filebeat) 采集层 CDC(Debezium/Canal) | 消息采集(Kafka Connect) | 日志采集(Logstash) 计算层 Flink(CEP/ProcessFunction) | Spark Streaming | 自定义算子 存储层 在线存储(Redis/HBase) | 离线存储(HDFS/ClickHouse) | 消息队列(Kafka) 服务层 特征API(gRPC/RESTful) | 特征缓存(Guava/Caffeine) | 特征治理(降级/熔断) 数据流方向:业务系统 → 采集 → 计算 → 存储 → 服务调用,全程毫秒级延迟

这张图你看懂了吗?数据从底部的业务系统出发,一路向上,经过采集、计算、存储,最终在服务层对外暴露。每一层都有明确的职责边界,这也是我反复强调的——分层清晰,才能各自优化。

2.2 数据流转路径:从源头到服务

光有架构图还不够,你得知道数据具体是怎么走的。我把它拆成四个阶段来讲。

2.2.1 阶段一:数据产生与采集

数据源头通常有三个:

  • 业务数据库:用户下单、支付、修改资料等行为,都记录在 MySQL 或 Oracle 里。
  • 消息队列:比如用户点击、浏览、搜索等实时事件,通过 Kafka 或 Pulsar 发出。
  • 日志文件:服务器日志、应用日志,通过 Flume 或 Filebeat 采集。

采集层要做的事,就是把这三类数据统一成标准格式,送入计算层。我常用的方案是:

  • 数据库变更用 Debezium 做 CDC(Change Data Capture),直接监听 binlog。
  • 消息队列事件用 Kafka Connect 直接接入。
  • 日志文件用 Logstash 解析后写入 Kafka。
我的经验: 采集层最容易出问题的是数据格式不一致。我曾经在一个项目中,CDC 吐出的 JSON 字段名是驼峰,日志采集吐出的却是下划线,导致计算层解析报错。我的建议是,在采集层就统一做一次 Schema 标准化,后面会省很多事。

2.2.2 阶段二:实时计算

数据进入计算层后,核心任务就是特征加工。这里我以 Flink 为例,讲一下典型的数据流转:

  1. 数据接入:Flink 从 Kafka 消费数据,反序列化为 POJO 或 Avro 对象。
  2. 窗口聚合:比如统计过去 5 分钟的用户点击次数,用 TumblingEventTimeWindows 实现。
  3. 状态计算:比如计算用户累计消费金额,用 ValueState 或 MapState 维护中间结果。
  4. 特征拼接:将多个维度的特征(用户画像、商品属性、上下文信息)拼接成一条完整的特征记录。
  5. 结果输出:将计算好的特征写入存储层。

这里有个关键点——计算延迟。我见过很多团队把计算搞得太重,导致延迟飙升。记住,实时特征计算的核心是“快”,复杂的模型推理应该交给专门的推理服务,不要放在特征计算链路里。

2.2.3 阶段三:特征存储

计算好的特征,需要存起来供服务层调用。存储策略通常分三种:

存储类型 典型组件 适用场景 延迟要求
在线存储 Redis、HBase 高并发、低延迟的特征查询 < 10ms
离线存储 HDFS、ClickHouse 特征回溯、离线训练 秒级到分钟级
消息队列 Kafka 特征分发、下游订阅 毫秒级

我个人习惯是,在线存储用 Redis Cluster,因为它的读写性能最稳定。HBase 虽然也能扛高并发,但运维成本高不少。离线存储用 ClickHouse,查询速度快,适合做特征分析。

避坑指南: 我曾经在一个项目中,把所有特征都塞进 Redis,结果内存爆了。后来才意识到,不是所有特征都需要在线存储。高频特征(如用户实时行为)放 Redis,低频特征(如用户历史画像)放 HBase 或 ClickHouse,这才是合理的分层。

2.2.4 阶段四:特征服务

最后一步,是让业务系统能方便地拿到特征。服务层通常暴露两种接口:

  • 实时查询接口:业务系统传入实体 ID(如用户ID、商品ID),服务层从存储中拉取特征并返回。一般用 gRPC 或 RESTful API。
  • 批量拉取接口:用于离线训练场景,一次性拉取大量实体的特征。

服务层还需要考虑高可用和降级。比如,当 Redis 挂了,服务层应该能自动降级到 HBase 读取,或者返回缓存中的旧特征。嗯,这里要注意,降级策略一定要在架构设计时就规划好,别等到线上出问题了再临时想。

2.3 核心组件详解

上面讲了数据流转,现在我把四个核心组件单独拎出来,讲一下每个组件的关键选型和设计要点。

2.3.1 采集组件

采集组件的核心能力是实时性和可靠性。我常用的组合是:

  • Debezium:监听 MySQL binlog,延迟在毫秒级,支持断点续传。
  • Canal:阿里开源的 MySQL binlog 解析工具,性能稳定,但社区活跃度不如 Debezium。
  • Kafka Connect:用于从 Kafka 到其他系统的数据搬运,自带 Source 和 Sink 插件。

选型建议:如果你的技术栈偏 Java/Spring,Canal 集成更方便;如果偏云原生或微服务,Debezium 更合适。

2.3.2 计算组件

计算组件是实时特征计算的核心。目前主流的选择是 Flink,原因有三:

  1. 真正的流处理:Flink 是原生的流计算引擎,不像 Spark Streaming 是微批处理。
  2. 强大的状态管理:支持 RocksDB 状态后端,可以处理 TB 级别的状态数据。
  3. Exactly-Once 语义:保证特征计算不丢不重,这对金融场景尤其重要。

当然,如果你们的场景比较简单,比如只是做简单的窗口聚合,Spark Streaming 也够用。但如果你要做复杂的事件关联或 CEP(复杂事件处理),Flink 是唯一的选择。

2.3.3 存储组件

存储组件的选型,取决于你的读写模式:

  • 读多写少:用 Redis,配合 Lua 脚本做原子操作。
  • 写多读少:用 HBase,利用其 LSM-Tree 结构优化写入性能。
  • 需要范围查询:用 TiKV 或 CockroachDB,支持分布式事务和 SQL。

我个人的经验是,不要试图用一个存储解决所有问题。我曾经见过一个团队,用 Redis 存了所有特征,结果因为 Redis 的单线程模型,高并发下延迟飙升。后来他们拆成了 Redis + HBase 双层存储,问题就解决了。

2.3.4 服务组件

服务层的关键是低延迟和高可用。我常用的技术栈是:

  • API 网关:用 Nginx 或 Kong 做负载均衡和限流。
  • 特征服务:用 Spring Boot 或 Go 编写,内部集成 Redis 客户端和 HBase 客户端。
  • 本地缓存:用 Guava Cache 或 Caffeine,缓存热点特征,减少对 Redis 的冲击。

这里有个小技巧:特征服务一定要做本地缓存。我做过压测,加了 Caffeine 本地缓存后,QPS 从 5000 提升到了 30000,延迟从 5ms 降到了 0.5ms。效果非常明显。

2.4 架构设计的关键原则

最后,我总结几条架构设计的原则,这些都是我在项目中踩过坑后总结出来的:

原则一:分层解耦

每一层只做一件事。采集层只负责数据接入,计算层只负责特征加工,存储层只负责数据持久化,服务层只负责对外暴露。不要跨层调用,不要在一个组件里做所有事。

原则二:数据闭环

特征计算链路要能自闭环。比如,计算层消费 Kafka 数据,处理完后再写入 Kafka,服务层从 Kafka 消费特征。这样即使某个组件挂了,数据也不会丢。

原则三:可观测性

每一层都要有完善的监控和日志。我习惯在采集层记录数据延迟,在计算层记录处理延迟和吞吐量,在存储层记录读写延迟,在服务层记录接口响应时间。这样出了问题,能快速定位到是哪一层的问题。