5.2 可扩展性与资源管理


5.2 可扩展性与资源管理

扩展性的本质是隔离。单机跑一个 GroupChat 没问题,跑一百个并发会话时,资源(内存、模型连接、执行环境)会互相踩。这一节讲怎么把系统从"能跑"扩到"能同时服务很多用户"。

隔离单位:每个会话一个 GroupChat

最稳的扩展方式是"会话级隔离"——每个用户会话拥有独立的 Agent 实例和对话历史,不共享可变状态。这样扩节点只需水平加进程,互不干扰。

from autogen import ConversableAgent, GroupChat, GroupChatManager import os cfg = {"model": "gpt-4o-mini", "api_key": os.environ.get("OPENAI_API_KEY")} def build_session(): # 每个会话新建一组 Agent,状态天然隔离 coder = ConversableAgent("coder", llm_config=cfg, system_message="你写代码。") reviewer = ConversableAgent("reviewer", llm_config=cfg, system_message="你审。") g = GroupChat(agents=[coder, reviewer], messages=[], max_round=6) return GroupChatManager(group=g, llm_config=cfg) # 用户 A 和 B 各自一个 session,互不影响 session_a = build_session() session_b = build_session()

执行环境的隔离

函数执行默认在主进程,多会话混跑时一个会话的脏状态可能漏给另一个。生产用容器或沙箱隔离每个执行环境,比如给每个会话起独立容器。

# 用 docker 沙箱隔离代码执行,避免会话间互相污染 executor = UserProxyAgent("exec", human_input_mode="NEVER", code_execution_config={"use_docker": "python:3.11-slim"}) # 每次执行在独立容器里,跑完即弃,状态不残留

资源上限与排队

模型连接数、并发线程数都要有上限,超出就排队。我们见过不设上限把模型服务打挂的案例。用一个信号量控并发最省事。

import threading sem = threading.Semaphore(5) # 最多 5 个并发会话调模型 def safe_run(session, msg): with sem: return session.initiate_chat(session, message=msg, max_turns=6) # 超出的会话自动等待,保护下游模型服务

无状态化与水平扩展

把对话历史存外部(见 3.5),Agent 本身做成无状态工厂函数,就能任意水平加副本。这是扩到大规模的关键一步。

工程取舍

  • 会话级隔离:最稳,但内存占用随会话数线性涨。
  • 共享 Agent:省内存,但状态易串,只适合只读场景。
  • 沙箱执行:安全,但启动有开销。

我们主张:默认会话级隔离 + 沙箱执行,成本换稳定性,规模上来再优化。

水平扩展的部署示意

把 Agent 工厂和会话隔离包进一个服务,每个请求起独立会话,前面加负载均衡,就能水平加副本。

# 服务侧:每个请求独立 session,互不影响 def handle_request(user_msg: str): session = build_session() # 新建隔离的 GroupChat with sem: # 控并发,保护模型服务 return session.initiate_chat(session, message=user_msg, max_turns=6) # 多副本部署时,请求被负载均衡分到不同进程,各自隔离

内存与成本的权衡

会话级隔离最稳,但每个会话都占内存存历史。如果用户量巨大,可以"热会话"留内存、"冷会话"历史落库(见 3.5),需要时再读回。这是个典型取舍:内存换速度,落库换成本。我们建议先全内存扛住中等规模,再优化冷存。

一个容量规划的经验值

单个 GroupChat 会话的历史在长对话下可能到几万字符,对应几十 KB 内存。千级并发就是 GB 级,单机很快到顶。所以规模上来必须落库+多进程。我们见过团队在单机用全局字典存所有会话,并发一高就 OOM——根因就是忘了会话状态会累积。把状态当资源规划,而非当作"反正有内存",能避开这类坑。

限流是扩展的第一道墙

很多系统不是被并发压垮,而是被模型服务的限流挡死。并发一提,大量请求被限流返回,要么重试风暴要么直接失败。所以信号量(前面示例)不是可选项,是必选项。我们建议信号数量设为模型方限额的八成,留余量;重试加指数退避,别瞬间重发把限流打成雪崩。

import time, threading sem = threading.Semaphore(4) # 留两成余量给限额 5 def safe_run(session, msg): for attempt in range(3): with sem: try: return session.initiate_chat(session, message=msg, max_turns=6) except Exception: time.sleep(2 ** attempt) # 指数退避 # 退避+限流余量,并发才稳

一个扩展检查清单

上线前我们过一遍:会话是否隔离、执行是否沙箱、并发是否限流、状态是否落库、失败是否兜底。五条全过,基本能扛中等并发。缺任何一条,先补再放量,别等线上炸了再补。

扩展的本质一句话

扩展性不是堆机器,是让状态可被隔离、可被复制、可被替换。做到这三点,水平加副本就自然成立。我们常把这三点当扩展设计的验收标准,缺一个就还不能算"可扩展"。

本节要点回顾

  • 扩展靠会话级隔离,水平加进程。
  • 执行用容器沙箱,防状态串染。
  • 并发加信号量,保护模型服务不被打挂。

⚠️ 多会话共享同一 Agent 实例且它带可变状态,必然出现串号串数据,只读场景才考虑共享。

💡 可扩展性把"对话即编排"从单机玩具变成能服务的系统,核心是隔离而非堆机器。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U