1. 我为什么放弃“手工部署”这条老路
先讲一段我自己的真实经历。2022年那会儿,我还在一个小团队里做推荐算法落地,团队里算法工程师写模型、后端工程师写接口、运维负责发版,听起来分工挺清楚,实际上每次模型上线都是灾难。模型训练好之后,算法同学把权重文件传到NAS上,再发一条消息:“模型在xxx目录,yaml格式,跑一下试试。”然后我把模型文件拷到服务器,手动配Python环境,手动装依赖,手动起Flask服务,手动测接口,手动改Nginx配置把流量切过去——整个过程全靠一张Excel表格和聊天记录撑着。最惨的是有一次新模型效果回退,想往回滚,结果上一版模型的配置文件已经不知道被谁覆盖了,只能重新训练。那一次之后我就确定,模型推理部署这件事,如果不自动化,最后一定会在“人”这个环节出问题。
这个标题——“AI模型推理自动化部署架构”,说的就是解决这个问题的整套思路。它不是某个单一工具,也不是一条命令搞定的事,而是一整套把模型从训练环境搬进生产环境、把手动操作变成流水线的系统设计。它要解决的核心问题有三个:一是模型版本管理混乱,二是部署过程依赖手工操作,三是上线后没有可观测性,出了问题只能靠“猜”。我写这篇文章,就是把我踩过的坑和重新梳理过的架构方案完整讲一遍,技术栈偏Kubernetes和容器化,但核心思路对只用Docker甚至裸机部署的团队同样适用。
在正式拆解之前,先给一个整体认知:自动化部署不是“按一个按钮然后把模型跑起来”,而是把“验证—构建—发布—观测—回滚”这五件事全部工程化。只有当你把这条链路跑通,才能说你的模型推理系统算是有架构了。下面我按实际落地顺序,把每个环节的设计思路和关键细节展开讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模型从训练到服务的生命周期:这条流水线拆开来看有四段
很多人一说到自动化部署,第一反应就是“写个脚本调用一下”。但模型推理部署和普通Web服务部署有个本质区别:模型本身是“有状态”的产物。它有一个体积不小的权重文件,依赖特定的运行时版本(比如PyTorch或者TensorFlow的某个小版本),推理结果还受输入数据分布和推理参数的影响。所以你不能把它当作普通代码包来发布。我梳理下来的完整生命周期分为四段:模型注册、镜像构建、服务发布、观测反馈。
2.1 第一段:模型注册——给每个模型一张“身份证”
模型注册是整个流水线的起点,但不是简单地把模型文件扔到一个共享目录里就完事。你需要一个Model Registry(模型仓库),它至少要记录四类信息:模型ID、版本号、模型文件索引(存在哪个存储路径)、元数据(框架版本、输入输出格式、评估指标)。我早期用过MLflow来做模型注册,因为它是开源方案里做得比较轻量的,和Python生态结合得很好。后来规模大了,换成了Seldon Core自带的模型仓库能力,配合对象存储使用。
这里有一个很容易踩的坑:模型文件名和版本号脱节。有的团队喜欢把模型文件命名成model_final_v3.pth这种风格,看起来有版本,其实到了回滚的时候根本分不清哪个是真正上线过的版本。我建议从第一天起就强制要求:模型版本号由系统生成,不让人手动命名;每个上传的模型在注册时自动打上Git提交号、训练时间、评估指标等标签,这样后续无论回滚还是审计,都能精准定位。
2.2 第二段:镜像构建——把环境依赖“锁死”
模型注册完,下一步是构建推理镜像。这一步为什么要单独拎出来讲?因为推理环境是“脆弱的”——你在训练环境用torch 1.13跑通的模型,放到装了torch 2.0的生产环境,可能输出结果就不一样了(很多算子实现变了)。我见过太多次“本地没问题上线就出问题”,最后排查半天发现是依赖版本变了。
所以构建阶段的核心原则是“锁依赖”。推荐做法是用Dockerfile把Python包依赖固化成requirements.txt,并且精确到补丁版本号,不要用>=这种范围写法。举一个我常用的Dockerfile片段:
dockerfile复制FROM pytorch/pytorch:1.13.1-cuda11.6-cudnn8-runtime
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY model_store/ /app/model_store/
COPY inference_server.py .
ENV MODEL_PATH=/app/model_store
ENV PORT=8080
EXPOSE 8080
CMD ["python", "inference_server.py"]
这里有一个细节:基础镜像不要用latest标签。latest在你今天构建的时候可能指向1.13.1,三个月后可能就指向2.0了,你的依赖锁了个寂寞。我在项目里甚至会对基础镜像做二次校验,在CI流程里检查基础镜像的SHA256是否和预期一致,防止上游镜像被覆盖。
2.3 第三段:服务发布——把流量安全地切过去
镜像构建好之后,就是发布环节。发布本身不是“把容器跑起来”这么简单,而是要考虑新旧版本如何过渡。我常用的策略是蓝绿发布(Blue-Green Deployment):先启动一组新版本的Pod,等健康检查全部通过后,再在网关层把流量从旧版本切到新版本;切完之后,旧版本不立即销毁,而是保留一段时间,方便快速回滚。
2.4 第四段:观测反馈——验证模型上线后真的在“好好干活”
这是最容易被人忽略的一段。很多团队部署完模型就松了一口气,觉得“服务起来了,任务完成”,但模型推理不是“起来就行”,而是“起来之后要持续正确”。我强调的观测是全链路的,不只监控服务器的CPU和内存,还要监控模型的推理质量指标。
具体来说你要埋至少三类指标:第一类是系统指标,包括CPU使用率、内存使用率、GPU利用率、请求QPS、P99延迟;第二类是模型指标,包括预测结果的分布、置信度均值、异常输入比例;第三类是业务指标,比如推荐场景的点击率、CV场景的准确率。前两类可以靠Prometheus加Grafana搞定,第三类就要业务方配合上报了。我在这套架构里还做了一条数据回流通道:把线上请求的原始输入和模型预测输出做采样,存到数仓里,后续用于模型漂移检测——现在有些平台把这个能力叫Model Monitoring,但它本质就是“给模型装个行车记录仪”,非常重要但往往最后才有人想起来做。
3. 部署架构的核心模块:从网关到推理引擎的完整链路
聊完生命周期,再来看看这套自动化部署架构在运行时的“骨架长什么样”。我把整个推理系统的运行时架构分成四层:接入层、调度层、推理层、存储层。每一层都有自己独立的职责,层与层之间通过接口通信,尽量减少耦合。
3.1 接入层:统一入口,屏蔽后端差异
接入层最常见的实现就是API网关。它的作用不只是转发请求,更重要的是路由策略——同一个模型的不同版本怎么分流?不同模型的不同服务怎么寻址?模型升级时流量怎么平滑切换?这些都是网关的职责。
我在这套架构里用了Kubernetes Ingress加自定义路由规则来实现。核心思路是:每个模型版本封装成一组独立的Deployment,Service的名字带版本号(比如recommend-model-v3),Ingress规则按请求头或查询参数把流量分配到不同版本。举个配置示例:
yaml复制apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: model-gateway
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10"
spec:
rules:
- host: inference.internal.example.com
http:
paths:
- path: /v1/recommend
pathType: Prefix
backend:
service:
name: recommend-model-v3
port:
number: 80
这个配置利用了Nginx Ingress的canary能力:默认10%的流量打到v3版本,剩下90%还走v2。等v3的指标确认没问题,再把权重调高到100%。整个过程不需要重启服务,改下注解apply一下就好。
3.2 调度层:管理实例的“生老病死”
调度层是这个架构里最“自动化”的部分,我直接基于Kubernetes原生能力来做。Kubernetes的Deployment负责保证实例数量,HPA(Horizontal Pod Autoscaler)负责按负载伸缩,NodeSelector配合taint/toleration来管理GPU节点的调度。
调度层有一个特殊的场景要处理:GPU显存管理。如果你的模型跑在GPU上,一个GPU卡你可能想塞多个推理实例,但Kubernetes默认把GPU当作可整除的资源来调度,实际上显存有碎片化的问题。我的做法是用NVIDIA的Device Plugin配合显存预留策略,每个推理Pod声明nvidia.com/gpu: 1(一整块卡),但通过MIG或者显存切割技术把一块物理GPU切成多个小分区,提高卡利用率。这块如果你的团队刚起步,建议先不搞那么复杂,一块卡跑一个模型,等业务量起来了再优化。
3.3 推理层:模型服务的“心脏”,要认真设计启动逻辑
推理层就是我们说的推理服务本身。它决定了请求进来后怎么调用模型跑前向计算,再返回结果。这里有一个长期被低估的点:模型的加载策略。
一个常见的设计错误是:模型在服务进程启动时才从磁盘加载。这意味着每次新Pod创建都要读取一次几百MB甚至几个GB的模型文件,冷启动时间可能长达几分钟。在自动扩缩容场景下,这就很尴尬——流量高峰来了,HPA检测到QPS上升然后扩容新Pod,结果新Pod光加载模型就要五分钟,等它ready,高峰已经过去了。我的方案是把模型加载分成两段:Pod启动时先只声明“加载中”状态,Kubernetes的readinessProbe返回失败,让流量暂时不流入;同时模型文件提前复制到本地节点的缓存目录,启动时优先从缓存加载,只有缓存缺失时才走远程拉取。实测下来,冷启动时间从5分钟压缩到40秒以内。
推理服务本身的代码,我推荐做请求级批处理(Dynamic Batching)。简单说就是:当多个请求在很近的时间内到达时,把它们攒成一个batch一起喂给模型,提高GPU利用率。这个逻辑用Python实现其实不复杂,核心是维护一个积压队列,设定最大batch大小和最大等待时间,两者满足任一条件就触发推理。我贴一段简化版的实现思路:
python复制import asyncio
import numpy as np
class BatchInferenceEngine:
def __init__(self, model, max_batch_size=8, max_wait_time=0.05):
self.model = model
self.max_batch_size = max_batch_size
self.max_wait_time = max_wait_time
self.queue = asyncio.Queue()
async def predict(self, input_data):
future = asyncio.get_event_loop().create_future()
await self.queue.put((input_data, future))
return await future
async def _process_batch(self):
while True:
batch = []
first_item = await self.queue.get()
batch.append(first_item)
# 等待更多请求进入,直到达到最大batch或超时
try:
while len(batch) < self.max_batch_size:
item = await asyncio.wait_for(self.queue.get(), timeout=self.max_wait_time)
batch.append(item)
except asyncio.TimeoutError:
pass
# 组装batch并推理
inputs = np.stack([item[0] for item in batch])
outputs = self.model.predict(inputs)
for item, output in zip(batch, outputs):
item[1].set_result(output)
这段代码虽然简略,但已经把批处理的核心逻辑表达清楚了:_process_batch是一个常驻任务,不断从队列里取数据,攒够一批或者等到超时就执行推理,推理结果通过Future回传给各个等待的请求。实际生产里还需要处理异常、超时和并发安全问题,但思路就是这样。
3.4 存储层:模型文件不是放在本地磁盘就完了
存储层管的是模型文件本身的存放位置。生产环境我建议不要依赖容器本地磁盘保存模型——Pod重建或者漂移到别的节点,模型文件就丢了。标准做法是使用对象存储(比如MinIO、AWS S3、阿里云OSS)作为模型文件的主存储,Pod启动时从对象存储拉取到本地缓存目录。
这里有一个必须提前设计的点:存储和计算分离。我见过有的团队图省事,把模型文件放在NFS上,所有Pod直接挂载NFS读文件。一开始没问题,但模型体积大了之后,NFS的IO很容易成为瓶颈,而且NFS本身的高可用又要单独维护。对象存储的方案则在弹性上更占优,模型文件只在加载时拉取,之后完全走本地内存和GPU显存,不产生持续的存储IO。
4. 灰度发布与弹性伸缩:自动化部署的核心目标不是“省事”,是“可控”
前面讲的是静态架构,这一部分重点讲动态行为——发布和伸缩。我认为这是“自动化部署”和“脚本部署”之间最大的分水岭:脚本部署追求“少敲几次命令”,自动化部署追求“每次变化都可控、可回退、可观测”。
4.1 发布策略:金丝雀发布为什么比蓝绿更细
蓝绿发布的最大优点是回滚快,但缺点是切换成本高——你至少要同时保持两套完整环境的资源。如果你只有一组GPU节点,蓝绿意味着你要准备双倍的GPU资源才能支撑,这在成本上有点奢侈。**金丝雀发布(Canary Release)**则更细粒度:每次只把一小部分流量(比如5%)切到新版本,验证没问题后逐步放大比例,直到100%。
金丝雀发布带来的额外要求是:你必须有一套流量控制的机制。在前面接入层那部分我已经展示了Ingress的canary注解,这里再补充一个线上经验:不要只按百分比做灰度,还要结合“按请求参数分流”。举个例子,如果你想先让内部测试账号体验新模型,可以在网关层配置一条规则:请求头携带X-Canary: true的请求一律转发到新版本,其余请求按百分比放量。这样既能定向验证,又能对整个系统的稳定性做一个渐进式确认。
4.2 弹性伸缩:不只是“CPU超过80%就扩容”
Kubernetes的HPA默认支持按CPU使用率扩容,但对推理服务来说,CPU利用率并不是一个足够灵敏的信号。更合理的指标是基于QPS的伸缩——因为模型推理是典型的CPU/GPU密集型任务,请求量一上来,CPU必然升高,但CPU升高的时刻,延迟可能已经明显恶化了。所以更好的做法是按请求队列长度或者自定义QPS指标来驱动扩缩容。
我这边实测效果最好的一套配置是:用Prometheus Adapter把推理服务的QPS指标暴露给HPA,设置当单Pod QPS超过100时扩容,低于50时缩容。同时给了HPA一个“冷却时间”参数(--horizontal-pod-autoscaler-sync-period默认15秒,配合stabilizationWindowSeconds防止抖动),避免K8s在流量波动时频繁扩缩Pod。这里有个血泪教训:刚开始我们没有配置缩容稳定窗口,结果一个瞬时流量小高峰导致HPA把副本数从3扩到10,流量平复后5分钟内又缩回3,这期间一缩一扩,服务重启了好多次,GPU节点被折腾得够呛。
弹性伸缩还有一个容易被忽略的前置条件:Pod要能快速Ready。前面提到的模型本地缓存就是为这个准备的。如果你是靠每次启动都拉模型文件,那HPA的扩容效果会大打折扣,因为新Pod迟迟不Ready,HPA会继续扩容,最后可能把集群资源都耗尽。记住一个原则:推理Pod的启动时间必须控制在秒级,否则弹性伸缩就是空中楼阁。
4.3 自动回滚:自动化系统的最后一道保险
自动化部署做得再好,也难免有意外。所以我强烈建议在流水线里加入自动回滚机制,触发条件可以是:新版本上线后5分钟内错误率超过1%、P99延迟超过阈值、或者健康检查连续3次失败。一旦触发,CI/CD系统自动执行回滚操作,把Ingress流量全部切回上一个稳定版本。
这里有一个设计取舍:自动回滚不是越灵敏越好。如果阈值设得太低,一个小抖动就触发回滚,业务连续性反而被打破;设得太高,又起不到保护作用。我常用的做法是分两档:第一档是“警告”,指标异常只发告警,由值班人员判断是否人工回滚;第二档是“熔断”,比如错误率超过5%持续时间超过3分钟,自动回滚且通知所有人。这样既避免了“狼来了”效应,又兜住了最严重的故障场景。
5. 落地过程中踩过的坑和对应解法
讲完了架构设计,我整理了几个实际踩坑记录。这些坑都很典型,几乎每个团队在搭建类似系统时都会碰上。我按“现象—排查过程—根因—解法”的结构写,方便大家对照排查。
5.1 Python依赖冲突:同一个环境装两套框架直接崩溃
现象:一个推理服务里既要加载TensorFlow模型,又要加载PyTorch模型,按顺序import的时候后者直接Segmentation Fault崩溃。
排查过程:一开始怀疑是CUDA版本问题,查了NVIDIA驱动,正常;后来怀疑显存不足,看指标也正常。最后把问题定位到protobuf库冲突——TensorFlow和PyTorch都用protobuf,但各自对版本要求不同,导致底层二进制接口错乱。
根因:依赖隔离没做好。一个容器里硬塞两套深度学习框架,迟早出问题。
解法:两个方案任选:一是把两个模型拆成独立的推理服务,通过内部API互相调用(我推荐这个,职责更清楚);二是用虚拟环境隔离依赖,但容器里再套虚拟环境会增加镜像体积和调试复杂度,不推荐。
5.2 模型加载路径写死,导致多个版本无法共存
现象:线上同时跑v1和v2两个版本,但v2上线后v1的请求偶尔报错,报错内容指向模型文件不存在。
排查过程:查了日志,发现两个版本的Pod挂载的是同一个hostPath目录,新版本发布时把这个目录里的模型文件覆盖了。不是v2有问题,而是v1被“误伤”了。
根因:模型文件和代码没有做到“版本绑定”。代码用的是代码库标签,模型文件用的是共享目录,两套版本体系没有对应起来。
解法:模型文件路径里必须带上版本号,比如/models/recommend/v1/model.pkl、/models/recommend/v2/model.pkl,并且在部署时把模型文件的引用作为环境变量注入,不允许在代码里写死路径。再加一道校验:Pod启动时检查模型文件的元数据(版本号、哈希值),不一致就直接不启动。
5.3 GPU显存泄漏,跑几天后服务OOM
现象:推理服务刚上线时GPU显存占用正常,但运行48小时后显存占用持续增长,直到OOM,Pod被Kill然后重启,循环往复。
排查过程:用nvidia-smi观察显存变化,发现每处理一定数量的请求后,显存就有小幅但不回落的增长。怀疑是推理框架的显存缓存机制问题,但又换过不同框架,仍然复现。
根因:这类问题很多是数据加载部分有引用泄漏。比如某个Python对象在处理完请求后没有被正确释放,导致GPU上的tensor无法被回收。还有一个常见原因是推理框架的缓存机制——有些框架为了减少重复分配开销,会缓存一部分显存,属于正常现象,但缓存阈值设得不对就会一直增长。
解法:第一,在代码里显式调用torch.cuda.empty_cache()可以缓解,但不能根治(这只是把缓存释放回PyTorch的内存池,不是还给操作系统)。第二,真正有效的是做“周期重启”——用Kubernetes的CronJob定期滚动重启推理Deployment,比如每天凌晨低峰期重启一次,把潜在的泄漏“清零”。第三,长期方案是定位具体泄漏点,通常要结合heap profiling工具,这个工作量比较大,适合上线稳定后慢慢优化。
5.4 服务发现与负载均衡的隐蔽问题
现象:请求偶尔出现超时,日志里能看到“Connection refused”或者“No healthy upstream”,但大部分请求是正常的。
排查过程:刚开始以为是Pod资源不足,但看CPU、内存指标都不高。后来用kubectl get endpoints检查Service对应的Endpoint列表,发现有部分Pod的地址反复在“存在”和“消失”之间横跳。
根因:这是Kubernetes的探针(livenessProbe和readinessProbe)配置不合理造成的。我们的readinessProbe探测的是“模型是否有加载完”,但模型加载完后这个探针一直返回成功,即使推理服务内部已经出现了线程阻塞,探针也无法感知。结果是Service仍然把请求转发给它,请求被堵在队列里,最终超时。
解法:readinessProbe不能只看进程活着没有,要探测“服务是否真的能处理新请求”——最好的方式是给推理服务加一个轻量的/healthz接口,接口里做两件事:检查模型是否已加载、检查请求队列深度是否超过阈值。如果队列堆积超过一定量,就返回500,让K8s把该Pod摘出负载均衡池。这个改动让我们的超时问题几乎绝迹。
5.5 镜像仓库权限管理失控
现象:某天发现生产环境的Pod镜像变成了一个不存在的版本,服务起不来,排查半天发现是镜像tag被覆盖了——有人在测试环境构建镜像时用了和线上一样的tag,推送到了同一个仓库。
根因:镜像tag的命名和管理不规范。用latest或者固定的版本号做tag,后面推的镜像就会覆盖之前的tag,但Pod的imagePullPolicy如果是IfNotPresent,新Pod起来时可能拉到了旧缓存,也可能拉到被覆盖的新镜像,完全取决于节点缓存状态,太不可控了。
解法:构建时给镜像打唯一的tag,推荐带上Git commit,例如recommend-inference:3a2f91b。这样每次构建都是全新的,不可能覆盖。同时设置镜像仓库的不可变tag策略,一旦某个tag被推送,任何覆盖推送都会被拒绝。这一条是成本最低、收益最高的规范,强烈建议从第一天就执行。
6. 从零搭建的三种路径:按团队规模选方案
架构和踩坑都讲完了,最后聊一聊怎么落地。不是所有团队一上来都需要Kubernetes+自研监控这套重方案。我按团队规模和资源情况,给出三条可参考的路径。
6.1 路径A:小团队或原型验证,用Docker Compose过渡
如果你的团队只有两三个人,模型一天推理量也就几万次,那直接上K8s就是给自己增加负担。这时候用Docker Compose把推理服务、模型仓库(本地目录)、监控(Prometheus+Grafana)编排起来,每天手工或者写个简单的Shell脚本做发布,完全够用。我在早期就是这个阶段过来的,很多架构理念其实在这个阶段就能实验出来,比如镜像构建、模型注册、请求批处理。
这条路径的局限是:没有自动扩缩容,没有故障自愈,发布回滚靠脚本。但没关系,先跑起来,等业务量起来了再迁移。
6.2 路径B:中型团队,Kubernetes + GitLab CI/CD的标准组合
大部分推荐系统、内容理解、风控系统的推理平台,用这套组合就非常合适了。我现在的团队也是走这条路。具体分工:
- GitLab仓库放推理代码和Dockerfile,CI/CD流水线负责镜像构建和推送
- 模型注册用MLflow或Seldon Core自带仓库
- Kubernetes负责调度和弹性伸缩
- Ingress Nginx负责流量管理
- Prometheus + Grafana负责监控
- 自定义一个自动化脚本或平台,负责触发发布、判定健康、执行回滚
这条路径的投入大概是一个人维护一个月左右就能跑通,之后就是逐步优化灰度策略和扩容策略。这里我强调一个经验:先别急着造管理平台。很多团队第一步就想做一个漂亮的Web界面来管理模型发布,结果平台没做完,部署还是靠手。我建议是先用GitLab CI的Pipeline YAML把发布流程串起来,界面后期再说。
6.3 路径C:规模化团队,引入专业MLOps平台或自研控制面
当你的模型数量达到几十个、每天都有多个版本上线时,自研控制面或者引入商业MLOps平台就是必然选择了。Kubeflow、Seldon Core、BentoML这些开源项目都提供了丰富的模型管理和部署能力,可以大幅减少自研成本。商业平台比如阿里云的PAI、AWS SageMaker也有成熟的模型部署和监控能力,适合不想在基础设施上投入太多人力的情况。
不过要提醒一句:引入平台不等于万事大吉。平台的抽象层会掩盖很多底层细节,一旦出问题,排查难度反而更大。我见过一个团队用Kubeflow做推理平台,后来出现一个奇怪的GPU显存分配问题,排查了快一周,最后发现是底层K8s版本的bug。所以无论选哪条路径,团队里一定要有至少一个人对Kubernetes和容器原理有深度的理解,这个不是可选项,是必要项。
7. 关于这套架构,我最后想说的话
写到这里,整套架构的思路基本讲完了。从模型注册、镜像构建到发布、控制、观测、回滚,这套方案已经在我的多个项目中跑了一年多,整体效果很稳定。最后分享几个我做这件事的体会。
第一,自动化部署的价值曲线不是线性的,而是阶梯式的。 当你只自动化了构建环节,省下的时间可能只有几分钟;当你把灰度、回滚、观测全部打通后,省下的不是时间,而是“事故处理成本”——原来一次模型上线事故可能要折腾两三个小时,现在大多数情况系统自己就处理掉了,人只是看着。这个转变非常巨大,但需要坚持把链条走完。
第二,能“看见”比能“部署”更重要。 我见过很多团队把部署自动化做得很好,但监控只有“服务是否活着”这个维度。模型推理系统里,最怕的不是服务挂了,而是“服务活着但效果变差了”。埋好模型效果指标和请求采样日志,你的自动化系统才真正有闭环的“眼睛”。
第三,这套架构最核心的并不是技术选型,而是“版本意识”。 模型有版本、镜像有版本、配置有版本、回滚目标有版本——只有把每个环节的版本都管理清楚,自动化才不会变成“自动制造混乱”。这个意识从第一天就要有,不要在混乱中指望后期重构能救回来。
如果你现在刚好在搭自己团队的推理部署架构,可以先把这篇文章里的链路图画在心里,然后从最小可用的闭环开始,一个环节一个环节地补。这个过程没有想象中那么难,但每补完一环,你的系统都会肉眼可见地结实一点。
