过去十年,"云原生"架构建立在一个 20 年前的假设上:状态存储在数据库,计算是无状态的。如果你想扩展,就垂直扩展数据库(换更大的机器),然后水平扩展应用服务器(增加更多机器)。任何请求都可以发送到任何服务器,负载均衡器无需关心,数据库才是唯一真相来源。
但 LLM 和 AI Agent 正在悄悄打破这个假设,让这种架构越来越难用。Zak Knill 在其博客中指出了三个微妙的方式。
- 长时间运行任务:一个执行 10 分钟任务的 Agent 不是一个"请求",而是一个长时间运行的异步进程。传统 HTTP 请求-响应模式无法处理这种长时间运行的计算。
- 有状态计算:Agent 可能运行多轮对话,处理多个工具调用,依赖累积的上下文。这种状态不是真正的"数据库状态",而是 Agent 的记忆。
- 双向交互:用户想观看 Agent 思考过程,想中断并重定向它。这是对一个进程说话,而非查询无状态 API。
轮询:无奈的 workaround
HTTP + 负载均衡器 + 无状态服务器无法将请求路由到特定进程,只能路由到数据库。所以当客户端想与运行在持久执行框架中的进程通信时,大家又回到了轮询这个万能解决方案——但轮询的体验糟糕:延迟选择、数据库负载、无效请求、流媒体 UX 极差。
轮询本质上是用数据库当消息队列。这是人们在没有真正的消息队列之前做的事。
缺失的路由原语
Knill 认为当前架构缺失一个根本的路由原语:一个可路由的传输名称,而不是服务器。我们想说:"把这个消息投递给正在产生工作流 X 输出的任何人",而无需知道是哪个机器、哪个服务器副本、哪个进程。
解决方案:pub/sub channels
Pub/sub 频道反转了所有权。服务器进程和客户端都不是可寻址的,传输层才是可寻址的。客户端和服务器都通过名称连接到 pub/sub 频道,获得双向、有状态的通信。频道才是地址——与 WebSocket 不同,它不是连接。它是一个持久的频道,即使连接断开也能重连并继续与同一进程通信。
LLM 让问题更明显
在 LLM 之前,如果连接断开,请求足够便宜,可以重试,而且可能得到确定性响应。但 LLM 的响应是非确定性的,而且不便宜。如果你在为 token 付费,你不会想因为客户端进了隧道、连接断开就浪费它们。你也不想为了使应用对客户端连接问题有韧性,而把每个 token 都写入数据库。
LLM 正是由于非确定性和昂贵,让当前架构的局限更加可见。
参考来源:https://zknill.io/posts/llms-are-breaking-20-year-old-system-design/