3. 机器学习数据平台集成架构

机器学习项目失败的原因,往往不在算法本身,而在数据链路。一个可复用的 ML 数据平台,需要把"数据从哪来、怎么进来、放在哪、怎么算、怎么用"这五个问题拆成清晰的层次,让每一层只解决一类问题,层与层之间通过稳定的接口衔接。本节讨论的正是这种分层设计:数据源层、采集层、存储层、计算层、服务层,以及 Elastic 在这条链路中究竟站在什么位置。

五层架构的职责边界

分层的第一价值是"变更隔离"。当上游数据源从 MySQL 换成 Kafka,理想情况下只应改动采集层;当特征计算从批处理改为流式,只应改动计算层。如果各层职责混杂,任何一处变动都会引发全链路返工。

  • 数据源层:业务库(MySQL、PostgreSQL)、日志文件、消息队列(Kafka、Pulsar)、对象存储(S3、OSS)、第三方 API、埋点 SDK。这一层的关键属性是"数据产生方式"——是事务写入、追加日志,还是定时拉取。
  • 采集层:负责把源数据搬运到平台内,并完成格式统一、时间戳对齐、脏数据过滤。常见组件包括 Logstash、Filebeat、Fluent Bit、Kafka Connect、Flink CDC、DataX。采集层要回答的核心问题是"全量还是增量、实时还是批量、是否允许丢数据"。
  • 存储层:按用途分为原始区(Raw/ODS)、明细区(DWD)、汇总区(DWS)和特征区(Feature Store)。技术选型上,对象存储承担低成本冷数据,Elasticsearch 承担检索与聚合,ClickHouse/Doris 承担 OLAP 分析,Redis 承担在线特征低延迟读取。
  • 计算层:批处理(Spark、Hive)、流处理(Flink、Spark Streaming)、特征工程、模型训练与调优。计算层消费存储层的数据,产出模型文件和特征表,回写到存储层或直接推送到服务层。
  • 服务层:模型推理 API、特征在线查询、A/B 实验分流、监控告警。服务层直接面对业务调用方,对延迟、可用性、版本管理要求最高。

数据在层间的流动形态

理解分层不能只看静态的方框,更要看数据在层间"变成什么"。以一条电商用户行为日志为例:在数据源层它是带业务字段的 JSON 事件;进入采集层后被补齐 ingest_time、清洗掉非法字段,转为统一 schema;落到存储层时,原始 JSON 存入对象存储做归档,同时写入 Elasticsearch 供检索;计算层从 Elasticsearch 或数仓读取,按用户维度聚合出"近 7 天点击品类分布"等特征;服务层则把特征以毫秒级响应返回给推荐模型。同一份数据在五层中形态不同、时效不同、精度要求也不同,这正是分层存在的意义。

数据源层 MySQL 业务库 Kafka 消息 日志文件 对象存储 埋点 SDK 原始事件流 采集层 Filebeat / Logstash Flink CDC Kafka Connect 清洗 / 格式统一 时间戳对齐 统一 schema 存储层 对象存储 (Raw/ODS) Elasticsearch ClickHouse / Doris Redis 在线特征 明细区 DWD 检索 + 聚合 汇总区 DWS 特征区 Feature Store 读取训练 / 特征数据 计算层 Spark 批处理 Flink 流处理 特征工程 模型训练 / 调优 模型 + 特征上线 服务层 推理 API 在线特征查询 A/B 实验分流 监控告警 Elastic 定位 存储层检索 + 采集层接入

Elastic 在分层中的真实定位

很多团队把 Elasticsearch 简单归为"存储层的一个数据库",这低估了它的作用,也容易导致架构误用。从分层视角看,Elastic 实际上横跨了三个位置:

第一,采集层的接入端。Filebeat、Logstash、Elastic Agent 本身就是采集层的主流组件,它们把日志、指标、事件统一送入 Elasticsearch,同时完成字段解析、时间戳处理和基础清洗。在这个意义上,Elastic 生态提供了采集层的一部分能力。

第二,存储层的检索与聚合引擎。Elasticsearch 擅长的是"带条件的快速检索 + 多维聚合",例如按用户 ID 查最近行为、按时间窗口统计点击量、按品类做嵌套聚合。它不适合承担大规模明细数据的全量扫描和复杂 JOIN,那类工作应交给 ClickHouse 或 Spark。把 Elasticsearch 当作唯一存储,往往在数据量增长后遇到分片膨胀和聚合延迟问题。

第三,服务层的特征读取后端。在线推理需要毫秒级读取用户画像和近期行为特征,Elasticsearch 的 _mget 和 _search 可以承担这一角色,尤其是当特征本身带有文本检索需求时(如"用户最近搜索过的商品标题")。但纯数值型、超低延迟的特征,Redis 仍是更优选择。

一个可落地的分层参数示例

下面给出一组教学示例参数,用于说明各层在真实项目中如何设定关键指标。这些数值属于合理假设,实际项目需根据数据量和 SLA 调整。

层次关键组件示例参数判断依据
数据源层Kafka Topic3 分区,副本数 2峰值 5000 条/秒,单分区约 1700 条/秒
采集层Logstash Pipelinebatch.size=125,workers=4消费延迟 < 2 秒
存储层Elasticsearch Index5 主分片,1 副本,rollover 50GB单分片 10–30GB 为健康区间
计算层Spark 特征任务executor 4 个,每核 4GB7 天窗口特征计算 < 15 分钟
服务层推理 APIP99 < 80ms特征查询 + 模型推理总耗时

分层设计中最容易犯的三个错误

错误一:采集层做重计算。有人在 Logstash 里写复杂 Ruby 脚本做特征聚合,导致采集吞吐骤降。采集层只应做轻量清洗和格式转换,重计算交给计算层。

错误二:存储层职责重叠。同一份数据既写 Elasticsearch 又写 ClickHouse,却没有明确"谁服务检索、谁服务分析",结果两边数据不一致,排查成本极高。正确做法是按查询模式分工:检索走 Elasticsearch,OLAP 走 ClickHouse,并明确同步链路和延迟容忍度。

错误三:服务层直接查原始索引。推理 API 直接对原始日志索引做聚合查询,随着数据增长延迟飙升。应通过计算层预生成特征索引或特征表,服务层只读特征结果,不碰原始数据。

判断 Elastic 是否用对了位置,可以问三个问题:这个查询是"按条件找文档"还是"全量扫描做复杂关联"?前者适合 Elastic,后者不适合。这个数据是"需要被检索"还是"只需要被计算"?前者放 Elastic,后者放数仓或对象存储。这个读取是"在线毫秒级"还是"离线分钟级"?在线读特征索引,离线读原始数据。

分层不是目的,而是让每一层的变化不波及全局。Elastic 在这套架构中的价值,不在于替代数仓或计算引擎,而在于把"检索、聚合、在线特征读取"这三件事做到足够快、足够灵活。认清它的边界,比盲目扩大它的职责更重要。