"虽然核心机器学习推理步骤通常受限于 CPU,但许多 ML API 工作流涉及大量的输入/输出 (I/O) 操作。这些操作可能包括从远程数据库获取特征数据、从另一个微服务检索用户配置文件、加载配置文件,或将预测日志保存到存储设备。当同步执行时,这些 I/O 任务会成为主要的性能瓶颈。"考虑一个典型的 API 请求,它在运行预测前需要获取数据:接收请求。查询数据库以获取所需特征(网络 I/O 等待)。预处理获取的数据(CPU 工作)。运行模型推理(CPU 工作,可能计算密集)。后处理结果(CPU 工作)。将结果或日志保存到存储设备(磁盘/网络 I/O 等待)。发送响应。在传统的同步框架中,如果步骤 2 涉及等待数据库 100 毫秒,处理该请求的工作进程将完全阻塞。在该等待期间,它无法处理任何其他传入请求。同样,在步骤 6 中,工作进程再次阻塞,等待存储操作完成。如果您的 API 收到许多并发请求,大多数工作进程可能只是在等待 I/O,导致高延迟和低吞吐量。这就是异步编程的优势所在。通过使用 async def 定义您的路由处理程序,并在调用 I/O 密集型函数时使用 await(由兼容异步的库提供,例如用于 HTTP 请求的 httpx、用于数据库访问的 asyncpg 或 databases、用于文件系统操作的 aiofiles),您就可以让 FastAPI 的事件循环有效管理这些等待时间。当 I/O 操作遇到 await 时(例如 await database.fetch_one(...) 或 await http_client.get(...)),函数会暂停在该点的执行。然而,重要的是,工作进程不会阻塞。事件循环可以切换上下文,并使用工作进程处理其他就绪任务,例如处理不同的传入请求,或继续执行已完成 I/O 等待的其他异步函数。一旦原始 I/O 操作完成(例如,数据库返回数据),事件循环就会从暂停的地方恢复已暂停的函数。digraph G { rankdir=LR; node [shape=box, style=rounded, fontname="Arial", fontsize=10]; edge [fontname="Arial", fontsize=9]; subgraph cluster_sync { label = "同步工作进程"; bgcolor="#ffe066"; // Yellow background style=filled; s_req1 [label="请求 1\n(需要数据库)", shape=Mrecord, style=filled, fillcolor="#a5d8ff"]; s_req2 [label="请求 2\n(需要 API)", shape=Mrecord, style=filled, fillcolor="#a5d8ff"]; s_wait1 [label="等待数据库...", style=filled, fillcolor="#ffc9c9"]; // Red for waiting s_process1 [label="处理请求 1", style=filled, fillcolor="#b2f2bb"]; // Green for processing s_wait2 [label="等待 API...", style=filled, fillcolor="#ffc9c9"]; s_process2 [label="处理请求 2", style=filled, fillcolor="#b2f2bb"]; s_req1 -> s_wait1 [label="I/O 开始"]; s_wait1 -> s_process1 [label="I/O 结束"]; s_process1 -> s_req2 [label="工作进程空闲"]; s_req2 -> s_wait2 [label="I/O 开始"]; s_wait2 -> s_process2 [label="I/O 结束"]; } subgraph cluster_async { label = "异步工作进程"; bgcolor="#96f2d7"; // Teal background style=filled; a_req1 [label="请求 1\n(需要数据库)", shape=Mrecord, style=filled, fillcolor="#a5d8ff"]; a_req2 [label="请求 2\n(需要 API)", shape=Mrecord, style=filled, fillcolor="#a5d8ff"]; a_await1 [label="等待数据库", style=filled, fillcolor="#ffec99"]; // Yellow for await a_await2 [label="等待 API", style=filled, fillcolor="#ffec99"]; a_process1 [label="处理请求 1", style=filled, fillcolor="#b2f2bb"]; a_process2 [label="处理请求 2", style=filled, fillcolor="#b2f2bb"]; a_req1 -> a_await1 [label="I/O 开始"]; a_req2 -> a_await2 [label="I/O 开始 (并发)"]; a_await1 -> a_process1 [label="请求 1 I/O 结束"]; a_await2 -> a_process2 [label="请求 2 I/O 结束"]; } timeline [shape=none, label="时间 ->", fontsize=12]; s_process2 -> timeline [style=invis]; a_process1 -> timeline [style=invis]; a_process2 -> timeline [style=invis]; }同步和异步 I/O 处理的对比。同步工作进程按顺序处理请求,并在每次 I/O 等待时阻塞。异步工作进程可以启动多个 I/O 操作,并在 I/O 完成时在处理任务之间切换,从而提高整体吞吐量。在您的 ML API 中将异步操作用于 I/O 密集型任务的主要优点包括:并发能力增强: 应用程序可以处理更多并发请求,因为工作进程不会因等待缓慢的 I/O 而停滞。它们可以被释放去开始或继续处理其他请求。吞吐量提高: 通过高效使用工作进程,而不是让它们在 I/O 等待期间空闲,API 可以在单位时间内处理更多请求。(负载下的)更低延迟: 虽然单个请求的延迟可能不会明显降低(它仍然需要等待其 I/O),但在负载下,用户感受到的平均延迟通常会更低,因为请求不会在因 I/O 而阻塞的其他请求后面排队。资源利用率更佳: CPU 资源可以更有效地用于计算,而不是在等待外部系统时处于空闲状态。需要记住的是,async/await 主要对 I/O 密集型操作有益。对于像实际模型推理这样的 CPU 密集型任务,单独使用异步定义并不能阻止事件循环的阻塞。正如之前讨论的,run_in_threadpool 等技术对于卸载这些密集计算是必要的,通常需要结合用于周边 I/O 操作的异步封装器。通过将异步 I/O 处理与针对 CPU 密集型工作的适当策略结合起来,您可以为您的机器学习模型构建高度响应且可扩展的 FastAPI 应用程序。