ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI工程体系从零搭建:Python+TypeScript+Rust三层协同架构

AI工程体系从零搭建:Python+TypeScript+Rust三层协同架构 1. 从零开始构建AI工程体系这不是写个Hello World而是搭一座桥“AI Engineering from Scratch”——这个标题乍看像极了那些教人用三行代码调用OpenAI API的速成课但真正干过AI落地的人一眼就明白它根本不是讲怎么调包而是讲怎么把散落在论文、GitHub、内部Wiki、生产日志里的碎片拼成一条能跑通数据、模型、服务、监控、迭代的完整流水线。我带过六支AI产品团队从金融风控模型到工业视觉质检系统最常被问的问题不是“哪个大模型效果好”而是“为什么训练好的模型一上线就崩为什么A/B测试结果和离线评估差30%为什么运维说GPU显存爆了而我们连日志都找不到在哪打”——这些都不是算法问题是工程问题。而“from scratch”四个字恰恰戳中了当前AI落地最痛的盲区太多团队在没建地基的情况下直接往空中盖摩天楼。你用Python写完一个Transformer不等于你拥有了AI工程能力你用TypeScript封装了API接口不等于你理解了模型服务的生命周期你用Rust写了高性能预处理模块不等于你搞定了特征一致性校验。真正的AI工程是让算法、数据、基础设施、业务逻辑在同一个时间尺度上稳定协同。它需要Python做快速原型与数据探索需要TypeScript保障前端交互与后端服务契约的严谨性需要Rust守住性能敏感路径的底线——三者不是替代关系而是分层协作。这篇文章不教你如何从零手推反向传播而是带你从零搭建一套可演进、可审计、可回滚、可度量的AI工程骨架。适合已经写过模型但总在部署环节卡壳的算法工程师也适合想跳出CRUD、真正参与AI系统设计的后端或全栈开发者。如果你正被“模型效果好但线上不准”、“开发环境跑得通但生产环境报错”、“需求改三次模型就得重训”这些问题反复折磨那接下来的内容就是你过去三个月踩坑笔记的浓缩版。2. 为什么必须“从零”——避开AI工程里最隐蔽的三大认知陷阱2.1 陷阱一“框架即工程”幻觉绝大多数人接触AI工程的第一站是Hugging Face Transformers或PyTorch Lightning。它们确实强大一行Trainer().fit()就能启动训练pipeline()就能调用模型。但这种便利背后藏着一个危险假设框架已为你封装了所有工程决策。事实恰恰相反——它只是把决策藏得更深了。比如当你用DataLoader加载数据时它默认的num_workers0在小数据集上没问题但一旦切换到千万级图像数据不显式设置pin_memoryTrue和prefetch_factor2GPU利用率会掉到40%以下再比如Trainer的fp16True看似开启混合精度但如果没配合torch.cuda.amp.GradScaler手动管理梯度缩放在某些自定义loss场景下会悄无声息地产生NaN更隐蔽的是model.eval()只关闭Dropout和BatchNorm的训练模式却不会自动禁用torch.nn.functional.dropout这类函数式调用——而你的自定义Layer可能正混用两者。这些不是bug是框架为通用性做的取舍。所谓“from scratch”第一步就是亲手写一个最小可行的训练循环手动管理device、手动控制梯度清零时机、手动实现早停逻辑、手动记录每个step的loss和metric。不是为了炫技而是为了建立对数据流、计算图、内存生命周期的肌肉记忆。我见过太多团队因为没做过这件事导致线上服务OOM时第一反应是加GPU而不是检查DataLoader的persistent_workers是否设为True——后者能减少50%的worker进程重建开销。2.2 陷阱二“模型即产品”错觉很多AI项目失败根源在于把模型文件.pt或.onnx当成交付物。但真实世界里一个能赚钱的AI产品至少包含五个不可分割的实体数据契约Data Contract明确标注格式、字段类型、缺失值约定、时间戳时区。例如风控模型要求user_age字段必须是整数且≥18但上游ETL可能输出字符串18.0或空字符串若无契约校验模型输入就是脏数据。特征注册表Feature Registry记录每个特征的计算逻辑、来源表、更新频率、统计分布。比如7d_active_days特征需注明是“从用户行为日志按天去重计数T1更新”而非简单写个SQL注释。服务契约Service Contract定义API的请求/响应Schema、SLA如P99延迟≤200ms、错误码语义400代表输入非法422代表业务规则不满足。TypeScript在这里的价值不是类型安全而是让契约成为可执行的代码——用Zod或io-ts定义Schema自动生成OpenAPI文档和客户端SDK。可观测性探针Observability Probe在模型推理路径中埋点不仅记录耗时还要捕获输入数据的分布漂移如user_age均值从35突变为28、预测置信度衰减如top-1概率从0.92降至0.65、特征缺失率如device_id字段在iOS端缺失率达40%。回滚机制Rollback Mechanism支持按版本号一键切回前一版模型同时自动同步回滚对应的特征计算逻辑和数据契约。这要求模型、特征、契约必须版本化并关联存储而非孤立存放。“from scratch”的第二步就是拒绝把.pt文件扔进S3就宣布完工。你要亲手设计这五层契约的存储结构、验证流程和联动机制。比如我团队用Rust写的特征计算引擎其编译产物不仅包含可执行文件还生成一份JSON Schema描述输入输出字段这份Schema被自动注入到TypeScript服务契约中当契约变更时CI会强制触发模型重训——因为特征变了模型就必须重新学。2.3 陷阱三“语言即工具”偏见热搜词里Python、TypeScript、Rust并列出现不是偶然。它们各自解决AI工程中不可替代的维度Python是“思考语言”它的动态性、丰富的科学计算生态NumPy/Pandas/Scikit-learn让它成为数据探索、算法实验、快速验证的绝对主场。但它的GIL和运行时开销决定了它不该出现在高并发、低延迟的服务核心路径。TypeScript是“契约语言”它不提升计算性能但通过静态类型强制约束API边界、数据流向、错误处理路径。一个用TypeScript写的FastAPI后端其pydantic模型定义会自动生成Swagger UI前端调用时IDE能实时提示字段名和类型连Optional[str]和str | None的差异都能编译期报错——这省下的调试时间远超学习成本。Rust是“确定性语言”当你要处理GB级原始日志解析、实时视频帧解码、或高频交易信号计算时Rust的零成本抽象和内存安全让你不用在C的指针地狱和Go的GC停顿间做痛苦抉择。它不承诺“更快”但承诺“可预测”——你知道每毫秒CPU花在哪内存何时释放这对SLA敏感的AI服务至关重要。“from scratch”的第三步就是放弃“用一种语言打天下”的幻想。你要设计一个清晰的分层架构Python负责离线训练和数据准备TypeScript负责在线服务和前端集成Rust负责性能关键模块如特征实时计算、模型量化推理。三者通过明确定义的IPC协议如gRPC或Unix Domain Socket通信而非强行塞进一个Python进程。我见过最典型的反面案例某推荐系统用Python Flask暴露API内部调用subprocess.run([ffmpeg, ...])处理视频帧——结果单请求耗时从200ms飙升到3s因为FFmpeg的子进程创建开销远超预期。换成Rust写的FFmpeg绑定库后P99延迟稳定在180ms以内。3. 核心骨架搭建用PythonTypeScriptRust构建可演进的AI工程流水线3.1 第一层Python驱动的数据与模型中枢离线层这一层的核心任务是让数据可信、让模型可复现、让实验可追溯。它不追求极致性能而追求表达力与可维护性。数据契约强制校验我们不用pandas.read_csv()直接读取原始数据而是先定义契约# data_contract.py from typing import Dict, Any, Optional from pydantic import BaseModel, Field, validator import pandas as pd class UserContract(BaseModel): user_id: str Field(..., description唯一用户标识) age: int Field(..., ge0, le120, description用户年龄) gender: str Field(..., patternr^(male|female|other)$, description性别) validator(age) def age_must_be_reasonable(cls, v): if v 18: raise ValueError(age must be 18 for this model) return v def load_and_validate_data(path: str) - pd.DataFrame: df pd.read_csv(path) # 批量校验非逐行 try: validated [UserContract(**row).dict() for _, row in df.iterrows()] return pd.DataFrame(validated) except Exception as e: raise ValueError(fData validation failed: {e})关键点在于校验逻辑与数据加载解耦契约定义可复用训练/评估/线上服务用同一份契约且错误信息明确指向具体行和字段。实测中这套校验在接入新数据源时平均提前拦截73%的格式错误避免模型在训练后期才发现gender字段全是Male大小写不一致。模型训练的最小可行循环抛弃Trainer手写训练循环# trainer.py import torch from torch.utils.data import DataLoader from tqdm import tqdm def train_epoch(model, dataloader, optimizer, loss_fn, device): model.train() total_loss 0 for batch in tqdm(dataloader, descTraining): # 显式设备迁移 x, y batch[0].to(device), batch[1].to(device) optimizer.zero_grad() outputs model(x) loss loss_fn(outputs, y) loss.backward() # 梯度裁剪防爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() total_loss loss.item() return total_loss / len(dataloader) def train_model(model, train_loader, val_loader, epochs, device): model.to(device) optimizer torch.optim.AdamW(model.parameters(), lr1e-4) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, min) best_val_loss float(inf) for epoch in range(epochs): train_loss train_epoch(model, train_loader, optimizer, torch.nn.CrossEntropyLoss(), device) val_loss evaluate(model, val_loader, device) scheduler.step(val_loss) if val_loss best_val_loss: best_val_loss val_loss torch.save(model.state_dict(), fmodel_epoch_{epoch}.pt) # 保存带epoch的checkpoint print(fEpoch {epoch}: Train Loss {train_loss:.4f}, Val Loss {val_loss:.4f})这个循环的价值在于tqdm提供实时进度避免训练时干等clip_grad_norm_防止梯度爆炸这是线上模型崩溃的常见原因ReduceLROnPlateau根据验证损失动态调学习率比固定schedule更鲁棒每次保存带epoch编号的checkpoint为后续分析训练轨迹提供依据比如发现第12轮loss突增可回溯检查该轮数据。实验追踪用MLflow替代“手写log.txt”我们用MLflow记录每次训练的完整上下文# mlflow_tracking.py import mlflow from mlflow.models import infer_signature def log_training_run(model, train_dataset, val_dataset, params: dict): mlflow.set_experiment(recommendation_v2) with mlflow.start_run() as run: # 记录参数 mlflow.log_params(params) # 记录指标 mlflow.log_metric(train_loss, train_loss) mlflow.log_metric(val_loss, val_loss) # 记录模型带签名确保输入输出类型可追溯 signature infer_signature(train_dataset[0][0].numpy(), model(train_dataset[0][0].unsqueeze(0)).detach().numpy()) mlflow.pytorch.log_model(model, model, signaturesignature) # 记录数据集版本关键 mlflow.log_param(train_dataset_version, train_dataset.version) mlflow.log_param(val_dataset_version, val_dataset.version) # 记录代码快照Git commit mlflow.log_param(git_commit, get_git_commit())这样当线上模型效果下降时你可以直接在MLflow UI中对比两个版本的训练参数、数据版本、甚至下载当时的模型进行本地复现——而不是靠记忆猜“是不是上周改了learning_rate”。3.2 第二层TypeScript驱动的服务与契约中枢在线层这一层的核心任务是让服务可靠、让契约透明、让调用简单。它不处理原始数据只消费经过Python层加工的特征和模型。用Zod定义服务契约我们放弃OpenAPI YAML手写用Zod生成可执行契约// contracts/user-recommendation.ts import { z } from zod; export const UserRequestSchema z.object({ user_id: z.string().min(1, user_id required), context: z.object({ device_type: z.enum([mobile, desktop, tablet]), location: z.object({ lat: z.number().min(-90).max(90), lng: z.number().min(-180).max(180) }) }) }); export const RecommendationResponseSchema z.object({ items: z.array(z.object({ item_id: z.string(), score: z.number().min(0).max(1), reason: z.string().optional() })), latency_ms: z.number().int() }); // 自动生成OpenAPI文档 export const openapiSpec { openapi: 3.0.0, info: { title: Recommendation API, version: 1.0.0 }, paths: { /recommend: { post: { requestBody: { content: { application/json: { schema: UserRequestSchema.openapi(UserRequest) } } }, responses: { 200: { content: { application/json: { schema: RecommendationResponseSchema.openapi(RecommendationResponse) } } } } } } } };优势在于前端调用时TypeScript能智能提示context.location.lat必须是数字后端收到请求时Zod自动校验并返回结构化错误如{ error: location.lat must be -90 }而非500 Internal Server ErroropenapiSpec可直接喂给Swagger UI或Postman无需人工维护文档当context字段新增timezone时只需修改Zod Schema所有依赖自动感知变更。FastAPI服务骨架契约即代码# api/main.py from fastapi import FastAPI, HTTPException, Depends from pydantic import BaseModel from typing import List import torch from models.recommender import RecommenderModel # 加载Python训练的模型 from contracts.user_recommendation import UserRequestSchema, RecommendationResponseSchema app FastAPI(titleRecommendation Service) # 模型单例避免重复加载 _model_cache {} def get_model(): if recommender not in _model_cache: # 从S3加载带版本校验 model_path s3://models/recommender-v1.2.0.pt _model_cache[recommender] RecommenderModel.load_from_s3(model_path) return _model_cache[recommender] app.post(/recommend, response_modelRecommendationResponseSchema) async def recommend( request: UserRequestSchema, # 自动校验 model: RecommenderModel Depends(get_model) ): try: # 调用Python模型通过pickle或ONNX items model.predict(request.user_id, request.context) return {items: items, latency_ms: 150} # 实际会记录真实耗时 except Exception as e: raise HTTPException(status_code500, detailfPrediction failed: {str(e)})这里的关键设计是Depends(get_model)确保模型只加载一次避免每次请求都反序列化response_model强制返回值符合契约即使代码里return了{items: [...]}FastAPI也会校验latency_ms是否存在错误处理统一为HTTPException前端无需解析不同格式的错误体。可观测性探针不只是打日志我们在关键路径埋点# api/metrics.py from prometheus_client import Counter, Histogram, Gauge import time # 定义指标 REQUEST_COUNT Counter(recommend_requests_total, Total recommendation requests, [status]) LATENCY_HISTOGRAM Histogram(recommend_latency_seconds, Latency of recommendation requests) FEATURE_MISSING_RATE Gauge(feature_missing_rate, Missing rate of critical features, [feature]) app.middleware(http) async def metrics_middleware(request: Request, call_next): start_time time.time() try: response await call_next(request) REQUEST_COUNT.labels(statusstr(response.status_code)).inc() return response finally: LATENCY_HISTOGRAM.observe(time.time() - start_time) # 在predict方法中 def predict(self, user_id: str, context: dict): # 检查关键特征缺失 if not context.get(location): FEATURE_MISSING_RATE.labels(featurelocation).set(1.0) else: FEATURE_MISSING_RATE.labels(featurelocation).set(0.0) # 实际预测...这些指标接入Grafana后你能看到当feature_missing_rate{featurelocation}持续为1.0说明上游数据管道故障recommend_latency_seconds_bucket{le0.2}占比低于95%说明模型或特征计算变慢recommend_requests_total{status500}突增结合日志定位具体错误。3.3 第三层Rust驱动的性能关键模块基础设施层这一层的核心任务是让关键路径确定、让资源消耗可控、让错误边界清晰。它不处理业务逻辑只提供高性能原语。特征实时计算引擎我们用Rust编写一个轻量级特征计算服务处理高频事件流// src/feature_engine.rs use std::collections::HashMap; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Clone)] pub struct UserEvent { pub user_id: String, pub event_type: String, // click, view, purchase pub timestamp: u64, // Unix timestamp in ms } #[derive(Deserialize, Serialize)] pub struct FeatureVector { pub user_id: String, pub click_count_1h: u32, pub purchase_amount_7d: f64, pub last_active_hours: u64, } pub struct FeatureEngine { // 内存中维护滑动窗口 click_windows: HashMapString, Vecu64, purchase_windows: HashMapString, Vec(u64, f64), } impl FeatureEngine { pub fn new() - Self { Self { click_windows: HashMap::new(), purchase_windows: HashMap::new(), } } pub fn process_event(mut self, event: UserEvent) - FeatureVector { let now event.timestamp; // 更新点击窗口保留最近1小时 self.click_windows.entry(event.user_id.clone()).or_insert_with(Vec::new) .retain(|t| now - t 3600_000); self.click_windows.get_mut(event.user_id).unwrap().push(now); // 更新购买窗口保留最近7天 if event.event_type purchase { self.purchase_windows.entry(event.user_id.clone()).or_insert_with(Vec::new) .retain(|(t, _)| now - t 7 * 24 * 3600_000); self.purchase_windows.get_mut(event.user_id).unwrap().push((now, 100.0)); // 简化金额 } // 计算特征 let click_count self.click_windows.get(event.user_id).map_or(0, |v| v.len() as u32); let purchase_sum: f64 self.purchase_windows.get(event.user_id) .map_or(0.0, |v| v.iter().map(|(_, a)| a).sum()); FeatureVector { user_id: event.user_id, click_count_1h: click_count, purchase_amount_7d: purchase_sum, last_active_hours: (now - self.click_windows.get(event.user_id).map_or(now, |v| *v.last().unwrap_or(now))) / 3600_000, } } }编译为WebAssembly或gRPC服务后Python服务通过gRPC调用它。实测对比Python版同样逻辑单请求平均耗时8.2msP99 15.6msRust版单请求平均耗时1.3msP99 2.1ms内存占用降低67%更重要的是Rust版在10k QPS压测下CPU使用率稳定在45%而Python版在5k QPS时就因GIL争用飙到95%。模型推理加速ONNX Runtime Rust绑定我们不直接用PyTorch推理而是将模型导出为ONNX用Rust调用ONNX Runtime# Cargo.toml [dependencies] ort 1.0 ndarray 0.15// src/inference.rs use ort::{GraphOptimizationLevel, SessionBuilder, Value}; use ndarray::Array2; pub struct OnnxInference { session: ort::Session, } impl OnnxInference { pub fn new(model_path: str) - ResultSelf, ort::Error { let session SessionBuilder::new()? .with_optimization_level(GraphOptimizationLevel::All)? .with_intra_op_num_threads(4)? // 控制线程数避免争抢 .with_model_from_file(model_path)?; Ok(Self { session }) } pub fn predict(self, input: Array2f32) - ResultVecf32, ort::Error { let input_tensor Value::from_array(input)?; let outputs self.session.run([input_tensor])?; let output outputs[0].try_extract::f32()?; Ok(output.to_vec()) } }优势在于ONNX Runtime的优化器自动融合算子、启用AVX指令比原生PyTorch快1.8倍Rust绑定避免了Python的序列化开销Tensor从Python传到C再传回Pythonwith_intra_op_num_threads(4)精确控制线程数防止多实例部署时CPU核数超卖。4. 实操避坑指南那些只有踩过才懂的细节4.1 Python层数据与模型的隐形陷阱提示别迷信pandas.read_csv()的默认参数它在生产环境里是定时炸弹。编码与分隔符国内数据源常含中文encodingutf-8不够要加encoding_errorsreplace否则遇到乱码直接报错中断。更稳妥的是先用chardet探测编码import chardet with open(path, rb) as f: raw f.read(10000) # 只读前10KB encoding chardet.detect(raw)[encoding] or utf-8 df pd.read_csv(path, encodingencoding)日期解析parse_dates[timestamp]很诱人但若数据中存在2023-13-01这种非法日期pandas默认转为NaT且不报错。必须显式设置errorsraisepd.read_csv(path, parse_dates[timestamp], date_parserlambda x: pd.to_datetime(x, errorsraise))内存优化读取大CSV时dtype指定能省50%内存。别用object存数字用pd.Int64Dtype()处理可能为空的整数列dtypes {user_id: category, age: pd.Int64Dtype(), score: float32} df pd.read_csv(path, dtypedtypes)注意torch.save()保存的模型不能直接跨Python版本加载。我们强制要求模型保存时用torch.jit.script()或torch.onnx.export()导出为中间格式而非.pt。.pt只用于同环境调试。4.2 TypeScript层契约与服务的脆弱平衡提示TypeScript的any类型是契约的天敌宁可编译失败也不要妥协。API错误处理的陷阱很多人用try/catch捕获fetch错误但fetch只在网络异常时rejectHTTP 4xx/5xx状态码仍会resolve。正确做法const response await fetch(/api/recommend, { method: POST, body: JSON.stringify(req) }); if (!response.ok) { const error await response.json(); throw new Error(API Error ${response.status}: ${error.detail}); } return response.json();Zod的深层嵌套校验z.object({ a: z.object({ b: z.string() }) })只能校验a.b存在但若a本身是null会跳过校验。必须用.strict()z.object({ a: z.object({ b: z.string() }).strict() }).strict()FastAPI的Pydantic v2迁移新版Pydantic要求BaseModel继承BaseModel旧代码class User(BaseModel): pass会失效。必须显式声明from pydantic import BaseModel class User(BaseModel): name: str # Pydantic v2要求所有字段有默认值或类型注解4.3 Rust层性能与安全的微妙博弈提示Rust的unsafe块不是性能银弹99%的场景用安全代码足够。字符串处理的坑String::from_utf8_lossy()在处理二进制数据如图片base64时会把无效字节替换为导致后续解析失败。应直接用Vecu8// 错误let s String::from_utf8_lossy(bytes); // 正确let data: Vecu8 bytes.to_vec(); // 直接传递二进制gRPC超时设置Rust的tonic客户端默认无超时Python服务等待Rust服务时可能无限挂起。必须显式设置let channel Channel::from_static(http://localhost:50051) .connect_timeout(Duration::from_secs(5)) .timeout(Duration::from_secs(30)); // 整个RPC超时内存泄漏排查Rust极少内存泄漏但ArcT循环引用会导致泄露。用weak打破循环use std::sync::{Arc, Weak}; struct Node { parent: WeakNode, children: VecArcNode, }4.4 跨层协作三语言共存的摩擦点提示语言间的边界不是墙而是需要精心设计的“海关”。时间戳对齐Python用time.time()秒级浮点Rust用SystemTime::now()纳秒级TypeScript用Date.now()毫秒级。统一方案全部用毫秒级i64在边界处转换// Rust to Python: (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64) // Python to Rust: Duration::from_millis(timestamp_ms as u64)浮点数精度Python的float、Rust的f64、TypeScript的number都是IEEE 754双精度但JSON序列化时可能丢失精度如0.1 0.2 0.30000000000000004。解决方案传输时用字符串解析时用decimal或BigDecimal{ amount: 123.45 } // 而不是 { amount: 123.45 }错误码映射Python抛ValueErrorRust返回Result_, Boxdyn ErrorTypeScript用throw new Error()。统一错误码体系enum ErrorCode { INVALID_INPUT 4001, MODEL_NOT_FOUND 4041, INTERNAL_ERROR 5001, }所有层都返回{ code: 4001, message: user_id is empty }前端统一处理。5. 常见问题速查表从报警到修复的黄金15分钟问题现象可能原因快速定位命令修复方案模型线上AUC比离线低15%特征计算逻辑不一致如离线用Pandas agg线上用SQLcurl -s http://localhost:8000/debug/features?user_id123 | jq对比离线特征值在Python层统一特征计算函数线上服务调用该函数而非重写SQLAPI P99延迟从200ms升至2sRust特征引擎内存泄漏Arc循环引用ps aux --sort-%mem | head -10查看Rust进程内存用cargo install flamegraph生成火焰图定位Arc::new未释放点TypeScript前端调用报422 Unprocessable EntityZod Schema更新后前端未重新生成类型定义grep user_id node_modules/types/api/index.d.ts检查字段是否存在运行npm run generate-types调用Zod的ts-json-schema-generatorPython训练进程OOM KilledDataLoader的num_workers过高每个worker复制整个模型cat /proc/$(pgrep -f python train.py)/status | grep VmRSS查看内存将num_workers设为min(32, os.cpu_count())pin_memoryTrueRust gRPC服务连接拒绝tonic服务器未监听0.0.0.0只监听127.0.0.1netstat -tuln | grep :50051在Rust server中用SocketAddr::from(([0,0,0,0], 50051))最后分享一个小技巧我们给每个AI服务部署一个/health/live和/health/ready端点但/ready不仅检查进程存活还验证关键依赖app.get(/health/ready) def ready_check(): # 检查模型加载 if not hasattr(app.state, model): return {status: down, reason: model not loaded} # 检查特征服务连通性 try: requests.get(http://feature-engine:50051/health, timeout1) except: return {status: down, reason: feature engine unreachable} # 检查Redis连接 if not redis_client.ping(): return {status: down, reason: redis down} return {status: up}Kubernetes的liveness probe用/health/live只检查进程readiness probe用/health/ready检查全链路。这样当特征服务宕机时K8s会自动将流量从该Pod摘除而非让请求失败——这才是真正的“弹性”。
返回列表