AI Agent 的并发调度工程实战:任务队列、并发限制与 Fan-out/Fan-in 模式

AI Agent 的并发调度工程实战:任务队列、并发限制与 Fan-out/Fan-in 模式

Datadog 2026 年的 AI 工程现状报告显示:5% 的 LLM 调用 span 报错,其中 60% 是 rate limit 触发的。而这 60% 里,绝大多数源自同一个根因------并发没有控制好,重试风暴引发系统雪崩。

我在生产中遇到过这样的场景:一个 AI Agent 接到需求,并发启动 20 个子 Agent 去爬取、总结、比较数据。前 3 秒看起来很快,第 4 秒 API 开始 429,第 5 秒重试进来,第 8 秒整个 LLM 调用队列爆了,服务不可用。

无脑并发,是 AI Agent 在生产中最常见的工程事故。

这篇文章系统讲清楚三件事:

  1. 为什么 AI Agent 的并发比普通微服务更危险
  2. 三种核心并发模式:任务队列、并发限制、Fan-out/Fan-in
  3. 生产级实现:Python asyncio + TypeScript,含背压、超时、Hedging

一、AI Agent 并发的特殊性:不是普通的 HTTP 并发

普通微服务的并发问题,核心是数据库连接池和线程池耗尽。AI Agent 的并发问题,有三个不一样的地方:

1.1 Token 消耗是非线性的

10 个并发请求,不只是 10 倍的 QPS,还是 10 倍的 token 消耗。每个 LLM call 的输入 token 数取决于 context 长度,Agent 任务越复杂,context 越长,token 越多。触发 rate limit 的往往不是请求数,而是 TPM(tokens per minute)上限

OpenAI 的 TPM 上限按 tier 划分:Tier 1 是 200K TPM,Tier 5 才有 10M TPM。一个中等 Agent 任务可能每次消耗 2000-5000 tokens,20 个并发瞬间打满 Tier 1 的 TPM 上限。

1.2 Fan-out 下的尾延迟放大效应

这是很多人没意识到的数学问题。

假设一个 Agent 任务需要并发调用 N 个子任务,每个子任务完成时间满足均值 2s、标准差 0.5s 的正态分布。

Fan-out 的整体延迟 = max(子任务延迟),而不是 mean(子任务延迟)

复制代码
N=1:  期望延迟 ≈ 2.0s
N=5:  期望 max ≈ 2.84s(+42%)
N=10: 期望 max ≈ 3.24s(+62%)
N=20: 期望 max ≈ 3.60s(+80%)

10 个并发子任务,哪怕 9 个都在 1.5s 完成,只要有 1 个跑了 8s,整批就等 8s。InfoQ 的研究(2026-01)证实:Fan-out 架构中,stragglers(慢节点)而非失败,是 p99 延迟的主要驱动因素

1.3 重试风暴的正反馈

LLM API 被 rate limit 之后,正常的做法是重试。但如果 20 个并发任务同时被 429,同时开始退避重试,退避结束后又同时打进来------这就是雷群效应(Thundering Herd),会反复触发 rate limit,形成正反馈循环。

Datadog 的报告明确指出:rate limit 触发重试,重试增加负载,进一步触发 rate limit,"evolve the issue into a sustained system failure"。


二、基础模式:并发限制(Concurrency Limiter)

在处理 Fan-out 之前,先解决最基础的问题:限制同时进行的 LLM 调用数量

2.1 Python asyncio.Semaphore

python 复制代码
import asyncio
import time
from openai import AsyncOpenAI

client = AsyncOpenAI()

# 生产建议:根据你的 API tier 和 TPM 上限调整
# Tier 1: max_concurrent=5, Tier 2: max_concurrent=10
MAX_CONCURRENT = 8
semaphore = asyncio.Semaphore(MAX_CONCURRENT)

