提前备好一批容器,45 秒变成零点几秒,代价是它们一直在空转
Agent 沙盒系列第三篇。温池就是"机场排队等客的出租车",快是快了,但空车也在烧油。这一篇也照实写出这版调度写得不好的三个地方——它们正是后面几篇的起因。
上一篇的结论是:隔离没错,错的是等用户来了才开始准备。那就提前准备。
这个思路有个现成的类比:机场出租车候客区。你出了航站楼直接上车,是因为车早就排在那儿等了——不是你打电话之后司机才从城里出发。
代码:三个部件
一、一排等着的容器。 就是让系统一直保持 5 个容器开着:
spec:
replicas: 5 # 常驻 5 个,随时待命
二、每个容器会自报“我闲不闲”。
is_busy = False # 我现在忙不忙
@app.get("/health") # 别人问我状态,我就回答闲 / 忙
def health_check():
return {"status": "busy" if is_busy else "idle"}
三、一个调度员。 请求来了,它挨个问过去,找到第一个闲着的就把活派过去:
for pod in pods: # 挨个问
resp = await http_client.get(f"...{pod}/health")
if resp.json().get("status") == "idle":
return pod # 找到闲着的,派活
请求路径上不再有“造容器”这一步,也不再需要不停地问管理系统“好了吗”。等待从 45 秒掉到零点几秒——具体多少取决于机器和池子大小,接口的返回值里就带着这次实测的时间,自己跑一次看到的才算数。
快了几百倍,代码反而更短。这一步的爽快感很有欺骗性,所以下面这半篇更重要。
账单:这几百倍是租来的
出租车能秒接客,是因为有一排车在那儿空等。空车不载客,但司机在、油在烧、车位占着。温池是一模一样的交易:用一直花着的钱,换用户不用等。
于是问题从技术题变成了经营题:
- 备几辆才够? 看的不是平均客流,是高峰客流。池子空了,用户拿到的是一句“暂时没有空闲资源”,比等 45 秒还难看;
- 怎么随客流调整? 临时加车本身要走一遍那 45 秒,所以扩容必须赶在高峰之前,而不是等高峰来了再反应;
- 能不能少备点? 能,代价是高峰更容易空池;也可以低谷时收到 0,代价是第一个客人重新等 45 秒。
这里没有标准答案,只有你的业务能接受的那个位置。但它必须被明确地选一次——温池最危险的地方,恰恰是它让延迟问题看上去“已经解决了”。
这版调度写得不好的三个地方
代码在仓库里,毛病也一并留着,因为它们正好是后面几篇的起因。
一、问和派之间有空档。 调度员先问“你闲吗”,得到“闲”,再把活派过去——这两步之间隔着一次网络往返。人一多,两个请求可能同时选中同一个容器;慢的那个被回一句“我已经在忙了”,而调度员没有重试,直接把这个错误丢给了用户。
就像两个调度员同时把同一辆空车派给了两位客人:先到的上车,后到的站在原地,而且没人再给他叫下一辆。
二、问一圈的成本随规模涨。 5 个容器时无感;50 个时就是最多 50 次网络往返,每次还要等超时。“找一个空闲的”这件事本身,变成了新的延迟来源。
三、满了只会拒绝。 没有排队叫号的概念,高峰只能靠把人挡在门外来消化。
还有一处实现上的取巧要说明:调度员是通过本地的 kubectl proxy 隧道去访问容器的。本地开发这么干没问题,但它不是生产做法——生产里应该走服务发现或直连容器地址。
下一堵墙
上面这些都还能忍。真正致命的是:“我忙不忙”这个状态,和任务进度一样,只存在容器自己的内存里。
容器一挂,两样一起没。而这批容器是随时会被系统回收、被滚动更新、被机器故障带走的——这正是下一篇要动手拆的。
一句话版本:速度是拿常年空转换来的;这笔交易必须明着算一次,而不是等账单来了才发现。
- Kubernetes
- AI Agent
- 冷启动
- 容量规划
- 架构演进