尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

自托管LLM网关实践:智能路由与流量治理的关键设计

发布时间:2026/9/26 6:32:51

资讯中心
01
ARTICLE

自托管LLM网关实践:智能路由与流量治理的关键设计

自托管LLM网关实践:智能路由与流量治理的关键设计
1. 从“一把梭”到LLM网关我为什么愿意折腾自托管1.1 代码里写死各家SDK的那段日子先说个真实的场景。假设你的产品已经接了三家模型OpenAI 的 GPT 系列负责通用对话、某家国产大模型负责中文长文本总结、还有个本地部署的开源模型负责敏感数据脱敏处理。这听起来很美好但实际代码层会变成什么样子回调函数各写各的、请求超时重试逻辑五花八门、每个供应商的鉴权头都不一样更别提有的模型 SDK 有自己的一套流式解析协议。换一个模型或者加一个供应商就得动业务代码出一次线上问题就要翻半天日志才能定位到是哪个上游。大多数团队走到这一步第一个想法是“我们搞个抽象层吧”。抽象层确实能解决一部分问题但它往往只停留在接口统一上。真正的难点在于请求发给谁、什么时候发、发失败了怎么办、整体预算怎么管控、不同业务线之间的优先级怎么调度。这些东西如果你全部在业务代码里实现代码会膨胀得非常快而且每一处调用点都得遵循同一套逻辑这几乎是人力维护的噩梦。所以我更倾向于把这一层能力独立出来做成一个基础设施组件——这正是我开始认真考虑自建 LLM 网关的原因。Relay 这个项目就是在这种背景下出现的。它本质上是一个 self-hosted 的 LLM gateway你可以把它理解成所有 AI 请求的“统一交通枢纽”。业务服务不再直接面对各式各样的模型 SDK而是只跟 Relay 对话。Relay 负责做 smart routing 和 request pacing也就是智能路由和请求节奏控制。当时我第一眼在 Show HN 上看到这个项目名并没有觉得它有多特别但看完它的设计思路和实践细节之后我开始意识到这可能是很多 AI 应用团队迟早要面对的一类基础设施。1.2 Relay 想解决的三个具体问题第一个问题是连接管理。没有网关的时候每一台业务服务器都要跟各家模型服务保持连接连接池参数、超时时间、重试策略都得自己调。一旦模型服务端出现抖动上游的故障会直接传导到你的所有业务节点。有了网关上游连接集中收敛在 Relay 这一层业务侧只需要维护与 Relay 之间的一条稳定连接即可。第二个问题是请求分发策略。多个模型并存时你不能简单轮询或者写死路由。要考虑模型能力边界比如某个模型虽然便宜但支持的工具调用能力弱某个模型延迟低但上下文窗口小。这些判断不应该让业务开发每次手动指定而是让网关把路由规则沉淀下来业务侧只需表达需求和预算约束。第三个问题是流量整形。LLM 接口一个很反直觉的特点在于单次请求的耗时长且方差大大量突发请求会瞬间打满上游的配额而一旦达到供应商的 token 速率限制等到的往往是一串 429 错误。Relay 用 request pacing 的机制去平滑请求的发出速率把“一波流”变成“匀速跑”。这跟传统限流rate limiting的思路不一样它不是简单地拒绝多余请求而是让请求排队并按节奏放行从而维持整体吞吐的稳定。这三个问题覆盖了接入层、策略层、流量层。理解了它到底在解决什么后面所有设计细节才能串得起来。2. 为什么不直接用云服务商提供的托管网关2.1 托管的网关服务看起来很省事但也有不少“不对味”的地方现在各大云平台基本都推出了自己的 LLM 网关或 API 管理产品托管网关最直接的吸引力是免运维不用自己部署控制台点几下就能完成模型路由配置。如果你是一个小团队想在一天之内跑通所有实验托管服务确实香。但我个人在实践中对托管网关有几处始终不太满意的点。第一是数据链路和隐私控制。请求会先经过别人的网关再转发到模型供应商。这个过程中如果你有敏感的业务数据就会多一跳不可控的传输。对于有数据合规要求的业务自托管网关是更容易说清楚的方案因为数据从你的 VPC 到模型提供方之间所有的中间环节都是你自己控制的。当然自托管不意味着绝对安全但至少安全边界是你能看得见、摸得着的。第二是策略灵活度。托管网关提供的能力永远是“别人替你定义好的能力”。比如我想实现一个非常细粒度的成本路由策略要求当某个项目的日预算用完时自动把请求降级到开源模型或者我想在网关层做一套特殊的语义缓存逻辑只缓存特定领域的问法。这些需求在小厂商那里很难被快速响应等你提工单走流程业务早就变了。第三是供应商锁定。托管网关通常绑定其云平台的生态你用的路由规则 DSL、监控面板、告警体系都是他们家的私有格式。如果哪天想整体迁移到其他平台等于网关层全部要重新实现。自托管方案至少还有迁移的可能性代码、配置、数据库都在自己手里。2.2 自托管给团队协作方式带来的改变除了安全和灵活度我觉得自托管网关还有一个很实际的收益是让“谁能调模型、花了多少钱、响应快不快”这件事在团队内部变得透明。Relay 这类网关可以暴露统一的内部 API前端团队、后端团队、数据分析团队都走同一个入口同时网关可以按项目、按部门、按调用方身份去划分 token 配额和速率限制。举一个我实际经历过的场景市场部门的活动页想接入 AI 客服他们跟开发提需求开发初期并不确定这个功能会调用多少次也不知道会不会被某个恶意的刷量逻辑打爆。如果每个业务线各自去申请模型 API key那预算和用量就彻底失控了。但如果一切流量都过 Relay就可以在网关层先给活动页项目一个较保守的配额观察几天再动态调整。这个能力带来的不是技术便利而是管理上的确定性。再者自托管网关天然适合多云多模型的架构。今天业务可能需要某个模型的新版本——从 OpenAI 切到别的模型在传统模式下可能牵扯业务代码改造在网关模式下你只需要修改路由规则把原有逻辑转发到新的模型端点即可。业务侧甚至无感知这种“低耦合”是我认为 LLM 网关最大的价值之一。2.3 部署边界与安全模型当然自托管也有它自己的代价。最明显的是多了一个需要维护的服务这对小团队来说是一个真实的负担。所以一定要控制它的部署半径Relay 不需要跟业务应用部署在同一台机器上但也不建议直接暴露在公网。我个人的实践是把 Relay 放在内网的一个独立节点上通过内部 DNS 或服务网格供各业务单元访问。鉴权方面网关层必须支持两种凭证的分离。一种是上行凭证即 Relay 访问各家模型供应商时使用的 API key另一种是下行凭证即业务服务访问 Relay 时使用的密钥。这两种凭证如果混在一起管理很容易出现权限过大的问题。合理的做法是下行凭证按项目维度分发每个项目只能访问它被允许的模型组上行凭证统一由网关持有业务开发者永远看不到真实的模型厂商密钥。至于数据安全网关层虽然是中转站但它不应该承担存储对话内容的角色除非你明确开启了日志或审计功能。Relay 这类设计的核心原则是“不落盘不窥探”正常转发模式下所有内容只存在于内存里转发完成后即释放。这样即便网关节点被攻破数据泄露面也控制在最小程度。3. Smart Routing 的工程解读好网关是调度中心不是分流器3.1 “路由”与“分流”在认知上根本不同如果说只按照固定比例把流量分给不同模型那 Relay 和其他普通负载均衡器就没有本质区别了。Smart routing 的含义要深刻得多每一次请求进来网关要根据这个请求的内容向量、上下文长度、业务优先级、当前各后端服务的健康状态和成本预算等维度动态计算一个最优的转发目标。这是一个持续在线决策的过程不是一份静态配置。我拿现实中的例子打比方。一个网约车平台的调度中心并不是把乘客给离他最近的司机就行它要综合考虑路况、司机当前的接单方向、乘客到达目的地的概率分布、平台的溢价规则。Smart routing 之于模型请求好比是同一个调度逻辑在模型世界的复刻。一个简单的请求未必非要走最贵的旗舰模型也许 70 亿参数的小模型就能产生同样的输出质量而一个高难度的代码生成任务如果错误地路由到小参数模型输出质量可能惨不忍睹还得重试一次整体成本反而更高。3.2 影响路由决策的几个关键因素先说能力域。这是最基础的维度某些模型支持工具调用和结构化输出某些模型不支持某些模型对英文的理解强于中文某些则专门做了中文语料优化某些模型上下文窗口能达到 128K 以上另外一些只有 16K。Relay 在注册每个上游模型时会带上一份能力清单。路由过滤器在请求进入时先做能力匹配凡是不满足请求要求的候选模型直接在预选阶段淘汰。其次是延迟和成本。这里要引入“代价函数”的概念。一个请求如果属于低延迟要求比如聊天交互场景那么网关应该倾向选择历史 P95 延迟更低的模型如果一个请求是后台离线批处理任务延迟要求不高那网关可以走价格更便宜、吞吐更多的模型。更高级的做法是引入用户自定义的预算上下文——同一个请求如果这个用户已经在本月消耗了较多数量的 token网关可以选择降级到一个成本更低的方案。再者是实时健康状态。LLM 供应商并不总是稳定某个时段可能某个模型端点的错误率飙升或平均响应时间明显恶化。Relay 会根据滑动窗口内的失败率指标对上游进行打分一旦连续失败次数超过阈值就会自动把流量切走。这种熔断机制不像某些网关那样直接一刀切拒绝所有请求而是转移到后备模型继续完成服务。3.3 路由策略的可视化与可干预性路由决策如果是一个黑盒那团队成员根本不敢把流量放心交给它。这也是我从 Relay 的设计里比较欣赏的一点它把每个请求的最终路由结果和原因暴露成结构化日志。比如用户可以清晰地看到“此请求被路由到模型 B原因是模型 A 的预估成本超过当前项目预算阈值模型 C 的上游健康度评分低于 60”。这种可解释性对于排查线上模糊问题至关重要。实际落地时我会建议把路由策略划分成三层全局默认策略、项目级覆盖策略、请求级标记策略。全局默认策略负责那些没有特定需求的普通流量项目级策略让不同业务线可以使用自己特定的模型偏好请求级标记则允许业务侧在下发请求时附加一个 hint比如“这单我就要用最高质量的模型预算不敏感”。三层之间按优先级从低到高叠加既保证了默认兜底又提供了灵活性。配置方式上我倾向于用 YAML 或 JSON 描述路由规则而不是在面板里点来点去。规则文件入库后走 CI 流程变更可审计可回滚。比如routes: - id: default-chat match: purpose: chat max_context_tokens: 8192 candidates: - model: llama-70b-local weight: 0.1 cost_score: 1.0 - model: gpt-4o-mini weight: 0.6 cost_score: 0.6 - model: claude-sonnet weight: 0.3 cost_score: 0.4 policy: optimization_target: latency轻量的路由规则本身不需要太复杂真正复杂的是规则背后对每个模型的实际观测数据。所以 Relay 这类网关一定要内置完善的指标采集和反馈机制路由决策质量是随着运行时间推移逐步提升的不是靠拍脑袋配出来的。4. Request Pacing 的细节学问和限流是两码事4.1 限流常用“堵”的pacing 讲究“疏”的很多网关产品都宣传自己具备限流能力但普通的 rate limiting 本质上是在单位时间窗口内允许固定数量的请求超过部分要么直接拒绝要么简单丢弃。这种策略对传统 API 场景可能够用但对于 LLM 场景往往适得其反。你可以想象一下某个业务在秒级内突然收到大量请求限流器直接给上游放行了一大批结果上游供应商在毫秒级就返回 429 限流错误网关为了让业务方成功拿到结果又触发了一轮重试这轮重试再次集中在同一时刻涌向上游结果就是流量不仅没有被削峰反而被自己放大了。Request pacing 的思路不一样它更像是一个“流量整形器”把原本尖锐的突发流量波形抹成平缓的斜坡让请求以可控的速率流入上游而不是一次性全部涌入。4.2 令牌桶在 LLM 场景里的改造Relay 实现 pacing 的底层机制本质上是对经典令牌桶算法做了一些适配。我把业务里的配置模型简化描述一下你会定义一个全局令牌池令牌以固定的速率持续补充每个请求进来时需要消耗一定数量的令牌才允许继续转发如果令牌不够请求不是被拒绝而是进入一个队列等待后续令牌生成后再按下先进先出的顺序执行。但这里有一个建模细节值得注意LLM 请求的消耗不是简单的“每次消耗一个令牌”而应该考虑请求上下文长度和可能产生的大输出。一个 128K 上下文的请求和一个 512 token 的短请求对上游造成的压力完全是两个量级。所以我更推荐按 token 预估量加权重而不是按请求数。Relay 的配置也支持这种维度你可以设定一个普通请求的估算能耗再设定长上下文请求的倍率系数使流量整形更加贴近上游的真实消耗模型。pacing 还有一个很容易被忽略的维度是“请求之间的最小间隔”。即便令牌充足也要确保相邻两个请求之间的时间间隔不至于过密。原因在于很多上游供应商的限流指标不仅看每分钟 token 总量还会看每秒请求数。只做总令牌控制而不做并发控制仍可能触发另一套限流规则。所以在网关层应该同时配置并发上限和令牌桶速率两者共同作用才能真正稳住上游消费曲线。# 简化版 pacing 决策逻辑 class PacingDecision: def __init__(self, min_interval_ms, token_bucket, max_concurrency): self.min_interval_ns min_interval_ms * 1_000_000 self.last_send_ts 0 self.token_bucket token_bucket self.inflight 0 self.max_concurrency max_concurrency def acquire(self) - bool: now time.time_ns() if self.inflight self.max_concurrency: return False if now - self.last_send_ts self.min_interval_ns: return False if not self.token_bucket.take(1): return False self.last_send_ts now self.inflight 1 return True这份伪代码看起来简单但它体现了一个核心观点pacing 是一种多维约束不是单看某一个指标。实际生产环境中我往往会为不同优先级配置不同的队列关键业务请求可以插队到低优先级请求之前低优先级请求可以选择等待更长时间或者被降级到更便宜的模型。4.3 参数整定的经验值太多人把 pacing 配置想得太复杂一上来就整一堆参数结果效果很差。我建议从三个最基本的参数入手目标 QPS、平均 token 消耗率、上游可接受的最大并发数。举个例子如果你有一个上游模型支持 300 并发而你平时的请求分布中 80% 请求的上下文加输出在 2000 token 左右剩余的 20% 在 8000 token 左右那么你可以先把单请求平均 token 消耗按 3200 来估算。假设上游限制是每分钟 900K token那你在网关层设定的全局速率建议不要超过 200K token/min 或 150K token/min留出一半的缓冲给突发和重试这个缓冲比例是我测了很多次觉得比较安全的底线。你可以用压力测试工具一点一点加压找到平滑区和陡峭区之间的拐点再反向推算网关参数而不是在网上抄一份配置就直接上线。5. 部署 Relay 的完整落地记录与实际踩坑5.1 部署形态最简单的方案其实够用了Relay 的部署形式足够轻量官方提供了 Docker 镜像也支持 Docker Compose 一键拉起。我再强调一下网络规划网关节点需要能访问目标模型供应商的 API 端点通常建议放在有外网出口的 DMZ 区或 NAT 网络内跟业务应用之间通过内部网络通信。别图方便把 Relay 的端口直接绑定到公网除非你明确知道自己正在承担怎样的安全风险。我的部署方案很简单一台 2 核 4G 的云主机足以支撑 200 QPS 的普通业务量。原因在于 LLM 网关本身不做推理请求主要开销是转发、缓存和策略计算属于典型的 IO 密集组件对 CPU 和内存的要求并不高。如果你在网关里启用了语义缓存这类重逻辑才需要升级到 4 核 8G 或者更高规格。我一直遵循一个原则网关的容器内存要预留足够大的缓冲尤其是用了内存型缓存时避免在流量高峰因为 GC 抖动导致请求超时。5.2 配置实战从零搭一个带路由和 pacing 的转发链路下面给一个我实际用的部署配置骨架删掉了内部细节保留了核心框架version: 3.9 services: relay: image: relay:latest restart: always ports: - 8080:8080 environment: RELAY_CONTROL_API_PORT: 9090 RELAY_STORAGE_DRIVER: redis RELAY_REDIS_ADDR: relay-redis:6379 volumes: - ./config/relay.yaml:/etc/relay/config.yaml:ro depends_on: - relay-redis relay-redis: image: redis:7-alpine restart: always volumes: - relay-redis-data:/data volumes: relay-redis-data:运行时核心配置文件里有这么几段是必须仔细设计的providers: openai: base_url: https://api.openai.com/v1 api_key_env: OPENAI_API_KEY models: gpt-4o: { knowledge: [general, code], max_tokens: 128000 } gpt-4o-mini: { knowledge: [general, chat], max_tokens: 128000 } anthropic: base_url: https://api.anthropic.com/v1 api_key_env: ANTHROPIC_API_KEY models: claude-sonnet: { knowledge: [general, longtext], max_tokens: 200000 } local-llama: base_url: http://10.0.0.8:8080/v1 api_key_env: LOCAL_LLM_DUMMY_KEY models: llama-70b: { knowledge: [general, code], max_tokens: 8192 } routing: default: local-llama rules: - match: { max_context_tokens: 4096, need_tool_call: false } candidates: [gpt-4o-mini, llama-70b] - match: { need_tool_call: true } candidates: [gpt-4o, claude-sonnet] pacing: enabled: true token_bucket: tokens_per_refill: 5000 refill_interval_ms: 1000 bucket_capacity: 50000 min_interval_ms: 50 max_concurrency: 100配好之后业务侧只需要把请求发到 Relay 的/v1/chat/completions端点返回结构尽量兼容 OpenAI 的格式。这意味着你现有代码里的 OpenAI SDK 几乎可以无痛切换过去只要把 base_url 改为 Relay 的地址就行。这一步做对了团队里其他同事接入时的学习成本几乎是零。5.3 踩坑记录超时、重试与 429 风暴第一个坑是超时层级。模型供应商的请求耗时本身就长如果你只设置了网关到上游的超时时间却忽略了业务到网关的超时时间那么会出现一种诡异的现象业务侧等不及主动断开但 Relay 仍然把请求转发给上游导致上游继续产生计费 token响应结果却无处可去。正确做法是永远保证业务到网关的超时大于网关到上游的超时并且预留出排队和重试的时间。第二个坑是把重试当默认手段。当上游真的发生故障时如果没有熔断机制重试反而会加重上游压力形成“重试风暴”。我的建议是重试只用于超时和 5xx 类错误绝不用于 4xx 类业务错误并且每增加一次重试要把退避时间指数递增。如果你同时开着网关层的 pacing重试请求也应该进入同一个 pacing 队列而不是绕过队列直接发出。第三个坑是上游流式响应的连接管理。LLM 接口大量使用 SSE 流式响应。一些网关产品在转发 SSE 时由于缓冲设置不合理导致客户端很久才收到第一个 token体验极差。要留意 Relay 里对流式响应的 flush 策略确保每收到上游的一个数据块就立即向下游转发而不是积攒到一定量再统一推送。这个细节在交互式对话场景中基本决定产品体验好坏。第四个坑是监控指标的缺失。不少人部署完网关就算完事直到线上出问题才开始焦虑。我其实建议部署第一天就把指标接好至少要能看到这几条曲线各上游模型的请求量、错误量、响应延迟分位数、令牌消耗速率、PACING 队列长度、被降级的请求数。队列长度是一个很重要的信号如果它持续增长说明你的上游消费能力已经接近瓶颈该扩容或换更快的模型了。没有指标这些东西全都是盲人摸象。5.4 从单网关到高可用单实例部署的 Relay 能解决大部分问题但如果业务对可用性要求高还要考虑多实例部署。由于 Relay 把状态存储在 Redis 中它天然支持多副本横向扩展。这意味着你可以在多台机器上各起一个 Relay 实例前面加一个负载均衡器比如直接用 Nginx 或云厂商的 LB后面共享同一套 Redis 数据。不过要注意的是多实例部署后Pacing 的令牌桶就需要从本地内存迁移到 Redis 的原子操作上。这时候就不能再用简单的take(1)本地判断了而是要改用 Lua 脚本或 Redis 事务来保证令牌扣减的原子性。代价是每次取令牌多了一两次 Redis RTT但对跨实例共享配额这件事来说这点开销是必须接受的。如果你对配额一致性要求不是极致也可以采用“本地令牌桶定时同步全局配额”的方案性能更好但会有一定的配额超发窗口。两种方案各有取舍我这里更倾向于告诉你要根据业务对配额精确度的容忍度来选择。6. 一些真实经验什么情况下不要上网关什么情况下果断上6.1 不建议上网关的两种场景如果团队只是做内部实验每天调用量不到几百次那么引入网关反而是一种负担。多一个组件意味着多一个故障点尤其是当你还没有专职基础设施人员时出问题排查链路会变长。这种阶段最重要的是把事情跑通用最简单的 SDK 直连反而更高效。另外如果你们所有的模型都来自同一个供应商而且短期内完全没有更换供应商的计划网关的智能路由能力对你来说可能就是多余功能。你更需要的是一个统一的密钥管理平台和请求日志系统而不是一个完整的网关。但我自己见过不少项目起初觉得单供应商够用结果不到一年就引入第二个模型到时候再迁移成本远高于一开始就预留入口。所以即便不上网关也建议在代码层面向 OpenAI 标准协议对齐给自己留一条后路。6.2 果断上网关的信号当一个项目里出现以下三个信号中的两个我就会认真评估引入自托管 LLM 网关一是业务方开始频繁询问“这个模型为什么贵”“换成另一个模型行不行”二是代码仓库里有多个模型 SDK 且每一个的调用量都不小三是出现过一次因突发流量触发上游限流导致线上事故的问题。这些信号说明模型调用这件事已经不是“写几个接口”的事情而是变成了一个需要策略、预算和治理的基础设施问题。从我几个月的使用体验看Relay 这类 self-hosted 网关带来的最大收益不是某一个单点功能的高超而是把“模型接入”“路由策略”“流量治理”“成本观测”这些事情统一收拢到一处。你可以说它不性感但生产环境的稳定性往往就是靠这种不性感的模块撑起来的。最后分享一个小技巧配置路由规则时永远要留一个无条件兜底模型避免所有候选模型同时熔断导致请求无路可走。我把兜底模型设置为本地部署的小参数模型质量差一点没关系关键时刻能保命。这一点是我踩了一天故障之后总结出来的最实在的一条经验。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。