async def call_llm_with_limit(prompt: str, task_id: str) -> dict:
    """带并发限制的 LLM 调用"""
    async with semaphore:  # 超过 MAX_CONCURRENT 的请求会在这里等待
        start = time.monotonic()
        try:
            response = await client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": prompt}],
                timeout=30.0  # 重要:必须设置超时
            )
            elapsed = time.monotonic() - start
            return {
                "task_id": task_id,
                "status": "success",
                "content": response.choices[0].message.content,
                "elapsed_s": elapsed,
                "tokens": response.usage.total_tokens
            }
        except Exception as e:
            return {
                "task_id": task_id,
                "status": "error",
                "error": str(e),
                "elapsed_s": time.monotonic() - start
            }

async def process_tasks_batch(prompts: list[dict]) -> list[dict]:
    """并发处理一批任务,带全局并发限制"""
    tasks = [
        call_llm_with_limit(item["prompt"], item["id"])
        for item in prompts
    ]
    # return_exceptions=True 确保单个失败不会中断整批
    results = await asyncio.gather(*tasks, return_exceptions=True)
    
    # 处理异常情况
    processed = []
    for i, result in enumerate(results):
        if isinstance(result, Exception):
            processed.append({
                "task_id": prompts[i]["id"],
                "status": "exception",
                "error": str(result)
            })
        else:
            processed.append(result)
    return processed

关键点async with semaphore 的语义是"同时最多 MAX_CONCURRENT 个协程持有信号量"。第 9 个请求会在 async with 这里 await,直到有一个持有者释放。

2.2 为什么不用 asyncio.BoundedSemaphore

BoundedSemaphore 在 release 超过 initial value 时会抛出 ValueError。在正常 acquire/release 配对使用时,两者等价。但如果你的代码有复杂的 cancel/timeout 路径,BoundedSemaphore 能帮你发现异常的释放行为,适合调试阶段。生产中用标准 Semaphore 即可。

2.3 TypeScript 版本:p-limit

typescript 复制代码
import pLimit from 'p-limit';
import OpenAI from 'openai';

const client = new OpenAI();
const limit = pLimit(8); // 同时最多 8 个并发

interface TaskResult {
  taskId: string;
  status: 'success' | 'error';
  content?: string;
  error?: string;
  elapsedMs: number;
}

async function callLLMWithLimit(
  prompt: string,
  taskId: string
): Promise<TaskResult> {
  const start = Date.now();
  try {
    const response = await client.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
    });
    return {
      taskId,
      status: 'success',
      content: response.choices[0].message.content ?? '',
      elapsedMs: Date.now() - start,
    };
  } catch (err) {
    return {
      taskId,
      status: 'error',
      error: String(err),
      elapsedMs: Date.now() - start,
    };
  }
}

async function processTasksBatch(
  tasks: Array<{ id: string; prompt: string }>
): Promise<TaskResult[]> {
  // limit() 会自动排队,超过并发限制的任务等待前面的完成
  return Promise.all(
    tasks.map((task) =>
      limit(() => callLLMWithLimit(task.prompt, task.id))
    )
  );
}

p-limit 是 Node.js 生态里最简洁的并发限制库,内部基于 Promise 队列,无需手动管理 semaphore。


三、任务队列:处理大批量 + 背压

并发限制解决了"同时请求不超限",但当任务总数很大(比如 1000 个文档),还需要任务队列来:

  1. 背压:防止任务无限堆积在内存里
  2. 优先级:紧急任务先处理
  3. 持久化:程序崩溃后可恢复

3.1 内存队列(适合中等规模)

python 复制代码
import asyncio
import time
from dataclasses import dataclass, field
from typing import Any

@dataclass(order=True)
class Task:
    priority: int  # 数字越小优先级越高
    task_id: str = field(compare=False)
    prompt: str = field(compare=False)
    created_at: float = field(default_factory=time.time, compare=False)

