机器学习生产化落地:从Notebook到高可用模型服务的工程实践

机器学习生产化落地:从Notebook到高可用模型服务的工程实践 1. 项目概述这不是一次模型训练而是一场工程交付“From Notebook to Production: Running ML in the Real World (Part 4)”——这个标题里藏着一个被太多人轻描淡写、却让无数团队在临门一脚时彻底卡死的真相Notebook 是思考的草稿纸Production 是交付的合同书。它不讲怎么调参、不教怎么画 loss 曲线它直指那个没人愿意多说但每天都在吞噬工程师时间的核心问题当你在 Jupyter 里跑通了 accuracy 92.3% 的模型下一步该把这串代码交给谁用什么方式交交过去之后它会不会在凌晨三点因为一个空字符串输入就让整个订单系统延迟 8 秒有没有人能立刻知道它出错了出了错能不能回滚到上一版它的 CPU 占用率突然飙到 98%是模型本身的问题还是上游传来的数据格式悄悄变了我做过 7 个从零启动的机器学习落地项目其中 4 个在 Part 3模型验证与 API 封装就停住了剩下 3 个真正跑进生产环境的平均花了 11.6 周才完成 Part 4 的闭环。不是模型不够好而是我们长期把“能跑”和“能扛”混为一谈。Part 4 不是技术选型的终点而是工程责任的起点。它要求你同时戴上三顶帽子SRE稳定性、MLOps可复现性、业务 Owner可观测性。你得清楚知道当用户在 App 里点下“智能推荐”按钮背后触发的不只是model.predict()这一行代码而是一整条链路请求网关 → 负载均衡 → 特征预处理服务 → 模型推理容器 → 后处理逻辑 → 缓存策略 → 异步日志上报 → 实时指标聚合 → 告警阈值触发。任何一个环节掉链子用户感知到的不是“模型不准”而是“App 卡了”。这篇文章面向的不是刚学完 Scikit-learn 的新手也不是只管发论文的研究者而是已经把模型训出来、API 也封装好了正站在生产环境门口、手里攥着部署脚本却迟迟不敢按回车的那群人。你会在这里看到真实世界里的脏数据怎么绕过你的try...except看到 Kubernetes 的 HPA水平扩缩容为什么会在流量高峰时反而把副本数缩到 1看到 Prometheus 报出来的 P99 延迟飙升最后发现根源是一台宿主机的磁盘 I/O 等待时间超标——而这个指标你的模型监控 Dashboard 根本没接入。Part 4 的本质是把“算法思维”切换成“系统思维”把“我证明了这个方法有效”升级为“我保证这个服务可持续交付价值”。接下来的内容全部基于我在电商搜索排序、金融风控评分、IoT 设备异常检测三个高并发、强 SLA 场景下的实操沉淀没有理论推导只有踩坑记录、配置快照和可直接抄的 checklist。2. 内容整体设计与思路拆解为什么必须放弃“一键部署”的幻觉2.1 从单体推理服务到分层服务架构的必然性很多团队在 Part 3 结束后会本能地选择一条看似最短的路径把训练好的.pkl或.onnx模型连同Flask/FastAPI服务代码一起打包进 Docker 镜像用docker run -p 8000:8000启起来再加个 Nginx 反向代理就算完成了“生产部署”。我试过而且不止一次。第一次是在一个内部工具项目上模型很小QPS 不到 5跑了三个月相安无事。第二次是给一个日活 200 万的 App 做个性化 Push 推荐我们沿用了同样的模式上线第三天凌晨用户投诉推送延迟超过 2 分钟监控显示 API 平均响应时间从 120ms 暴涨到 4.7s。排查了 6 小时最终定位到特征工程部分有一段pandas.merge()操作在某个特定用户 ID 组合下会触发全表扫描而这个组合恰好在凌晨批量推送时集中出现。问题不在模型而在服务结构——所有逻辑数据获取、清洗、特征计算、模型预测、结果组装都挤在一个进程里一个慢查询拖垮了整个服务的事件循环。这就是 Part 4 必须重构架构的根本原因单体推理服务无法隔离故障域也无法独立伸缩。当特征计算耗 CPU模型预测耗 GPU缓存读取耗内存它们却共享同一套资源配额和同一个健康检查探针。Kubernetes 的 liveness probe 只会看端口是否通不会管你merge()是不是卡在了笛卡尔积上。所以我们放弃了“一个镜像打天下”的思路转向明确的三层分离Feature Serving 层独立微服务负责从 Redis/ClickHouse 中拉取原始特征并执行确定性、无状态的转换如归一化、one-hot 编码。它用 Go 重写QPS 能稳定在 12,000P99 延迟 8ms。关键点在于它不依赖任何 Python 运行时避免 GIL 锁死它所有的转换逻辑都通过 protobuf schema 严格定义下游服务只需按 schema 拉取无需关心实现。Model Serving 层纯粹的推理容器只做一件事加载模型、接收标准化特征向量、返回预测分数。我们强制要求所有模型必须导出为 ONNX 格式PyTorch/TensorFlow 训练后统一转换服务框架选用 Triton Inference Server。原因很实在Triton 原生支持模型热更新不用重启容器、支持动态批处理batch size 自适应、内置 GPU 显存管理且能同时托管多个框架的模型。我们曾用一个 Triton 实例同时运行 PyTorch 的排序模型和 XGBoost 的风控模型资源利用率比单独部署两个 Flask 服务高出 3.2 倍。Orchestration 层用 Python FastAPI 编写但它只做胶水工作接收原始请求如用户 ID、设备信息、调用 Feature Serving 获取特征、将特征向量喂给 Triton、对 Triton 返回的 raw score 做业务规则后处理比如“分数 0.8 且用户近 7 天有购买行为才触发 Push”、写入 Kafka 日志流、返回最终结果。它的代码量不到整个服务的 15%但它是唯一能理解业务语义的组件也是唯一需要频繁迭代的部分。这个分层不是为了炫技而是为了满足三个硬性 SLAFeature Serving 必须 99.99% 可用用户画像不能丢Model Serving P99 延迟 ≤ 150ms实时推荐不能卡Orchestration 层变更必须 5 分钟内灰度完成业务规则调整不能停服。单体服务无法对这三个目标分别设限、分别监控、分别扩容。分层之后Feature Serving 出问题模型还能用缓存特征兜底Triton 崩了Orchestration 层可以降级返回默认策略Orchestration 逻辑要改Feature 和 Model 层完全不受影响。这才是“生产就绪”的底层逻辑。2.2 监控体系从“有没有在跑”到“跑得健不健康”在 Notebook 里print(model.score(X_test, y_test))就是全部监控。到了生产环境这句话必须扩展成一张覆盖 4 个维度的监控矩阵维度监控对象关键指标采集方式告警阈值示例基础设施容器、节点、网络CPU/Mem 使用率、GPU 显存、网络丢包率、磁盘 I/O waitPrometheus Node ExporterCPU 90% 持续 5min服务健康HTTP 服务、gRPC 端点请求成功率HTTP 2xx/5xx、P50/P90/P99 延迟、QPSPrometheus Custom Metrics Exporter5xx 错误率 0.5%模型表现模型输出分布、特征漂移预测分数分布偏移KS 检验、关键特征值域变化如 age 120、线上 AUC 滑动窗口对比Evidently AI Kafka 流式采样KS 统计量 0.2 持续 1h业务影响用户行为、商业结果推荐点击率CTR、风控拦截准确率、模型决策导致的客诉量自研埋点 SDK BigQueryCTR 下跌 15% 持续 30min很多人以为加个 Prometheus 就算监控到位了其实最大的盲区在“模型表现”和“业务影响”这两层。我们吃过亏一个风控模型上线后Prometheus 显示一切正常——QPS 稳定、延迟达标、GPU 利用率 65%。但两周后业务方反馈高风险用户漏判率上升了 22%。回溯发现上游数据源变更了手机号脱敏规则导致模型最关键的“设备指纹一致性”特征失效而这个特征的值域变化从 0-1 变成全 0根本没被任何基础设施或服务健康指标捕获。从此我们强制要求每个模型服务必须暴露至少 3 个自定义指标model_input_features_drift_score、model_output_score_distribution_kl_divergence、model_business_impact_ctr_delta_30m。这些指标由 Evidently AI 在后台消费 Kafka 中的实时预测日志流计算得出每 5 分钟更新一次直接集成进 Grafana Dashboard。当 KL 散度超过阈值告警会自动创建 Jira ticket 并 对应的数据科学家当 CTR delta 触发会自动暂停该模型的流量分配切回旧版。这种监控不是锦上添花而是止损的最后防线。它把“模型是否还有效”这个问题从需要人工查日志、跑离线评估的“事后分析”变成了一个可量化、可告警、可自动响应的“实时信号”。2.3 发布策略灰度、金丝雀、蓝绿选哪个取决于你的“痛感阈值”“上线”这个词在 Part 4 里有精确的工程定义它是一次受控的风险释放过程而非一个时间点。我们绝不允许“一刀切”式发布。选择哪种策略核心依据只有一个如果新版本出问题你的业务能承受多大范围的失败灰度发布Gradual Rollout适用于所有场景尤其是首次上线或重大逻辑变更。我们规定新版本必须先对 0.1% 的真实流量生效持续观察 15 分钟确认所有监控指标特别是业务指标无异常后再以 5% → 20% → 50% → 100% 的节奏递增。Kubernetes 的RollingUpdate策略配合 Istio 的 VirtualService 路由权重能完美支撑这一流程。关键技巧是灰度流量必须是“真实用户、真实场景、真实数据”而不是构造的测试请求。我们用用户 ID 的哈希值后两位来决定路由hash(uid)[-2:] 01确保灰度样本具备统计代表性。金丝雀发布Canary Release当你要验证一个可能影响核心体验的变更时比如推荐算法从协同过滤换成图神经网络金丝雀是更优解。它和灰度的区别在于金丝雀是定向的、有明确对照组的。我们会选取一批高价值用户如月消费 5000 元的 VIP作为金丝雀群体新模型只对他们生效同时并行记录老模型对同一群体的预测结果。然后用 AB Test 平台我们自研的直接对比两组的 CTR、GMV、停留时长等业务指标。只有当新模型在所有核心指标上 p-value 0.01 且提升幅度 5%才进入灰度阶段。金丝雀的本质是用小成本验证大假设。蓝绿部署Blue-Green Deployment这是我们用于“不可逆变更”的终极方案比如模型版本升级伴随数据库 schema 变更。蓝环境v1和绿环境v2完全独立包括数据库、缓存、消息队列。发布时先全量部署绿环境用影子流量Shadow Traffic让它消化 100% 的线上请求但不返回给用户只记录日志验证 1 小时无异常后再通过 DNS 或 API 网关一次性将所有流量切到绿环境。回滚只需把流量切回蓝环境秒级完成。代价是资源开销翻倍但换来的是零停机、零数据丢失、零用户感知。提示别迷信“最先进”的策略。我们有个教训曾为一个内部 BI 工具强行上金丝雀结果发现 VIP 用户只占总流量的 0.03%导致金丝雀样本量太小AB Test 结果波动极大反而延误了上线。后来改成灰度用随机抽样30 分钟就拿到了稳定结论。策略的选择永远服务于你的风险控制目标而不是技术名词的酷炫程度。3. 核心细节解析与实操要点那些文档里不会写的“手抖时刻”3.1 Triton Inference Server 的 ONNX 模型优化实战把 PyTorch 模型转成 ONNX 只是第一步Triton 能否高效加载并推理取决于你转模型时的每一个参数。我们曾因一个dynamic_axes配置错误导致 Triton 在加载时直接 OOMOut of Memory崩溃排查了两天才发现问题根源。标准转换流程PyTorch → ONNX → Triton# 1. PyTorch 模型导出关键 dummy_input torch.randn(1, 128) # 注意batch_size1这是 Triton 动态批处理的基础 torch.onnx.export( model, dummy_input, model.onnx, export_paramsTrue, opset_version14, # Triton 23.03 推荐用 14兼容性最好 do_constant_foldingTrue, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size}, # 必须声明 batch_size 可变否则 Triton 会固定为 1 output: {0: batch_size} } ) # 2. Triton 模型仓库结构必须严格遵循 model_repository/ └── my_recommender/ ├── config.pbtxt # Triton 的核心配置文件下面详解 └── 1/ └── model.onnxconfig.pbtxt是 Triton 的“宪法”写错一个字段服务就起不来。以下是我们在生产环境验证过的最小可行配置name: my_recommender platform: onnxruntime_onnx max_batch_size: 128 # Triton 能接受的最大 batch size不是模型能处理的 input [ { name: input data_type: TYPE_FP32 dims: [128] # 必须和 ONNX 模型的 input shape 一致 } ] output [ { name: output data_type: TYPE_FP32 dims: [1] # 输出是单个 score } ] # 关键优化启用动态批处理这是降低 P99 延迟的核心 dynamic_batching [ { max_queue_delay_microseconds: 1000 # 请求最多等待 1ms凑 batch } ] # GPU 配置指定使用哪张卡避免多模型争抢 instance_group [ { count: 2 # 启动 2 个模型实例充分利用 GPU kind: KIND_GPU gpus: [0] # 绑定到 GPU 0 } ]注意max_batch_size: 128这个值不是越大越好。我们实测过设为 256 时虽然吞吐量提升了 12%但 P99 延迟从 85ms 涨到 142ms因为大 batch 导致单次推理时间过长排队等待时间激增。最终选定 128是吞吐和延迟的帕累托最优解。避坑心得Triton 默认会把所有模型加载进 GPU 显存。如果你的模型很大 2GB而 GPU 显存只有 16GBcount: 2就会导致 OOM。解决方案是在instance_group中添加profile: [gpu_memory]并手动设置memory_budget_bytes: 85899345928GB强制 Triton 控制显存占用。ONNX 模型里如果有torch.nn.Dropout层即使在eval()模式下Triton 推理时也可能产生随机输出。必须在导出前用model model.eval().to(cpu)并确保所有Dropout层被torch.no_grad()包裹或者干脆在模型定义里用nn.Identity()替换掉 Dropout。3.2 特征服务的幂等性与一致性保障Feature Serving 层看似简单却是整个链路最易出错的一环。它的核心挑战是如何保证在分布式、高并发、网络不稳定的环境下对同一个用户 ID 的多次请求返回完全一致的特征向量我们曾遇到过同一个用户在 1 秒内发起两次推荐请求第一次返回的user_age_group25-34第二次返回user_age_groupunknown原因是特征计算服务在中间发生了重启缓存失效而上游数据源MySQL的age字段恰好在那一刻是 NULL。解决方案是“双写 最终一致性”写路径Write Path当用户资料更新如修改生日业务系统不是直接写 MySQL而是发一条 Kafka 消息到user_profile_updatetopic。Feature Serving 的一个专用消费者Consumer Group监听此 topic收到消息后先更新 Redis 中的user:{id}:featuresHash 结构包含所有已计算好的特征再异步更新 ClickHouse 中的宽表用于离线分析最后向feature_computedtopic 发送一条确认消息包含user_id,timestamp,feature_hash。读路径Read Path当推荐服务请求用户特征时先查 Redis如果命中直接返回feature_hash与请求携带的last_known_hash比对一致则信任如果 Redis 未命中或hash不一致则触发“一致性校验”调用 ClickHouse 的实时查询接口SELECT ... WHERE user_id ? AND ts ? ORDER BY ts DESC LIMIT 1拿到最新特征写回 Redis并返回所有读操作都带timeout50ms超时则返回 Redis 中的旧值降级并记录stale_read告警。这个设计的关键在于Redis 是主读库ClickHouse 是主写库和权威源Kafka 是状态同步的管道。它牺牲了“强一致性”但换来了亚毫秒级的读取延迟和极高的可用性。我们线上 99.9% 的特征读取都在 Redis 完成平均延迟 1.2msClickHouse 查询只在 0.03% 的场景下触发P99 延迟 42ms完全在可接受范围内。实操心得不要试图用分布式锁如 Redis RedLock来保证读写一致那会成为性能瓶颈。最终一致性 降级策略才是高并发场景下的务实之选。我们甚至给 Redis 设置了maxmemory-policy allkeys-lru允许它自动淘汰冷数据因为“特征过期”比“服务不可用”后果轻得多。3.3 模型版本管理与回滚的“五步法”在生产环境模型不是“发布了就完了”而是“发布了才开始”。我们制定了严格的模型版本管理 SOP任何一次上线都必须走完以下五步缺一不可版本号固化模型文件名必须包含完整语义化版本号格式为model_v{MAJOR}.{MINOR}.{PATCH}_{YYYYMMDDHHMMSS}.onnx。例如model_v2.1.0_20240520143022.onnx。MAJOR表示特征工程逻辑变更如新增一个特征MINOR表示模型结构微调如层数变化PATCH表示纯超参优化。这个命名规则由 CI/CD 流水线自动注入人工无法修改。元数据注册模型上传到 S3 后必须向内部的 Model Registry一个 PostgreSQL 表写入完整元数据INSERT INTO model_registry ( model_name, version, s3_path, train_dataset_version, feature_schema_hash, eval_metrics_json, created_by, created_at ) VALUES (...);其中feature_schema_hash是当前特征服务所用 schema 的 SHA256确保模型与特征版本强绑定。自动化 Smoke TestTriton 加载新模型后CI 流水线会自动发起 100 次请求使用预设的 5 个典型样本覆盖边界值、空值、异常值验证HTTP 状态码为 200返回 JSON 结构正确score字段存在且为 floatP50 延迟 100ms无 Python 异常日志grep ERROR /var/log/triton.log。灰度流量标记Istio 的 VirtualService 配置中必须为新版本添加headers: { x-model-version: v2.1.0 }这样所有灰度请求的日志里都会带上模型版本便于后续问题追踪。回滚预案备案在 Jira 中创建一个名为ROLLBACK_PLAN_v2.1.0的子任务明确写出回滚命令kubectl set image deployment/triton-deployment tritonmy-registry/model:v2.0.0回滚验证步骤检查/v2/models/my_recommender/versions/1/ready返回 true回滚后必查指标model_output_score_distribution_kl_divergence是否回归基线回滚负责人DataScience-Lead。这套流程看起来繁琐但它把“回滚”从一个充满不确定性的救火行为变成了一个可预期、可演练、可审计的标准操作。我们上线三年共执行过 7 次回滚平均耗时 4.3 分钟最长的一次是 6.8 分钟因为要等数据库备份恢复没有一次引发二次故障。4. 实操过程与核心环节实现从零搭建一个可落地的 MLOps 流水线4.1 基础设施即代码IaC用 Terraform 定义你的“生产底盘”一切始于基础设施。我们拒绝手动在 AWS 控制台点点点所有云资源都通过 Terraform 管理。核心模块包括EKS ClusterKubernetes 集群启用了eksctl创建的托管节点组自动伸缩。RDS PostgreSQLModel Registry 和元数据存储开启 Multi-AZ 和自动备份。S3 Bucket模型二进制存储启用了版本控制和生命周期策略30 天后转 Glacier。Elasticache RedisFeature Serving 的缓存层集群模式3 分片 3 副本。KMS Key用于加密 S3 中的模型文件和 RDS 中的敏感字段。Terraform 代码不是写一次就扔而是和模型代码一样纳入 Git 仓库走 Code Review。每次 infra 变更都必须附带terraform plan的输出截图以及对业务影响的说明例如“增加一个 Redis 副本预计成本增加 $200/月但可将缓存可用性从 99.9% 提升至 99.99%”。关键配置片段main.tfmodule eks { source terraform-aws-modules/eks/aws version 18.33.0 cluster_name ml-prod-cluster cluster_version 1.27 # 托管节点组专为 Triton 优化 managed_node_groups { triton-ng { desired_capacity 4 max_capacity 12 min_capacity 2 instance_types [g4dn.xlarge] # 带 T4 GPU labels { node_type triton gpu true } } } }这个g4dn.xlarge的选择是我们实测的结果Triton 在单卡 T4 上对我们的 128 维特征模型能稳定支撑 320 QPSP99 延迟 95ms。如果换成p3.2xlargeV100QPS 能到 850但成本是 3.2 倍且 GPU 利用率常年低于 40%属于过度配置。基础设施选型永远是成本、性能、可靠性的三角权衡没有银弹。4.2 CI/CD 流水线GitHub Actions 驱动的全自动发布我们用 GitHub Actions 构建了端到端的 CI/CD 流水线从代码提交到生产上线全程无人值守。流水线分为四个阶段Stage 1: Lint Unit Test 2minpylint检查 Python 代码风格pytest运行所有单元测试覆盖率 ≥ 85%black和isort自动格式化失败则阻断。Stage 2: Build Package 5min构建 Triton 模型仓库 Docker 镜像Dockerfile.triton构建 Feature Serving Go 服务镜像Dockerfile.feature构建 Orchestration FastAPI 镜像Dockerfile.api所有镜像推送到 ECR并打上git commit hash和branch标签。Stage 3: Staging Deploy Integration Test 8min使用 Argo CD 的 ApplicationSet将staging环境的 manifests 应用到 EKS启动一个临时的integration-testJob它会调用 Feature Serving 获取 100 个用户特征调用 Triton 进行批量推理调用 Orchestration API 获取最终结果验证所有返回的score在 [0.0, 1.0] 区间内且无NaN或inf检查日志中无ERROR级别消息。Stage 4: Production PromoteManual Approval Required流水线在此处暂停等待MLOps-Lead在 GitHub UI 上点击 “Approve and Deploy”审批通过后自动执行更新 Argo CD 中productionApplication 的image.tag为新构建的 hash触发 Istio 的 VirtualService 更新将灰度流量权重设为 0.1%向 Slack#ml-ops-alerts发送通知“v2.1.0 灰度已启动监控中…”。整个流水线的 YAML 文件超过 1200 行但它带来的价值是每一次上线都是可重复、可追溯、可审计的。我们能精确回答“v2.1.0 是在哪一天、哪一分、由谁、基于哪个 commit、经过哪些测试、在哪个环境验证后最终推到生产的” 这不是为了应付审计而是为了在出问题时能以最快速度定位根因。4.3 实时监控与告警Grafana Prometheus Alertmanager 的黄金三角我们的监控 Dashboard 不是摆设而是工程师的“第二双眼睛”。核心看板包括Cluster OverviewEKS 节点 CPU/Mem/GPU 利用率、Pod 重启次数、网络流量。API HealthOrchestration 服务的 QPS、成功率、P50/P90/P99 延迟、错误率按 HTTP 状态码分类。Triton Metrics模型加载状态、GPU 显存使用、推理吞吐量inferences/sec、动态批处理效率avg batch size。Feature ServingRedis 命中率、ClickHouse 查询延迟、Kafka 消费 Lag。Model PerformanceEvidently 计算的data_drift_psi、prediction_drift_kl、target_drift_chi2。告警规则全部定义在 Prometheus 的alert.rules文件中经 Alertmanager 分发。我们坚持“少而精”的原则只设 12 条核心告警每一条都对应一个明确的、可执行的 SOP告警名称触发条件SOP标准操作程序Triton_Model_Load_Failedtriton_model_load_failure_total{modelmy_recommender} 01. 查/v2/models/my_recommender/versions/1/ready2. 查 Triton 日志grep ERROR /var/log/triton.log3. 回滚到上一版镜像。Feature_Redis_Hitrate_Lowredis_hit_rate{jobfeature-redis} 0.851. 查 ClickHouse 消费 Lag2. 查 Kafkauser_profile_updatetopic 的 produce rate3. 若 Lag 1000重启 Feature Consumer。Model_Prediction_Drift_Highevidently_prediction_drift_kl{modelmy_recommender} 0.251. 查 Evidently 报告详情2. 比对新旧特征分布3. 若确认漂移暂停该模型流量通知数据科学家。实操心得告警不是越多越好而是越“可行动”越好。我们曾经设过一条CPU_Usage_High告警结果每天收 20 条全是毛刺工程师直接 mute 了频道。后来改成CPU_Usage_High_For_5min并关联到具体的 Pod 名称和节点 IP告警量降到每周 1-2 条且每次都能精准定位问题。好的告警应该是一个带着上下文的工单而不是一个模糊的感叹号。5. 常见问题与排查技巧实录那些让你半夜爬起来的“经典瞬间”5.1 问题速查表高频故障与秒级定位法现象可能原因秒级定位命令解决方案API 响应延迟突增P99 500msTriton 动态批处理队列积压curl http://triton:8002/v2/models/my_recommender/stats | jq .model_stats[0].inference_stats.success.count对比历史值检查max_queue_delay_microseconds是否过小或上游 QPS 是否远超预估。临时增大该值。Triton 报Model not found模型仓库路径或 config.pbtxt 名称错误kubectl exec -it triton-pod -- ls /models/my_recommender/kubectl exec -it triton-pod -- cat /models/my_recommender/config.pbtxt确保目录名、config 文件名、config 中的name字段三者完全一致且大小写敏感。Feature Serving 返回null特征Redis 缓存穿透ClickHouse 查询超时kubectl logs -l appfeature-consumer | grep query timeoutkubectl exec -it redis-pod -- redis-cli KEYS user:*看 key 是否存在检查 ClickHouse 的max_execution_time参数调大或临时在 Feature Service 中增加熔断逻辑。Orchestration 服务 503 错误Istio Envoy Sidecar 未就绪或 VirtualService 配置错误kubectl get pods -o wide看 sidecar 状态kubectl get virtualservice -o yaml查路由规则kubectl rollout restart deployment/orchestration检查 VS 的http.route.destination.host是否指向正确的 Service 名。模型预测结果全为 0.0ONNX 模型输入维度与 Triton config 不匹配curl http://triton:8002/v2/models/my_recommender/config | jq .input[0].dims对比模型实际输入 shape重新导出 ONNX严格匹配