
1. 从喂数据和缩算力这两个动作说起九月这波更新里有两件事被反复提起一是数据要能实时喂给模型二是算力要能缩到零。听起来像两个独立的方向其实它们指向同一个目标——让 AI 负载真正具备生产环境的弹性。我过去一年在几个推理服务项目里反复折腾过这两件事踩过的坑比想象中多今天把完整的思路和实操细节摊开讲。先说清楚这篇内容适合谁看。如果你正在把离线训练的模型往在线服务迁移或者已经在跑推理服务但被数据延迟高和GPU 空转烧钱两头夹击那这篇就是写给你的。如果你只是好奇 AI 负载在生产环境里长什么样也能从里面看到一套完整的工程取舍逻辑。核心关键词会围绕Apache Fluss、Kubernetes、HPA、Flink、AI这几个展开但不会堆术语每个技术选择我都会解释为什么。我先把结论摆出来实时喂数据和算力缩到零本质上是一对矛盾。数据要实时意味着链路不能断、缓冲不能停算力要缩到零意味着资源要能彻底释放、冷启动要足够快。解决这个矛盾的关键不在于选哪个单一工具而在于把数据流和算力调度这两层解耦让它们各自独立伸缩。下面按这个思路一层层拆。2. 实时喂模型这件事卡点从来不在模型本身2.1 为什么实时两个字这么贵很多人第一次做实时推理会下意识觉得瓶颈在模型推理速度。实际跑下来会发现模型推理往往不是最慢的那一环真正拖后腿的是数据从产生到进入模型输入之间的那段链路。我做过一个统计在一个典型的推荐场景里从用户行为产生到特征拼接完成端到端延迟里模型推理只占 15% 左右剩下 85% 全花在数据采集、传输、特征计算和格式转换上。这就是为什么实时喂模型这件事重点根本不在模型侧而在数据侧。你要解决的是数据怎么以最低延迟、最高可靠性地送到模型输入口。这里有两个硬指标——端到端延迟和数据新鲜度。延迟好理解新鲜度指的是模型拿到的特征是不是刚刚发生的而不是五分钟前的快照。很多团队延迟做得不错但新鲜度很差模型拿到的还是过期特征效果自然上不去。2.2 流式链路里最容易被忽略的回压问题实时数据链路一旦跑起来最怕的不是慢是回压backpressure。上游数据产生速度超过下游消费速度时如果链路没有合理的缓冲和降级机制整个管道会像堵住的下水道一样压力一路往回顶最后把数据源都拖垮。我在一个项目里遇到过这种情况Flink 作业消费 Kafka 数据做特征计算某天上游埋点量突然翻了三倍Flink 的消费 lag 瞬间涨到几百万条。当时第一反应是加并行度但加了之后发现下游写特征库的连接池被打满反而更慢。后来才想明白问题不在并行度在于特征库的写入吞吐才是真正的瓶颈。这个坑让我记住一个原则流式链路的容量规划永远要看最慢的那一环而不是平均速度。处理回压的常见手段有这么几种我按实际效果排个序有界缓冲 背压感知给每个环节设置明确的缓冲上限超过就触发降级而不是无限堆积。Flink 自带的背压监控就是干这个的。分层降级实时特征算不过来时自动降级到用最近一次的快照特征保证服务不挂。这个策略在推荐和风控场景里特别实用。异步化写入特征库写入用异步批量方式避免单条写入把连接池占满。批量大小和超时时间需要根据实际吞吐调。提示回压监控一定要做成可观测的指标不能等出事了才去查。我习惯把每个环节的 lag、缓冲水位、写入耗时都打到监控面板上阈值告警设得比实际容量低 30%留出反应时间。2.3 Apache Fluss 在这条链路里扮演什么角色Apache Fluss 是这两年被讨论比较多的流式存储项目它的定位是为实时分析而生的流式存储层。和 Kafka 相比Fluss 更强调流批一体的存储抽象和低延迟的列式读取。在实时喂模型这个场景里Fluss 的价值主要体现在两点一是它能作为特征数据的统一存储层实时写入和批量回溯用同一套接口二是它的列式存储对特征读取这种只取部分列的场景更友好。我实际用下来的感受是Fluss 适合放在特征存储这一层而不是替代消息队列。典型的链路是业务数据先进消息队列Flink 消费后做特征计算算完的特征写入 Fluss模型服务从 Fluss 读取特征。这样做的理由是消息队列负责传输Fluss 负责存储和查询各司其职。如果直接把 Fluss 当消息队列用它的写入吞吐和生态成熟度目前还比不上专门的消息中间件。选型的时候有个判断标准如果你的特征读取模式是按 key 查最新值那 Fluss 的列式读取优势明显如果是按时间窗口扫描一批数据那可能传统的时序数据库或者对象存储更合适。这个判断我踩过坑一开始没想清楚把两种模式混在一起结果查询性能怎么调都上不去。3. 算力缩到零难点在缩之后的冷启动3.1 HPA 缩容到零为什么在 AI 场景里特别难Kubernetes 的 HPAHorizontal Pod Autoscaler大家都不陌生但默认的 HPA 有个硬限制最小副本数不能是零。也就是说你可以从 10 个副本缩到 1 个但没法缩到 0 个。对于 GPU 推理服务来说1 个副本和 0 个副本的成本差距可能是每月几千到几万块这个差距在业务低峰期非常可观。为什么 HPA 默认不支持缩到零因为缩到零之后流量来了没有实例能接必须有一个唤醒机制。Kubernetes 生态里解决这个问题的方案主要有几类Knative Serving的 scale-to-zero、KEDA的事件驱动伸缩、以及一些自研的网关层唤醒逻辑。这几类方案我都试过各有适用场景。Knative 的优势是集成度高和 Istio 配合能做精细的流量管理但它的冷启动链路比较长从零到能处理请求通常需要几秒到十几秒。KEDA 更轻量它通过外部指标比如消息队列积压数来触发伸缩缩到零和从零拉起都更直接适合和现有 HPA 体系结合。自研网关唤醒则最灵活但维护成本高除非有特殊需求一般不建议。3.2 冷启动时间到底花在哪算力缩到零之后最大的体验问题就是冷启动。我实测过一个基于 GPU 的推理服务从零副本到第一个请求返回总耗时大约 25 秒。拆开看时间分布大致是这样阶段耗时占比主要影响因素调度与节点准备20%节点池是否有空闲 GPU、镜像是否已缓存容器启动15%镜像大小、启动脚本复杂度模型加载45%模型体积、加载方式、是否用内存映射预热与首次推理20%是否做 warmup、CUDA 初始化可以看到模型加载是大头。优化冷启动重点要放在模型加载上。几个有效的手段用内存映射方式加载模型文件避免全量读入内存把模型文件放在节点本地缓存或者高速网络存储上减少下载时间启动脚本里做最小化 warmup只跑一次前向传播把 CUDA 上下文初始化好不要跑完整测试集。注意warmup 不是越多越好。我见过有团队在启动时跑几百条样本做 warmup结果冷启动时间直接翻倍。正确的做法是只做必要的初始化把真正的预热交给流量渐进的阶段。3.3 缩到零和保持最小副本的取舍不是所有服务都适合缩到零。判断标准其实很简单冷启动时间是否小于业务能容忍的等待时间。如果业务是面向用户的实时交互用户等 25 秒是不可接受的那就不适合缩到零应该保持一个最小副本数用 HPA 在 1 到 N 之间伸缩。如果业务是离线批处理或者异步任务任务提交后等几十秒无所谓那缩到零就很划算。我一般会按这个矩阵来决策高实时 高成本敏感保持最小副本 1用 HPA 弹性伸缩配合请求排队和限流。高实时 成本不敏感保持固定副本数不做缩容追求极致稳定。低实时 高成本敏感缩到零用 KEDA 或 Knative 做事件驱动唤醒。低实时 成本不敏感定时伸缩按业务规律提前扩容。这个矩阵看起来简单但实际决策时经常被忽略的是高实时的定义。很多团队嘴上说高实时实际业务容忍度是几秒那就完全可以走缩到零加预热池的方案。关键是把真实容忍度量化出来而不是凭感觉。4. 把数据流和算力调度解耦才是生产级的做法4.1 为什么耦合在一起会出问题早期我做实时推理服务习惯把数据消费和模型推理放在同一个进程里Flink 作业里直接加载模型消费到数据就推理推理完写结果。这种架构在 demo 阶段很爽部署简单链路短。但一到生产环境就暴露问题数据消费的伸缩需求和模型推理的伸缩需求完全不一样。数据量大时想加消费并行度但加并行度意味着要加载更多模型副本GPU 成本直接翻倍反过来业务低峰想缩 GPU但一缩就把数据消费也缩了数据 lag 立刻涨起来。这就是耦合的代价。数据流的伸缩维度是吞吐算力调度的伸缩维度是请求量两者节奏不同硬绑在一起必然互相拖累。解耦的思路是让数据流层独立伸缩算力层独立伸缩中间用一个缓冲层衔接。4.2 解耦后的典型架构长什么样解耦后的架构大致分三层第一层是数据接入与处理层用 Flink 或者类似的流处理引擎负责消费原始数据、做特征计算、写入特征存储。这一层的伸缩完全由数据吞吐驱动和 GPU 无关可以放心用 CPU 节点做弹性伸缩。第二层是特征存储与缓冲层用 Fluss 或者 Redis 这类低延迟存储负责存放算好的特征同时作为算力层的数据源。这一层的关键是读写分离和容量规划要能扛住数据流层的写入峰值和算力层的读取峰值。第三层是模型推理层用 Kubernetes 部署推理服务从特征存储读取特征做推理。这一层的伸缩由请求量驱动可以独立缩到零或者扩到 N。三层之间通过明确的接口通信任何一层的伸缩都不会直接影响其他层的稳定性。这个架构我第一次落地时最担心的是层与层之间的延迟叠加实测下来只要特征存储的读取延迟控制在毫秒级整体端到端延迟增加不到 10%。4.3 解耦之后监控和告警要重新设计解耦带来的一个副作用是问题定位变复杂了。以前一个进程里出问题看一个日志就行现在跨三层得有一套完整的链路追踪。我的做法是给每个请求打一个 trace id从数据接入层一直传到推理层任何一层出问题都能顺着 trace id 找到完整链路。监控指标也要分层设计数据接入层消费 lag、处理吞吐、写入成功率。特征存储层读写延迟、命中率、容量水位。推理层请求延迟、GPU 利用率、冷启动次数、副本数变化。告警阈值我一般按正常值的 1.5 倍来设但冷启动次数这个指标例外它应该设成突增告警因为冷启动频繁说明伸缩策略有问题需要人工介入调整。5. 实操中那些文档不会写的细节5.1 Flink 作业的 checkpoint 配置直接影响数据新鲜度Flink 的 checkpoint 机制是保证 exactly-once 语义的核心但 checkpoint 间隔设得太长会影响数据新鲜度。我遇到过一个问题checkpoint 间隔设了 5 分钟结果故障恢复时要从 5 分钟前的状态重放这 5 分钟的数据要么重复处理要么丢失特征新鲜度直接受影响。后来我把 checkpoint 间隔调到 30 秒同时开启非对齐 checkpointunaligned checkpoint减少 checkpoint 期间的阻塞。调整后故障恢复的数据重放窗口从 5 分钟降到 30 秒新鲜度问题基本解决。代价是 checkpoint 频率高了对存储的写入压力变大需要相应调整存储的 IO 能力。提示checkpoint 间隔不是越短越好。太短会导致频繁的 checkpoint 开销影响正常处理吞吐。我的经验值是 30 秒到 1 分钟之间具体看业务对新鲜度的要求。5.2 HPA 的指标选择比阈值设置更重要很多人调 HPA 只关注阈值比如 CPU 到 70% 就扩容。但在 AI 推理场景里CPU 利用率往往不是好指标因为推理瓶颈通常在 GPU 或者内存带宽上。用 CPU 做 HPA 指标经常出现 CPU 还没到阈值但 GPU 已经打满的情况扩容滞后。更合适的指标是请求队列长度或者GPU 利用率。Kubernetes 原生 HPA 不直接支持 GPU 指标需要借助 Prometheus Adapter 把 GPU 指标暴露成自定义指标。配置起来有点麻烦但效果比用 CPU 好很多。我实测下来用请求队列长度做 HPA 指标扩容响应时间比用 CPU 快 40% 左右。5.3 缩到零之后的惊群问题缩到零之后流量突然来了如果同时有大量请求触发扩容可能出现惊群——所有请求都在等第一个副本起来第一个副本起来后瞬间被压垮。这个问题在 Knative 里比较常见解决手段是请求排队 限流。在网关层做一层队列控制同时进入的请求数等副本起来后逐步放量。我在一个项目里用 Istio 的限流功能配合 Knative 做这个事效果不错。具体配置是给每个服务设一个并发上限超过的请求排队等待队列满了直接返回 503 让客户端重试。这样虽然部分请求会失败但整体服务不会雪崩。5.4 模型版本更新时的灰度策略实时推理服务更新模型版本时如果直接全量替换一旦新模型有问题影响面很大。我的做法是双版本并行 流量灰度。新模型先起一个副本接 5% 流量观察一段时间指标正常后再逐步放量。这个过程中特征存储层要保证两个版本都能读到兼容的特征格式所以特征 schema 的版本管理很重要。特征 schema 变更时我一般要求向后兼容至少两个版本。也就是说新模型上线时特征存储里要同时保留新旧两种格式等所有旧模型都下线后再清理。这个策略会增加存储成本但能避免模型更新时的特征不兼容问题。6. 这套方案跑在生产里的真实收益6.1 成本账缩到零到底省了多少我在一个中等规模的推理服务上做过对比。服务有 8 个 GPU 副本业务高峰期在白天夜间流量只有白天的 10% 左右。改造前夜间保持 8 个副本GPU 利用率不到 15%。改造后夜间缩到 1 个副本白天按需扩到 8 个。按 GPU 小时成本算夜间 12 小时节省了 7 个副本的成本一个月下来节省的费用相当可观。但要注意缩到零不是没有成本。冷启动带来的请求延迟增加、频繁伸缩对调度器的压力、以及为了支持快速冷启动而做的镜像优化和缓存投入这些都是隐性成本。我的经验是只有当低峰期持续时间超过 4 小时缩到零的收益才明显。如果低峰期只有一两个小时频繁伸缩的开销可能抵消掉节省的成本。6.2 稳定性账解耦之后故障影响面变小了解耦之前一次 Flink 作业重启会导致整个推理服务不可用因为数据和推理在一个进程里。解耦之后Flink 重启只影响数据接入层推理层继续用特征存储里的旧数据服务虽然特征新鲜度下降但服务不中断。这个变化在故障演练里体现得很明显改造后服务的可用性指标从 99.5% 提升到 99.9% 以上。不过解耦也带来新的故障模式比如特征存储层挂了三层全挂。所以特征存储层的高可用要做足我一般要求至少三副本 自动故障转移读写分离避免单点。6.3 一个具体的调优案例最后分享一个具体的调优过程。有个推理服务端到端 P99 延迟一直在 800ms 左右业务要求降到 500ms 以内。排查下来延迟主要花在特征读取上每次推理要读 200 多个特征逐个读取耗时很长。优化手段是批量读取 本地缓存。把 200 个特征按 key 分组一次批量读取减少网络往返同时在推理服务本地加一层 LRU 缓存缓存热点特征。改造后特征读取耗时从 400ms 降到 80ms端到端 P99 降到 450ms达标。这个案例说明一个道理实时喂模型这件事优化空间往往在数据读取方式上而不是模型本身。批量、缓存、预取这些经典手段在 AI 场景里依然有效。7. 几个我反复踩过的坑和对应的解法7.1 特征存储的容量规划不能按平均值算我吃过一次亏特征存储的容量按日均写入量规划结果大促当天写入量是日均的 5 倍存储直接写满整个链路堵死。后来改成按峰值写入量的 1.5 倍规划容量并且设置自动扩容和过期清理策略。特征数据一般有时效性超过一定时间的旧特征可以清理掉这样能大幅降低存储压力。7.2 HPA 的冷却时间设置太短会导致副本数震荡HPA 有扩容和缩容的冷却时间cooldown period默认缩容冷却时间比较长但扩容冷却时间短。如果业务流量波动频繁扩容冷却时间太短会导致副本数反复上下既浪费资源又影响稳定性。我的做法是把扩容冷却时间设成 3 分钟缩容冷却时间设成 10 分钟让扩容更灵敏、缩容更保守。7.3 冷启动预热池不是万能的为了缓解冷启动有些团队会维护一个预热池保持几个已启动但不接流量的副本。这个方案确实能降低冷启动延迟但预热池本身也占资源如果预热池太大缩到零的收益就没了。我的经验是预热池大小控制在总副本数的 10% 以内并且只在业务可预测的高峰前启用平时关掉。7.4 跨层 trace id 传递要统一格式解耦架构里trace id 从数据接入层传到推理层如果各层用的格式不一致链路追踪就断了。我要求所有层统一用 W3C Trace Context 标准格式并且在网关层做一次格式转换确保兼容。这个细节看起来小但排查问题时能省大量时间。8. 写在最后的一点个人体会这套方案我在三个项目里落地过每次都有新的调整。最大的体会是实时喂数据和算力缩到零这两件事单独做都不难难的是让它们共存。共存的钥匙是解耦而解耦的代价是复杂度上升。所以要不要做取决于业务对成本和实时性的真实需求而不是技术上的可能性。如果让我给一个起步建议我会说先把数据链路和推理服务拆开哪怕推理服务暂时不缩到零光是拆开这一件事就能让伸缩策略清晰很多。等数据链路稳定了再逐步引入缩到零和冷启动优化。一步到位容易翻车分步走更稳。另外监控和告警的投入不能省。解耦架构的问题定位比单体架构难没有完善的监控出了问题只能靠猜。我一般会在项目初期就把监控面板搭好哪怕功能还没全先把关键指标埋上后面调优才有依据。