async def worker(
    queue: asyncio.PriorityQueue,
    semaphore: asyncio.Semaphore,
    results: dict,
    worker_id: int
):
    """工作协程:从队列取任务,受 semaphore 限制执行"""
    while True:
        try:
            # 从优先队列取任务(阻塞直到有任务)
            task: Task = await queue.get()
            
            wait_time = time.time() - task.created_at
            print(f"[Worker-{worker_id}] task={task.task_id} waited={wait_time:.2f}s")
            
            async with semaphore:
                result = await execute_llm_task(task)
                results[task.task_id] = result
            
            queue.task_done()  # 必须调用,否则 queue.join() 不会结束
        except asyncio.CancelledError:
            break

async def execute_llm_task(task: Task) -> dict:
    """实际执行 LLM 调用"""
    # 这里调用 LLM API
    await asyncio.sleep(0.1)  # 模拟 LLM 调用
    return {"task_id": task.task_id, "status": "success"}

async def run_pipeline(task_list: list[dict], max_workers: int = 4, max_concurrent_llm: int = 8):
    """
    任务队列 + 工作者池 + 信号量三层控制
    
    max_workers: 工作者协程数(控制内存中并发协程数)
    max_concurrent_llm: 实际 LLM 调用并发数
    """
    queue: asyncio.PriorityQueue = asyncio.PriorityQueue(maxsize=50)  # maxsize=背压控制
    semaphore = asyncio.Semaphore(max_concurrent_llm)
    results = {}
    
    # 启动工作者池
    workers = [
        asyncio.create_task(worker(queue, semaphore, results, i))
        for i in range(max_workers)
    ]
    
    # 生产者:往队列里放任务(受 maxsize 背压限制)
    for item in task_list:
        task = Task(
            priority=item.get("priority", 5),
            task_id=item["id"],
            prompt=item["prompt"]
        )
        await queue.put(task)  # 队列满时会在这里 await(背压生效)
    
    # 等待所有任务处理完
    await queue.join()
    
    # 取消工作者
    for w in workers:
        w.cancel()
    await asyncio.gather(*workers, return_exceptions=True)
    
    return results

关键设计PriorityQueue(maxsize=50) 是背压的关键------当队列满时,await queue.put() 会阻塞生产者,自然限制了内存中待处理任务的数量。

3.2 关键参数设定指南

参数 建议值 说明
max_concurrent_llm 5-15 根据 API tier 的 RPM 上限;Tier 1 建议 ≤8
max_workers max_concurrent_llm * 1.5 工作者数略多于并发数,避免等 IO 时工作者全部等待
queue maxsize max_workers * 3 队列缓冲,太小会频繁阻塞生产者,太大耗内存
任务超时 30-60s 单个 LLM 调用的超时,防止一个慢任务卡住工作者

四、Fan-out/Fan-in:并行子任务的结果聚合

Fan-out/Fan-in(也叫 scatter-gather、map-reduce)是 AI Agent 里最常见的并发模式:协调者(Coordinator)将一个大任务分解为 N 个子任务并发执行,收集结果后聚合。

4.1 基础 Fan-out:asyncio.gather

python 复制代码
import asyncio
from typing import Optional
from openai import AsyncOpenAI

client = AsyncOpenAI()

async def analyze_document(doc_id: str, content: str, angle: str) -> dict:
    """单个分析子任务"""
    try:
        async with asyncio.timeout(30):  # Python 3.11+ 的超时语法
            response = await client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{
                    "role": "user",
                    "content": f"从{angle}角度分析以下文档:\n\n{content}"
                }]
            )
            return {
                "doc_id": doc_id,
                "angle": angle,
                "analysis": response.choices[0].message.content,
                "status": "success"
            }
    except asyncio.TimeoutError:
        return {"doc_id": doc_id, "angle": angle, "status": "timeout", "analysis": None}
    except Exception as e:
        return {"doc_id": doc_id, "angle": angle, "status": "error", "error": str(e), "analysis": None}

