首页
/ OmniRoute 准入通道(Admission Lanes)架构解析:字节级准入、自适应虚拟通道与扇出探针

OmniRoute 准入通道(Admission Lanes)架构解析:字节级准入、自适应虚拟通道与扇出探针

2026-09-07 20:27:48作者:董灵辛Dennis

导读

Admission Lanes(准入通道)是 OmniRoute 内置的两套进程内限流/准入系统:一套以「字节预算」为粒度的进程级准入(gate 在请求体缓冲/解析之前),另一套以「租户键」为维度的自适应运行时虚拟通道(gate 在 provider 分发之前)。两套系统作用域互补,配合 #9654 引入的扇出探针(fan-out probe) 后,能同时抑制"大请求体放大堆内存"与"单会话突发打挂其他租户"两类经典故障。读完本文,你将掌握:两套 lane 各自守什么门、在何处上报、如何通过环境变量调优,以及 combo/fusion 多路分发时 per-target 准入的设计权衡。

本文配套源码位于 docs/architecture/admission-lanes.md,实现代码见 src/shared/middleware/chatBodyAdmission.tssrc/shared/middleware/admissionBudget.tsopen-sse/services/admission

概览:为什么 OmniRoute 需要两套 lane 系统

OmniRoute 的进程内有两套作用域完全不同的 lane 系统,二者互补,运维者在观测时务必先搞清楚"自己在看的是哪一套":

