1. 首页 > 云计算 > 正文

Amazon Bedrock AgentCore Runtime V2 会话启动优化:快照机制与四种架构模式

AgentCore Runtime V2 把容器初始化从「每次会话都做」改成「构建快照时做一次」,新会话启动不再随镜像体积线性变长。本文面向在 AWS 上部署 AI Agent 的中文开发者与中小团队,讲清 V2 的启动机制、如何测量会话启动时间,以及四种把工作移出首个请求的架构模式,并说明各自的适用条件与代价。机制与模式依据 AWS 官方博客 2026-11-13 发布的《Architecture patterns for fast session startup on Amazon Bedrock AgentCore runtime》,本文未做独立实测,具体数值需在自身环境验证。

Amazon Bedrock AgentCore Runtime V2 会话启动优化:快照机制与四种架构模式

V2 改变了什么:从每次拉镜像到一次快照

AgentCore Runtime 为每个会话提供独立的 microVM(微型虚拟机,即每个会话独占的轻量级隔离运行环境,会话之间互不影响),无任务时缩容到零,按会话消耗的资源计费。在 V1 下,每个新会话都要从 Amazon ECR 拉取容器镜像、启动容器、执行初始化代码,然后才能处理第一个请求;镜像体积因此直接决定冷启动的长短。

V2 把这段工作前移。创建或更新运行时,平台会在新的 microVM 中启动容器,等待首次健康检查 GET /ping 返回(上限 120 秒),随后清空操作系统的文件缓存、冻结 microVM,并把内存与磁盘保存为快照。新会话到来时,直接把快照恢复到新的 microVM,进程从冻结处继续运行。代价是每次创建或更新运行时都要花几分钟构建快照,这段等待体现在运行时到达 READY 的时间上,而不是请求延迟。

这里说的「初始化」,指 Agent 在 /ping 报告健康之前完成的所有工作。使用 AgentCore Python SDK 时,就是 app.run() 之前的一切;如果自己实现 HTTP 服务,则应在初始化完成后才返回健康状态。

三条事实决定代码怎么改

理解 V2,抓住三点就够了:

  • 内存完整恢复。快照时进程持有的对象、导入的模块、已创建的客户端都会在会话启动时以内存速度可用;/dev/shm 这类内存文件系统中的内容同样如此。
  • 文件缓存不保留。冻结前文件缓存已被清空,所以在初始化阶段读一遍文件,并不会让恢复后的会话读得更快。
  • 镜像文件按块惰性读取。会话首次读取某个块时才从快照存储获取,之后缓存到更靠近宿主机的位置。

由此得出一个与常规冷启动建议相反的结论:在 V2 下,静态的导入、客户端创建、静态数据加载应尽量放进初始化,而不是推迟到 handler 里。推迟的部分,每个新会话都要重付一次。

哪些工作适合前移

导入库、创建 boto3 等客户端、把提示词、配置、小索引读进对象,都属于可以在快照中保留的静态工作。注意客户端恢复后套接字已失效,boto3 会在首次调用时重连;部分其他网络客户端会先尝试旧连接,额外增加数百毫秒,这类连接应在冻结前主动关闭。

无法放进进程内存的东西是例外:作为独立进程运行完就退出的 CLI、浏览器二进制,在初始化时读过,恢复后仍要从镜像重新取。这类场景需要 build hook 或内存盘来补。

先测量,再优化

Agent 应用的延迟不是一个数字。一个请求的时间由平台准备会话、业务代码执行(模型调用、工具调用、子 Agent、流式返回)两部分组成,优化手段不同,先要判断谁占主导。

AgentCore Runtime 在 CloudWatch 的 AWS/Bedrock-AgentCore 命名空间发布两项指标:Latency 是首字节时间,OverheadLatency 是平台侧开销,包括启动 microVM 和恢复快照。业务代码耗时可用两者相减得到。初始化耗时在 V2 下不进入请求延迟,只在构建日志里出现,可在 app.run() 前打一行日志记录。

import time
INIT_START = time.time()  # 模块第一行
# 导入、客户端、数据加载 ...
print(f"init_ms={round((time.time() - INIT_START) * 1000)}")
app.run()

解读方式:新会话上 OverheadLatency 偏高,说明时间花在会话启动,应考虑后文的预启动模式;同一会话内首个请求的业务耗时明显高于第二个,说明本该在初始化做的事跑到了 handler 里,可用 AgentCore Observability 的自定义 span 定位工具初始化、文件加载和延迟导入。

看分位数而非平均值。P90、P99 或去掉最慢尾部的 TM90 更能反映用户实际感受,平均值会把慢请求藏起来。OverheadLatency 同时覆盖新会话和已有会话,比较不同版本时要保证新会话比例相近,并按生产预期的会话创建速率测试,而不是部署后跑几次就下结论。

四种架构模式

下表是决策指引,四种模式在下方小节逐一展开,按需求从轻到重排列。