async def multi_angle_analysis(document: str, angles: list[str]) -> dict:
    """
    Fan-out: 从多个角度并发分析同一文档
    Fan-in: 收集所有角度的分析结果,聚合摘要
    """
    semaphore = asyncio.Semaphore(5)  # 限制并发
    
    async def bounded_analyze(angle: str) -> dict:
        async with semaphore:
            return await analyze_document("doc-001", document, angle)
    
    # ===== Fan-out =====
    subtasks = [bounded_analyze(angle) for angle in angles]
    
    # return_exceptions=True: 单个失败不影响其他子任务
    raw_results = await asyncio.gather(*subtasks, return_exceptions=True)
    
    # ===== Fan-in =====
    successful = []
    failed = []
    
    for i, result in enumerate(raw_results):
        if isinstance(result, Exception):
            failed.append({"angle": angles[i], "error": str(result)})
        elif result["status"] == "success":
            successful.append(result)
        else:
            failed.append(result)
    
    # 决策:是否有足够的成功结果来生成汇总?
    if len(successful) < len(angles) * 0.5:  # 超过 50% 失败,返回错误
        return {
            "status": "insufficient_results",
            "successful_count": len(successful),
            "failed_count": len(failed),
            "failed_details": failed
        }
    
    # 汇总成功的分析结果
    combined_analyses = "\n\n".join([
        f"【{r['angle']}】\n{r['analysis']}"
        for r in successful
    ])
    
    # 第二阶段:用 LLM 汇总(Fan-in 聚合)
    summary_response = await client.chat.completions.create(
        model="gpt-4o",  # 汇总用更强的模型
        messages=[{
            "role": "user",
            "content": f"综合以下多角度分析,给出总体结论:\n\n{combined_analyses}"
        }]
    )
    
    return {
        "status": "success",
        "summary": summary_response.choices[0].message.content,
        "successful_analyses": successful,
        "failed_analyses": failed,
        "coverage": f"{len(successful)}/{len(angles)}"
    }

4.2 动态 Fan-out:结果驱动的子任务展开

更复杂的场景:Agent 不知道子任务数量,根据中间结果动态决定展开多少子任务。

python 复制代码
import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI()

class DynamicFanoutAgent:
    """
    动态 Fan-out Agent:
    1. 先运行"分解"步骤,获得子任务列表
    2. 并发执行子任务(带并发限制)
    3. 聚合结果
    """
    
    def __init__(self, max_concurrent: int = 6, max_fanout: int = 10):
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.max_fanout = max_fanout  # 防止无限展开
    
    async def decompose(self, goal: str) -> list[str]:
        """第一步:分解目标为子任务"""
        response = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{
                "role": "user",
                "content": f"""将以下目标分解为独立的并行子任务(最多{self.max_fanout}个)。
                
目标:{goal}

以 JSON 数组返回,每个元素是一个子任务描述字符串。
格式:["子任务1", "子任务2", ...]"""
            }],
            response_format={"type": "json_object"}
        )
        import json
        result = json.loads(response.choices[0].message.content)
        subtasks = result.get("subtasks", result.get("tasks", []))
        # 安全:限制最大展开数
        return subtasks[:self.max_fanout]
    
    async def execute_subtask(self, subtask: str, idx: int) -> dict:
        """执行单个子任务"""
        async with self.semaphore:
            try:
                async with asyncio.timeout(45):
                    response = await client.chat.completions.create(
                        model="gpt-4o-mini",
                        messages=[{"role": "user", "content": subtask}]
                    )
                    return {
                        "idx": idx,
                        "subtask": subtask[:50] + "...",
                        "result": response.choices[0].message.content,
                        "status": "success"
                    }
            except asyncio.TimeoutError:
                return {"idx": idx, "subtask": subtask[:50], "status": "timeout"}
            except Exception as e:
                return {"idx": idx, "subtask": subtask[:50], "status": "error", "error": str(e)}
    
    async def run(self, goal: str) -> dict:
        # 分解
        subtasks = await self.decompose(goal)
        print(f"Fan-out: {len(subtasks)} 个子任务")
        
        # 并发执行(Fan-out)
        coroutines = [self.execute_subtask(task, i) for i, task in enumerate(subtasks)]
        results = await asyncio.gather(*coroutines, return_exceptions=True)
        
        # 聚合(Fan-in)
        successful = [r for r in results if isinstance(r, dict) and r["status"] == "success"]
        failed = [r for r in results if isinstance(r, Exception) or (isinstance(r, dict) and r["status"] != "success")]
        
        print(f"Fan-in: {len(successful)} 成功, {len(failed)} 失败")
        return {"goal": goal, "results": successful, "failed": failed}

