2、核心架构总览:实时特征计算的整体架构图、数据流转路径、核心组件
聊实时特征计算,很多人一上来就扎进代码细节。我个人习惯是,先退一步,把整个架构图在脑子里画清楚。你想想看,如果连数据从哪来、经过谁、存到哪、怎么被服务调用都说不清,那后面写再多代码也是白搭。
这一章,我就带你把这个架构图拆开揉碎了讲。我会把每个核心组件、每条数据流转路径都讲透。嗯,这里要注意,我不会给你画那种花里胡哨的“万能架构图”,而是基于我在生产环境中反复验证过的一套标准分层架构。
2.1 整体架构图:四层模型
实时特征计算架构,说白了就是四个层次:采集层、计算层、存储层、服务层。数据从业务系统流出,经过这四层加工,最终变成高可用的特征服务。
下面这张图,是我在项目里经常用来跟团队对齐的架构总览图。你可以把它当作整个课程的地图。
这张图你看懂了吗?数据从底部的业务系统出发,一路向上,经过采集、计算、存储,最终在服务层对外暴露。每一层都有明确的职责边界,这也是我反复强调的——分层清晰,才能各自优化。
2.2 数据流转路径:从源头到服务
光有架构图还不够,你得知道数据具体是怎么走的。我把它拆成四个阶段来讲。
2.2.1 阶段一:数据产生与采集
数据源头通常有三个:
- 业务数据库:用户下单、支付、修改资料等行为,都记录在 MySQL 或 Oracle 里。
- 消息队列:比如用户点击、浏览、搜索等实时事件,通过 Kafka 或 Pulsar 发出。
- 日志文件:服务器日志、应用日志,通过 Flume 或 Filebeat 采集。
采集层要做的事,就是把这三类数据统一成标准格式,送入计算层。我常用的方案是:
- 数据库变更用 Debezium 做 CDC(Change Data Capture),直接监听 binlog。
- 消息队列事件用 Kafka Connect 直接接入。
- 日志文件用 Logstash 解析后写入 Kafka。
2.2.2 阶段二:实时计算
数据进入计算层后,核心任务就是特征加工。这里我以 Flink 为例,讲一下典型的数据流转:
- 数据接入:Flink 从 Kafka 消费数据,反序列化为 POJO 或 Avro 对象。
- 窗口聚合:比如统计过去 5 分钟的用户点击次数,用 TumblingEventTimeWindows 实现。
- 状态计算:比如计算用户累计消费金额,用 ValueState 或 MapState 维护中间结果。
- 特征拼接:将多个维度的特征(用户画像、商品属性、上下文信息)拼接成一条完整的特征记录。
- 结果输出:将计算好的特征写入存储层。
这里有个关键点——计算延迟。我见过很多团队把计算搞得太重,导致延迟飙升。记住,实时特征计算的核心是“快”,复杂的模型推理应该交给专门的推理服务,不要放在特征计算链路里。
2.2.3 阶段三:特征存储
计算好的特征,需要存起来供服务层调用。存储策略通常分三种:
| 存储类型 | 典型组件 | 适用场景 | 延迟要求 |
|---|---|---|---|
| 在线存储 | Redis、HBase | 高并发、低延迟的特征查询 | < 10ms |
| 离线存储 | HDFS、ClickHouse | 特征回溯、离线训练 | 秒级到分钟级 |
| 消息队列 | Kafka | 特征分发、下游订阅 | 毫秒级 |
我个人习惯是,在线存储用 Redis Cluster,因为它的读写性能最稳定。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,原因有三:
- 真正的流处理:Flink 是原生的流计算引擎,不像 Spark Streaming 是微批处理。
- 强大的状态管理:支持 RocksDB 状态后端,可以处理 TB 级别的状态数据。
- 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 消费特征。这样即使某个组件挂了,数据也不会丢。
原则三:可观测性
每一层都要有完善的监控和日志。我习惯在采集层记录数据延迟,在计算层记录处理延迟和吞吐量,在存储层记录读写延迟,在服务层记录接口响应时间。这样出了问题,能快速定位到是哪一层的问题。