系统 文件/目录 作用域 守什么门 是否默认启用
System 1 字节级进程级准入 chatBodyAdmission.ts 进程全局单控制器 POST /v1/chat/completions/v1/messages/v1/responses 等 chat 形态路由的请求体缓冲/解析路径(防堆放大 #4380) (无条件生效)
System 2 自适应运行时虚拟通道 open-sse/services/admission 按租户键的 provider 分发 队列成本、延迟引导的限流自适应、通道排队、通道指标 (opt-in,需 OMNIROUTE_CHAT_VIRTUAL_LANES=true
Fan-out 探针(#9654 Wave 2) 依赖 System 2 每个 combo/fusion 扇出目标 基于父请求租户通道的 per-target 分发前 gate 随 System 2 开启

#9654 的判据 1("一个会话的突发不会把另一个会话打成 503")由 System 1 无条件强制,由 System 2 在 opt-in 后强制。


1. 字节级进程级准入(chatBodyAdmission.ts)

1.1 作用域与设计动机

Byte-level 准入守卫的是 buffered-body/heap 路径POST /v1/chat/completions/v1/messages/v1/responses 及其他 chat 形态路由。其直接对抗的故障是 heap amplification(堆放大,#4380):大体积的 coding-agent 请求体在被解析、翻译、压缩、分发的过程中会衍生出多份瞬时内存表示(原始 UTF-8 buffer + JS 字符串 + 解析后的对象图 + 翻译后的对象图会短暂共存)。仅靠堆快照无法阻止两个健康请求同时进入这条高分配路径,因此该模块在解析之前就进行进程本地的重量级容量预留(reserve),且硬限制基于实际读到的字节数而非不可信的 Content-Length 头。

chatBodyAdmission.ts 头部注释 可以看到,模块宣称的对齐目标正是 "one connection's burst cannot starve others"(#9654)。

1.2 一个进程全局控制器,而非 per-key 通道(#10110)

重点演进:每个 API key(哈希后)或 anonymous 会话都在对同一个共享预算做准入,会话的哈希 ID 只用作公平调度的 key(round-robin 分发 waiter),绝不做容量分片

文档明确标注:旧版本曾描述过"每 key 独立容量的 lane",该模型在 #10110 被移除,原因是它允许未认证的伪造凭据把进程级上限放大。代码在 chatBodyAdmission.ts 中同样给出了注释:pre-#10110 的设计为每个会话 new 一个 controller,等于把进程上限乘以最多 64 个 lane,让伪造凭据可以分片容量。

因此现在的实现是单例 perConnectionAdmissionController(见 getController() 恒返回同一实例),会话身份仅作为 per-key FIFO 队列 的分组键,容量释放时按 key 做 round-robin 轮转唤醒,保证某客户端突发无法独占所有空闲槽位。

1.3 Gate:#503-fanout 引入的自动推导「摄入字节预算」

旧的 CHAT_MAX_HEAVY_IN_FLIGHT 请求数上限(默认 1)把 coding-agent 的扇出行为(多 subagent/多 CLI,请求体经常 > 256 KB)压缩成了约 1 的有效并发,在完全正常的负载下就会打出 503。

新模型下:

  • 仅当运维者显式设置 OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT 时,请求数上限才生效;
  • 不设置时,准入改由 OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES 门控——这是一个由进程真实内存上限自动推导的字节预算,实现在 src/shared/middleware/admissionBudget.ts

推导公式(见 computeIngestByteBudget 的常量定义):

  • V8 堆上限cgroup/容器上限 中更紧的那一个作为 effective ceiling;
  • ceiling × 25%(INGEST_HEAP_FRACTION) ÷ 8(INGEST_AMPLIFICATION 瞬时放大系数)
  • 结果夹在 8 MiB 与 2 GiB 之间(MIN_INGEST_BUDGET_BYTES / MAX_INGEST_BUDGET_BYTES)。

因此该预算能从 512 MB 容器自动伸缩到 32 GB 桌面,无需任何环境变量调参。显式覆盖(OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES)同样经过相同的钳制。cgroup 上限通过 Node 的 process.constrainedMemory()(Node >= 19.6/20.13)读取;缺失时回退到 V8 堆上限(source: "v8_heap")。预算计算是纯函数且解析后缓存(resolveIngestByteBudget 首次调用后缓存),热路径上不会反复读取 cgroup/V8 状态。

1.4 三类被拒路径与资源压力协同

结合 admitChatRequest 的执行顺序,可以清晰看到字节级准入门的工作流:

  1. 内部自环子请求(vision-bridge 等 describe 调用)走 isInternalAdmissionBypass:跳过 lease 预留(父请求已持有),但仍强制硬字节上限;
  2. critical 资源压力:任一字节被摄入前,若 pressureSeverity() === "critical",立即以 503 resource_pressure 甩负载(shed),并记录 shed reason;
  3. Content-Length 已知且 > hardMaxBytes → 立即 413(对应 chatAdmissionRejectionResponse413 语义);
  4. Content-Length ≥ largeBodyBytes 但预算放不下!canFitBudget)→ 413 body_exceeds_budget,即文档所说"一个无法装进有效预算的请求体立即失败";
  5. 未知长度(chunked):只在超过 heavy 阈值后才做有界嗅探(bounded sniff),小请求不浪费重量级容量;摄入期间若总字节 > hardMax → 413,若 ≥ largeBody 且预算不足 → 413 body_exceeds_budget,若无法 reserve → 503;
  6. 只有"单个请求本身可服务、但彼此争用"的情况才进入有界公平队列(acquireHeavyWithin 内的 #dispatchFair + 队列字节堆阀 maxQueuedBytes,默认 4 MB)。

资源压力追踪器是多信号的实时探测器(V8 堆比例、cgroup、PSI、OOM 事件,见 open-sse/utils/resourcePressurePolicy.ts):

  • high 压力下缩短有界等待时间
  • critical 压力下在任何字节被摄入之前立即以 503 resource_pressure 甩负载。

1.5 调优参数总表(System 1)

环境变量 含义 默认值
OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES 覆盖自动推导的字节预算(走相同 8 MiB–2 GiB 钳制) 自动推导
OMNIROUTE_CHAT_MAX_HEAVY_IN_FLIGHT 遗留的请求数上限,opt-in;不设置时视为无限 未设置 → 不生效
OMNIROUTE_CHAT_ADMISSION_QUEUE_MS 排队等待时长,超时返回 503 2000 ms
OMNIROUTE_CHAT_ADMISSION_MAX_QUEUED_BYTES 排队字节的堆阀值(防止大请求体扎堆驻留放大堆) 4 MB
OMNIROUTE_CHAT_VIRTUAL_TTL_MS / OMNIROUTE_CHAT_VIRTUAL_MAX_SESSIONS 自 #10110 起废弃的空操作(为了配置兼容被接受、被忽略)

源码中还可见其余可调项:OMNIROUTE_CHAT_LARGE_BODY_BYTES(大请求体阈值,默认 256 KB)、OMNIROUTE_CHAT_HARD_MAX_BODY_BYTES(硬上限,默认 50 MB)、CHAT_ADMISSION_HEAP_SHED_RATIO(堆压 shed 比例,默认 0.75)、CHAT_ADMISSION_HEALTHY_HEADROOM(健康堆快速路径的有界余量)与 OMNIROUTE_CHAT_HARD_MAX_MESSAGES(历史条数硬上限,默认 0 即关闭,将超长会话交给压缩管线处理),详见 chatBodyAdmission.ts 常量区

1.6 上报:health 快照中的 chatAdmission

GET /api/monitoring/healthchatAdmission(#11244),由 src/app/api/monitoring/health/route.ts 调用 perConnectionAdmissionController.snapshot() 产出(snapshot 结构见 chatBodyAdmission.ts)。除了 activeHeavywaitingqueuedBytesshedTotalshedsByReason 之外,还包含 #503-fanout 新增字段:

  • inflightBytes / maxInflightBytes:当前摄入中的字节与预算上限;
  • budgetSourcev8_heap | cgroup | override
  • pressureSeveritynormal | high | critical
  • countCapEnabled:默认部署下为 false —— 恰好证明真正生效的是字节预算而非遗留的请求数上限

ChatAdmissionShedReason 的类型定义(chatBodyAdmission.ts)将 shed 原因收敛为 queue_timeout / queued_bytes_budget / body_exceeds_budget / inflight_bytes_budget / resource_pressure,与 shedsByReason 一一对应,可作为排障依据。


2. 自适应运行时虚拟通道(open-sse/services/admission)

2.1 作用域与本质

System 2 位于 open-sse/services/admission,其 index 注释称其为 "Pure weighted adaptive admission-control core"(纯加权自适应准入控制核心,无路由/设置/环境 wiring)。它的关注点是 tenant-key 的 provider 分发准入:队列成本、延迟引导的 limit 自适应、通道排队与通道指标。

核心控制器为 AdaptiveAdmissionController,纯类型契约(AdmissionRequest/AdmissionLease/AdmissionSnapshotoff | shadow | enforce 三态 mode、normal | high | critical 压力级别、租户快照结构)定义在 types.ts。关键点:

  • AdmissionRequest.tenantKey不透明公平键,绝不进快照(opaque fairness key; never exposed in snapshots);
  • 成本可以由调用方直接传 cost,也可以只传 features(bodyBytes、estimatedInputTokens、messageCount、toolCount、requestedFanout、streaming),由 cost.ts 内的 estimateAdmissionCost 结合 cost 配置推导。

2.2 Gate:opt-in,默认共享队列行为

System 2 是 opt-in:除非设置 OMNIROUTE_CHAT_VIRTUAL_LANES=true,否则该自适应控制器保持共享队列行为(即 #9654 判据 1 只有在运维者开启 lanes 后才成立)。配置结构见 types.ts 的 AdaptiveAdmissionConfig,其中显式标注了 "Per-tenant virtual admission lanes (#9654). Default: false" 的 virtualLanes 字段。

配置解析(config.ts)做了严格校验:minLimit <= maxLimitcriticalDecreaseFactor <= decreaseFactorlowUtilizationThreshold < highUtilizationThresholdshortLatencyAlpha > longLatencyAlpha 等,越界直接抛 RangeError

2.3 调优参数

配置项 含义 默认值(config.ts)
mode off / shadow / enforce shadow
minLimit / maxLimit / initialLimit 并发/成本限额的上下界与初值 必填
maxQueueCount 排队条数上限 必填
maxQueueCost 排队成本上限 必填
defaultMaxWaitMs 默认最大等待时间 5000 ms
windowMs 自适应窗口 1000 ms
decreaseFactor / criticalDecreaseFactor 降限因子 0.8 / 0.5
increaseStep / maxIncreasePerWindow 升限步长与单窗口上限 1(同步长)
shortLatencyAlpha / longLatencyAlpha 双 EWMA 延迟系数 0.5 / 0.1
highUtilizationThreshold / lowUtilizationThreshold 利用率升降阈值 0.7 / 0.3
latencyGradientThreshold 延迟梯度阈值 0.25
virtualLanes 是否开启租户虚拟通道 false

2.4 上报:health 快照中的 adaptiveAdmission

GET /api/monitoring/healthadaptiveAdmission(health route 中经 getAdaptiveAdmissionRuntime().snapshot() 取数),其中:

  • laneCount / laneQueuedCount / laneQueuedCost:租户通道规模与排队量;
  • laneTenants不透明 lane ID 列表,绝不暴露原始 key(见 types.ts 的 laneTenants 注释 "opaque keys, never raw API keys");
  • virtualLanes:快照中"通道已开启"的权威标志位(true/false);
  • 其余如 admittedCountrejectedCountcurrentLimitutilization、双 EWMA 延迟、pressure 等均可在 AdmissionSnapshot 中直接观测。

3. 扇出探针:combo/fusion 的 per-target 准入(#9654 Wave 2)

3.1 动机

Combo(priority / round-robin)与 fusion 会在一个父请求下扇出 N 个模型目标。自 #9654 Wave 2 起,每个扇出目标在分发前都由一个 per-target 探针(PerTargetAdmissionHook,由 createPerTargetAdmissionHook 构建,类型契约见 types.ts)对着父请求的租户通道做 gate。

  • 作用域:combo、fusion 以及 chaos engine 分发的每一个扇出目标。System 1(字节级)不受影响——它从不探测扇出目标
  • Gate:随 System 2 opt-in。未设置 OMNIROUTE_CHAT_VIRTUAL_LANES 时为 no-op——因为该模式下父请求已经持有共享队列的 lease,再探测会造成双重计数、误伤 combo 目标;
  • 探测只发生在 provider 分发路径,字节级摄入路径对扇出目标天然"看不见"。

3.2 语义一:严格非阻塞——skip, never queue

maxWaitMs 0:通道满时跳过该目标,由 combo 的 fallback 机制(或 fusion 的 survivor panel)顶上。这是刻意设计——扇出目标是冗余工作,为它排队等于往通道本来要阻止的拥塞上继续堆负载。因此:

  • defaultMaxWaitMs 只作用于父请求
  • 扇出探针永不等待,且故意没有任何 knob能让它们等待(issue 历史显示,加等待 knob 曾产生 #9654 要阻止的大规模 502/504;除非运维者报告"被跳过的扇出目标伤害了响应质量",否则不会回归该能力)。

3.3 语义二:admit 即释放

被允许的探针立即释放它的 lease:它是容量门(capacity gate),不是占用(hold)。父请求自己的 lease 已经覆盖了整个扇出;若再持有 N 个,会膨胀共享 active cost、把其他租户拒之门外。

同时它是 best-effort,不是 reservation:通道可能在 probe 与分发之间被重新填满,所以在高争用下,探针可能"准入了一个在真正分发时又满了的通道"。

3.4 语义三:按真实扇出体定价

探针的成本用目标实际请求体来估算——包括由其 stream 标志导出的请求类别,与父请求路径完全一致。因此:

  • fusion panel 成员(stream: false)按它们真正会占据的非流式类别定价;
  • priority/RR 目标按用户实际请求的类别定价。

3.5 上报与观测缺口

  • 第一个目标之后发生的探针跳过,会累计进 combo 的按请求 fallbackCount(沿用既有 fallback 语义,可在 combo 日志看到);
  • fusion 在所有 panel 成员都被跳过时返回 503;
  • 当前快照上没有聚合计数器(例如 virtualFanoutSkipped)——文档明确指出:如果运维者报告"看不出 lane gate 跳过扇出目标有多频繁",那正是需要新增该计数器的触发点。

4. 仪表盘上看到的是哪一套?(判别指南)

运维者面对 health 快照时按以下规则快速判别:

  • adaptiveAdmission.laneCount / laneTenants → 看的是 adaptive virtual lanes(System 2)
  • adaptiveAdmission.virtualLanes === true → 第 3 节的扇出探针也处于激活状态
  • 载荷中 virtualLanes 缺失或为 false → 说明 OMNIROUTE_CHAT_VIRTUAL_LANES 未设置——此时 字节级 lanes(System 1)仍然有效,但 adaptiveAdmission 下的一切(以及所有扇出 gating)尚未生效,直到开启该变量为止。

System 1 的状态则看 chatAdmission 字段:重点观察 countCapEnabled === false(确认默认部署下字节预算是实际约束)、budgetSource(预算来源)、pressureSeverityshedsByReason(分布式 shed 归因)。

5. 为什么两套系统共存

  • 字节级 lanes 约束的是内存密集的 parse/compress 路径——它阻止大请求体在缓冲/解析/压缩阶段放大堆内存(#4380);
  • 自适应 lanes 约束的是每个租户的分发成本——它让 provider 分发阶段的并发/成本按租户自适应收敛;
  • #9654 的判据 1("一个会话的突发不会让另一个会话 503")由 System 1 无条件强制、由 System 2 在 opt-in 后强制;两套系统在各自的作用域上互补,不会相互替代。

一句话总结:看到 chatAdmission 字段,那是字节级 System 1 在汇报"摄入大门";看到 adaptiveAdmissionvirtualLanes,那是 System 2 + 扇出探针在汇报"分发大门"。运维与排障时先按此分辨,再决定调 OMNIROUTE_CHAT_MAX_INFLIGHT_BYTES 还是 OMNIROUTE_CHAT_VIRTUAL_LANES 体系下的自适应参数。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391