五、生产级陷阱:Straggler 与 Hedging

5.1 Straggler 问题

Fan-out 最大的生产陷阱:一个慢子任务导致整批超时

场景:并发 10 个子任务,9 个在 2s 内完成,第 10 个因为 LLM 服务器负载高跑了 12s。用户等了 12s,而不是 2s。

解决方案一:超时 + 降级

python 复制代码
async def fanout_with_timeout(subtasks: list, timeout_per_task: float = 10.0) -> list:
    """每个子任务独立超时,超时的返回 None 而不是等待"""
    
    async def task_with_timeout(coro, idx):
        try:
            async with asyncio.timeout(timeout_per_task):
                return await coro
        except asyncio.TimeoutError:
            return {"idx": idx, "status": "timeout", "result": None}
    
    coroutines = [task_with_timeout(subtask, i) for i, subtask in enumerate(subtasks)]
    return await asyncio.gather(*coroutines, return_exceptions=True)

解决方案二:Hedging(对冲请求)

InfoQ 2026 年的研究显示,Adaptive Hedging 可将 p99 延迟降低 74%。原理:当某个子任务超过阈值时间还没完成,发起一个"对冲"请求,用最先完成的那个结果。

python 复制代码
import asyncio
import time

async def hedged_llm_call(
    prompt: str,
    hedge_after_seconds: float = 3.0,
    max_hedges: int = 1
) -> dict:
    """
    对冲请求:如果第一个请求超过 hedge_after_seconds 还没完成,
    发起第二个请求,取最先完成的结果。
    """
    
    async def single_call(attempt_id: int) -> dict:
        start = time.monotonic()
        response = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            timeout=30.0
        )
        return {
            "attempt_id": attempt_id,
            "content": response.choices[0].message.content,
            "elapsed_s": time.monotonic() - start
        }
    
    # 创建第一个请求
    tasks = {asyncio.create_task(single_call(0))}
    hedge_count = 0
    
    while True:
        # 等待任意一个完成,或者超过对冲阈值
        done, pending = await asyncio.wait(
            tasks,
            timeout=hedge_after_seconds if hedge_count < max_hedges else None,
            return_when=asyncio.FIRST_COMPLETED
        )
        
        if done:
            # 有任务完成,取第一个成功的结果
            result = done.pop().result()
            # 取消其他进行中的请求(节省 token)
            for task in pending:
                task.cancel()
            return result
        
        # 超时还没完成,发起对冲请求
        if hedge_count < max_hedges:
            hedge_count += 1
            tasks.add(asyncio.create_task(single_call(hedge_count)))
            # 继续等待,不设超时(等任意一个完成)
            hedge_after_seconds = float('inf')

注意:Hedging 会增加 token 消耗(两个请求都会计费,即使取消了一个)。对冲阈值应该根据你的 p50 延迟设定(通常 p50 的 1.5-2 倍)。

5.2 Rate Limit 的指数退避

当遇到 429 时,正确的退避策略:

python 复制代码
import asyncio
import random
import time
from openai import RateLimitError, AsyncOpenAI

client = AsyncOpenAI()