你的情况 建议做法
新会话约 2 秒可接受 模式一:切换到 V2,把所有初始化放在 app.run() 之前
Agent 早期就需要大文件、CLI 或浏览器,且无法常驻进程 模式二:用 build hook 在快照构建期间加载并预热
需要亚秒级,且用户在发出首个请求前已有交互(打开应用或聊天窗口) 模式三:在用户进入界面时后台预启动会话
工具链需以独立子进程运行,如 tsc、语言服务器,且每次请求都重新启动 模式四:初始化时常驻辅助进程,或把工具链复制到内存盘

表中的「约 2 秒」是原文对典型场景的定性描述,不是本文实测值,实际数字取决于镜像、初始化内容和区域,需在自身环境验证。

模式一:把初始化全部前移

这是成本最低的一步,多数 Agent 只做这一项就能受益。要点是创建对象而不只是导入模块,因为创建对象会加载其依赖并保留在活动内存中;本地工具若依赖外部库,也在初始化时实例化;静态数据读进对象持有,而不是只读一遍。

import json, pathlib
import boto3
import pandas as pd  # 初始化时导入,包括不常用的库
from strands import Agent
from strands.models import BedrockModel
from bedrock_agentcore.runtime import BedrockAgentCoreApp

MODEL_ID = "<你的模型ID>"  # 以 Bedrock 控制台模型列表为准

# 初始化:快照前运行一次,每个新会话继承这些对象
PROMPTS = json.loads(pathlib.Path("prompts.json").read_text())
ddb = boto3.client("dynamodb")
agent = Agent(model=BedrockModel(model_id=MODEL_ID), system_prompt=PROMPTS["system"])
app = BedrockAgentCoreApp()

@app.entrypoint
def invoke(payload):
    return agent(payload["prompt"])

app.run()  # 这一行之后 /ping 才会报告健康

取舍在于:首字节时间等于启动 microVM、恢复快照加上 handler 执行时间。把工作移进初始化会缩短 handler,但内存中保留的对象会增大快照,并在每个会话中占用内存、计入费用。改完要测端到端会话启动时间,确认延迟是下降了,而不是从 handler 转移到了恢复阶段。

模式二:用 build hook 预热大文件与工具链

build hook 建立在初始化之上,不替代它。在容器中暴露一个 HTTP 端点,平台在构建期间向它发送两次 POST:第一次在快照前,加载并保留在内存中的内容会进入快照;第二次在测试恢复后,这次调用不会被快照,但可以读取会话启动时要用的文件,从而影响恢复后优先拉取哪些块。

请求头 X-Amzn-Bedrock-AgentCore-Runtime-Warmup-Type 区分两次调用,值为 memory 或 disk。两次调用共享 120 秒,磁盘调用只能用内存调用剩下的时间。只有工作完成后才返回 2xx,其他响应、连接失败或超时都会导致创建或更新失败,且平台不会重试;失败原因可在运行时日志组的构建日志流中查看。具体配置参数以 AWS 官方文档中 AgentCore Runtime 生命周期钩子章节为准。

client = boto3.client("bedrock-agentcore-control")
client.create_agent_runtime(
    agentRuntimeName="my_agent",
    agentRuntimeArtifact={"containerConfiguration": {"containerUri": IMAGE_URI}},
    roleArn=ROLE_ARN,
    networkConfiguration={"networkMode": "PUBLIC"},
    platformVersion="V2",
    hooksConfiguration={
        "build": {
            "warmup": {
                "action": {"httpPost": {"path": "/warmup", "port": 8080}}
            }
        }
    },
)

端口可省略,默认使用 Agent 的服务端口;若显式设置,需在 8000 到 12000 之间且未被平台占用。路径不能与平台已使用的路由冲突,例如 POST /invocations 或 GET /ping。另外,UpdateAgentRuntime 会整体替换钩子配置,每次更新若想保留钩子,都必须重新传入 hooksConfiguration,否则会静默丢失。

Agent 侧在同一个端点上处理两种调用,并把耗时工作放到工作线程,避免阻塞 /ping:

import asyncio
from fastapi import FastAPI, Request, Response

app = FastAPI()
STARTUP_FILES = ["/app/models/tokenizer.json", "/app/config/agent.json"]

def load_shared_state():
    # 加载每个会话都需要的重内容:重导入、模型权重、缓存
    ...

def read_startup_files():
    # 预读会话启动时要用的文件,让新会话更快命中
    for path in STARTUP_FILES:
        with open(path, "rb") as f:
            while f.read(1024 * 1024):
                pass

@app.get("/ping")
def ping():
    return {"status": "Healthy"}

@app.post("/warmup")
async def warmup(request: Request):
    warmup_type = request.headers.get(
        "X-Amzn-Bedrock-AgentCore-Runtime-Warmup-Type", ""
    ).lower()
    if warmup_type == "memory":
        await asyncio.to_thread(load_shared_state)
    elif warmup_type == "disk":
        await asyncio.to_thread(read_startup_files)
    return Response(status_code=200)