async def call_with_exponential_backoff(
    prompt: str,
    max_retries: int = 5,
    base_delay: float = 1.0,
    max_delay: float = 60.0
) -> dict:
    """指数退避 + 全量抖动,防止雷群效应"""
    
    for attempt in range(max_retries):
        try:
            response = await client.chat.completions.create(
                model="gpt-4o-mini",
                messages=[{"role": "user", "content": prompt}]
            )
            return {"status": "success", "content": response.choices[0].message.content}
        
        except RateLimitError as e:
            if attempt == max_retries - 1:
                return {"status": "rate_limited", "error": str(e)}
            
            # 指数退避 + 全量抖动(Full Jitter)
            # Full Jitter: sleep = random(0, min(cap, base * 2^attempt))
            # 比固定指数退避更能分散请求,防止雷群
            cap = min(max_delay, base_delay * (2 ** attempt))
            sleep_time = random.uniform(0, cap)
            
            print(f"Rate limited (attempt {attempt+1}/{max_retries}), sleeping {sleep_time:.1f}s")
            await asyncio.sleep(sleep_time)
        
        except Exception as e:
            return {"status": "error", "error": str(e)}
    
    return {"status": "max_retries_exceeded"}

Full Jitter(全量抖动):这是 AWS 在 2015 年总结的最佳实践,比"指数退避 + 小抖动"更能分散并发重试,在高并发场景下降低服务器负载。


六、LangGraph 中的并行节点

如果你用 LangGraph,并发调度有原生支持:

python 复制代码
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator

class AgentState(TypedDict):
    query: str
    research_results: Annotated[list, operator.add]  # 多个节点写入,用 add reducer
    synthesis: str

async def research_node_a(state: AgentState) -> dict:
    """并行节点 A:从数据库搜索"""
    result = await search_database(state["query"])
    return {"research_results": [{"source": "db", "content": result}]}

async def research_node_b(state: AgentState) -> dict:
    """并行节点 B:从网络搜索"""
    result = await search_web(state["query"])
    return {"research_results": [{"source": "web", "content": result}]}

async def research_node_c(state: AgentState) -> dict:
    """并行节点 C:从知识库检索"""
    result = await search_knowledge_base(state["query"])
    return {"research_results": [{"source": "kb", "content": result}]}

async def synthesize_node(state: AgentState) -> dict:
    """Fan-in:合并所有研究结果"""
    all_results = state["research_results"]
    # 调用 LLM 综合所有结果
    synthesis = await synthesize_results(state["query"], all_results)
    return {"synthesis": synthesis}

# 构建图
builder = StateGraph(AgentState)
builder.add_node("research_a", research_node_a)
builder.add_node("research_b", research_node_b)
builder.add_node("research_c", research_node_c)
builder.add_node("synthesize", synthesize_node)

# 并行展开(Fan-out)
builder.add_edge(START, "research_a")
builder.add_edge(START, "research_b")
builder.add_edge(START, "research_c")

# 收敛(Fan-in)
builder.add_edge("research_a", "synthesize")
builder.add_edge("research_b", "synthesize")
builder.add_edge("research_c", "synthesize")
builder.add_edge("synthesize", END)

graph = builder.compile()

注意 LangGraph 的一个已知问题 :LangGraph Forum(2026-03)有用户反馈 parallel nodes 存在"sequential execution issue",即声明为并行的节点实际上顺序执行。确认并行是否真正生效的方法:加计时日志,用 asyncio.current_task() 验证是否在不同 Task 中执行。


七、生产监控:并发调度的可观测性

并发系统的 bug 往往不是报错,而是"怎么这么慢"。必须监控这几个指标:

python 复制代码
import asyncio
import time
from dataclasses import dataclass, field
from collections import defaultdict

@dataclass
class ConcurrencyMetrics:
    """并发指标追踪"""
    active_count: int = 0
    queue_depth: int = 0
    completed: int = 0
    failed: int = 0
    rate_limited: int = 0
    latencies: list = field(default_factory=list)
    
    def p50(self) -> float:
        if not self.latencies:
            return 0
        sorted_l = sorted(self.latencies)
        return sorted_l[len(sorted_l) // 2]
    
    def p99(self) -> float:
        if not self.latencies:
            return 0
        sorted_l = sorted(self.latencies)
        return sorted_l[int(len(sorted_l) * 0.99)]
    
    def report(self) -> str:
        return (
            f"active={self.active_count} queue={self.queue_depth} "
            f"done={self.completed} failed={self.failed} "
            f"rate_limited={self.rate_limited} "
            f"p50={self.p50():.1f}s p99={self.p99():.1f}s"
        )

metrics = ConcurrencyMetrics()

class InstrumentedSemaphore:
    """带监控的信号量"""
    
    def __init__(self, max_concurrent: int):
        self._sem = asyncio.Semaphore(max_concurrent)
        self.max_concurrent = max_concurrent
    
    async def __aenter__(self):
        wait_start = time.monotonic()
        await self._sem.acquire()
        wait_time = time.monotonic() - wait_start
        if wait_time > 0.1:  # 等待超过 100ms,记录队列压力
            print(f"[WARN] Semaphore wait {wait_time:.2f}s (queue pressure)")
        metrics.active_count += 1
        return self
    
    async def __aexit__(self, *args):
        metrics.active_count -= 1
        self._sem.release()

必须追踪的关键指标

  • semaphore_wait_time:排队等待时间,高于 200ms 说明并发数设置太低或任务太重
  • p99_latency:99 分位延迟,Fan-out 的整体性能指标
  • rate_limited_count:rate limit 次数,高了要降低并发数或升级 tier
  • queue_depth:队列深度,持续高意味着生产者比消费者快,需要扩容 worker

八、设计决策矩阵

场景 推荐模式 并发数建议
批量文档处理(无 deadline) 任务队列 + 工作者池 5-10,优先稳定
用户请求的多角度分析 asyncio.gather + timeout 3-6,优先低延迟
实时 Agent 多步推理 动态 Fan-out + Hedging 3-5,加 hedge 控 p99
大批量离线数据处理 Queue + Semaphore + Redis 持久化 10-20,关注吞吐
TPM 敏感场景(Tier 1) Semaphore(3) + Full Jitter 退避 ≤3,优先不触发 429

总结

AI Agent 的并发调度,坑在三个地方:

1. 无脑并发打爆 rate limit :用 asyncio.Semaphorep-limit 限制同时进行的 LLM 调用数。根据你的 API tier 调整。

2. Fan-out 的尾延迟放大:p99 延迟 = max(子任务 p99)。10 个任务里 1 个慢,全部都慢。解法是给每个子任务设独立超时,对 p99 要求高的场景用 Hedging。

3. 重试风暴:429 后不要立即全量重试。用 Full Jitter 指数退避,分散重试时间,打破雷群效应。

Datadog 报告里那 60% 因为 rate limit 导致的 LLM 调用失败,大部分是可以通过上面这三点解决的。

并发不是银弹,Fan-out 也不是越多越快。在 LLM 调用的世界里,适当的并发 + 精细的控制,才能在性能和稳定性之间找到平衡。


参考资料:

相关推荐
江畔柳前堤19 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术19 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客19 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光
阿里云大数据AI技术20 小时前
从算力到智能体,面向 Agentic AI 的基础设施演进
人工智能·agent
hangyuekejiGEO21 小时前
GEO技术服务选型指南
大数据·人工智能·python
阿里云大数据AI技术21 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
维基框架21 小时前
GitHub源码处理提速 一趟扫描反而更慢
人工智能·github
冬奇Lab21 小时前
代码库知识库系列(05):向量检索 vs 知识图谱——加了调用图并没有变更好
人工智能
AKAMAI21 小时前
你的源服务器可能是你做出的最昂贵决定
运维·人工智能·云计算
冬奇Lab1 天前
【无标题】
人工智能·开源