@app.post("/invocations")
async def invoke(request: Request):
    payload = await request.json()
    return {"response": payload.get("prompt", "")}

示例中的路径、端口和文件列表均为占位,实际以官方 SDK 文档和运行时配置参考为准,本文未在本地运行验证。

模式三:在用户进入界面时后台预启动

聊天、语音这类交互场景,用户打开窗口到发出第一条消息之间通常有几秒。如果 OverheadLatency 显示会话启动占主导,可以在用户进入界面时就发起一次会话创建,把等待提前到用户输入之前。这一模式需要应用侧配合,适合前端可控、能感知「用户已进入」状态的产品;对纯 API 调用、无交互前置信号的场景不适用。

模式四:能常驻的进程就别每次启动

handler 中启动的子进程,每个新会话都要重新读取二进制和库。改为在初始化时启动常驻的辅助进程,例如语言服务器或预加载完依赖的 worker,通过管道或套接字通信,它就会作为进程内存的一部分随快照恢复。无法常驻内存的工具链,可在初始化时复制到 /dev/shm 或 ramfs 挂载点,让读取走内存。Python 项目还可通过 UV_COMPILE_BYTECODE=1 或 pip install --compile 预编译字节码,减少延迟导入时的编译开销。

快照陷阱:哪些东西必须留在 handler 里

初始化期间创建的对象会被冻结进快照,直到下次部署前,每个会话都从同一份副本开始。对多数对象这是目的所在,但以下几类留在初始化里就是缺陷:

  • 网络连接与套接字。恢复后已失效,应在冻结前关闭,或交给客户端在首次调用时重连。
  • 动态数据。会话期间才变化的数据无法被快照,必须每次请求重新获取。
  • 随机值与密钥。初始化时生成的随机数、令牌会被所有会话共享,涉及安全的场景尤其要注意。
  • 会话级状态。属于单个会话的上下文不应固化进快照。

成本上也要留意:快照中保留的内存越多,快照越大,每个会话占用的内存也越多。延迟与内存费用之间需要按实际负载权衡,而不是把所有东西都塞进初始化。

迁移到 V2 的检查清单

  1. 确认运行时 platformVersion 设为 V2。注意创建或更新会因构建快照多出几分钟等待,这会影响 CI/CD 发布节奏;回滚到前一版本同样需要等待快照构建,发布与回滚预案应把这段时间算进去。
  2. 把导入、客户端创建、静态数据加载移到 app.run() 之前,创建对象而非只导入模块。
  3. 关闭初始化期间建立的、恢复后会失效的网络连接。
  4. 把随机值、令牌、会话级状态移回 handler。
  5. 大文件、CLI、浏览器等无法常驻的内容,评估 build hook 或内存盘方案。
  6. 在 CloudWatch 中对比迁移前后的 Latency 与 OverheadLatency,按 P90/P99 或 TM90 观察,并按生产会话创建速率测试。
  7. 把钩子配置纳入每次运行时更新的发布流程,避免被整体替换后丢失。

对自建推理服务的团队,本文讨论的是托管运行时层面的启动优化,思路(预热、快照、把静态工作前移)可以借鉴,但具体机制不能直接套用到海外云服务器上的自建环境。选机房和线路时,仍应先测目标用户所在地区的访问表现,再按负载与预算决定配置。

常见问题

V2 是否对所有 Agent 都有明显收益?

镜像体积大、初始化重的 Agent 受益最明显,例如带本地小模型权重、嵌入索引、浏览器或 CLI 工具链的场景。只导入几个库、调用托管模型的 Agent,收益主要来自把导入和客户端创建放对位置,幅度有限。

为什么初始化耗时不出现在请求延迟指标里?

V2 下初始化只在构建快照时运行一次,不随每个会话重复,因此不计入请求延迟,而是体现在运行时到达 READY 的时间上。要看这段耗时,需在初始化代码中打日志并查看构建日志。

build hook 失败会怎样?

返回非 2xx、连接失败或超过共享的 120 秒,创建或更新运行时都会失败,平台不重试。应检查运行时日志组中的构建日志流,确认是 memory 还是 disk 调用出了问题。

更新运行时后钩子为什么没生效?

UpdateAgentRuntime 整体替换钩子配置,更新时未传入 hooksConfiguration 就会丢失。把该字段固定写进发布脚本可避免遗漏。

快照里保留大量内存对象会不会更贵?

会占用更多内存资源,具体计费口径需以 AWS 官方定价页为准。判断标准是端到端会话启动时间是否真的下降,而不是只看 handler 变短。

Mark · 主笔 / 15 年 IDC 从业经验

长期跟进云服务器、VPS、高防与网络线路的实际表现,负责本站测评方案设计、数据核对与内容审校。文中结论以实测数据为准,厂商合作不影响评测结果。

关于作者与评测方法 →

本文由网上采集发布,不代表我们立场,转载联系作者并注明出处:https://www.uuccloud.com/12072.html

联系我们

在线咨询:点击这里给我发消息

微信号:13180206831

工作日:9:30-18:30,节假日休息