Show HN: ShanHaiGateway – multi-upstream LLM gateway in Go A developer released ShanHaiGateway, an open-source multi-upstream LLM gateway written in Go that pools free tiers, vouchers and paid subscriptions behind a single OpenAI-compatible endpoint, reporting a production workload of 20 billion tokens per month at roughly ¥170 in cash versus about $3,800 (≈¥26,600) at list prices. The gateway uses SWRR weighted round-robin, error-class-driven cooldowns (403/429 exponential backoff from 5m to a 4h cap), task-level session affinity via X-Job-Id/X-Session-Key, and a quota-window probe that writes snapshots every 180s for a sidecar to evict exhausted backends, which the author says lifted prefix-cache hit rates from 27% to 51% with a 69% peak. The author states free and low-cost channels carried about 63% of throughput while paid commercial channels handled roughly 37%, and that the ¥170 figure depends on promotional pricing that would return to about ¥340 per month at normal rates. 中文 | English https://github.com/ffseika0304/shanhai-gateway/blob/master/README EN.md 把多个上游模型供应商组织成一个 OpenAI 兼容端点 SWRR 加权轮转 · 配额指数冷却 · 任务级亲和 · 影子留存观测 · Docker 三分钟上产线 两京锁钥无双地,万里长城第一关。 官网 seika.ltd https://seika.ltd/ · 源码、文档与问题追踪在 GitHub 外国人开了个不限量的免费模型,中国人就去薅:注册一堆号,自己薅够了转手卖给别人薅,薅到厂商受不了,免费就没了。 我干的是同一个路子,区别有两条:我免费教你怎么薅;而且我不白嫖,每个月拿一百多块养着场子,商业通道、代金券、免费池混着走,该付的付掉,剩下的让免费池扛。10 月这波实测,月 200 亿 token 的现金账单压到了 ¥170。 代价是毛病一堆。下面这些坑我全踩过,一个坑换回来一条规则,全记在运维语义表里。 山海关就是这么长出来的网关:把各路免费池、代金券和按量套餐拧成一个稳定的上游池,替你把失败截住,把账单砍下来。 你接了三家模型上游做轮询,然后开始遇到这些事: - 某家上游今天抽风,整个产品跟着 503。 你需要一个比 retry 更聪明的失败处理:按错误类别分流,配额类指数退避,满载排队,网络类只观测不惩罚。 - 前缀缓存命中率永远上不去。 轮询把同一对话的第二轮甩给另一家 provider,前缀缓存在 provider 侧,换家就凉。实测过:散装轮转下同前缀簇命中率 51~56%,恰好等于池内抽取概率, 轮询就是天花板 。 - 上游悄悄下架某个模型,后端 0 成功率空转了四天半才被发现。 4xx 不一定是客户端的错,得有人替后端认领这类失败。 - 一次断连风暴打出 1720×503。 客户端主动断开被记成后端失败,正常后端挨个进冷却,最后全网无后端可用。 - key 撒得到处都是,仓库永远不敢见人。 上面每一条都对应运维语义表里的一条规则。每条规则都是一次真实事故换来的,不是论文里的理论模型。 - SWRR 加权轮转 + 兜底链隔离 :权重/并发/超时逐后端可调,兜底后端与主力池隔离,不互相污染 - 错误分类学驱动的冷却 :403/429 指数退避 5m→10m→…→4h 封顶;满载排队 60s 而不是甩 503;客户端断连不吃冷却 - 任务级会话亲和 :优先 X-Job-Id 、退回 X-Session-Key ,同一任务钉回同一后端,前缀缓存的复用单元接上了(实测命中率 27% → 51%,峰值 69%) - quirk 怪癖层 :某家专属的报错体/用量键/出口定制写进独立包,内核零按名分支,没怪癖的上游零配置 - 多用户下游 key 计费 : sk-user- 额度核发 + 可插拔登录(注册表模式)+ 公版用户中心 - 影子留存观测 :SQLite 单库 attempts/captures(落库前自动脱敏), /api/stats 细粒度看板 - 配置热加载 : backends.json 按 mtime 热加载,改权重/加后端不重启 - sidecar 慢周期自愈 :独立 Python 进程(零第三方依赖),按窗口增量失败率做限流/降级决策,与网关快路径分工 - 额度窗探针 + 自动摘除 : scripts/quota probe.py 按后端名查每家的 5h/7d/30d 额度窗,每 180s 把快照写进 quota.json ;sidecar 每 60s 判一次,窗打满就摘掉这个后端,回落到阈值下再开回来。每个订阅独立计量,加订阅不用改任何映射表 - Docker 一把梭 :多阶段构建、纯 Go SQLite(零 CGO)、非 root 运行、healthcheck、镜像内零密钥 有人在生产里用它跑月 200 亿 Token,现金账单 ≈ ¥170/月。同一批 Token 若按各家的目录价全量付费,约 $3,800/月(≈ ¥26,600)。 本网关的核心能力之一,是把极不稳定的免费/赠送额度混进高可用主力池。它不区分主力与打杂,完全随机地发。结果是: - 纯商业化支付通道(火山 Pro/Plan 等) :只承担 ~37% 的真实请求; - 低成本订阅赠送的 Free 模型池、代理池、缓存读 :硬抗了 ~63% 的吞吐。 免费池的 429 与假死被熔断、600 秒双向超时和失败归类彻底隔离。免费池频繁报错,主业务的上游 429 依然为 0。 为什么免费池塌了不影响主业务 :网关和调用方都是 600 秒兜底,两端的"切断"最后都落到换一条道重发。上游主动切断,那条道进冷却,调用方的重试自动落到别的道;网关自己先超时,它内部就换道重发,调用方几乎无感。 再挂一个订阅账号,就多一份免费池 :每个 Go 订阅都自带一个 Free 模型池,所以付费占比会持续走低(实测 43.6% → 再挂一个 → ~37%),总吞吐同步扩容。账单取决于哪条通道便宜,跟用了多少 token 关系不大。厂商只是配置里可换的路由项,别当成架构假设。 在生产日耗 10 亿 Token 的场景下,靠路由平摊、缓存亲和(命中率 70%)和"多号代金券 + Free 赠送池"的滚动轮转,月度实际现金支出 ≈ ¥170: | 通道 | 数量 | 月 Token 承载 | 月现金 | |---|---|---|---| | 火山订阅 | 4 个 | 4 × 20 亿 = 80 亿 | 2 个现金付 ¥99.8 ,另 2 个由 2 张 50 元渠道券覆盖(还剩 10 元) | | opencode Go | 2 个 | 2 × 6 亿付费 Token = 12 亿 | 收购半价 $5/个 (≈¥70) | | Free 赠送池 · credits 赚取 · 缓存读 | — | 承接其余 ~63% | ¥0 | → ¥170 买 200 亿 Token ≈ ¥0.85 / 亿;真正按全新 token 全额计价的只有 23%。同量按目录价全付 ≈ $3,800/月。光靠代码(缓存亲和 + 免费档混编)加上池子配置层的按请求大小分流,能把按目录价付费的量压到 1/4~1/3,叠加渠道策略后总降本 99%。 | 能力 | 做什么 | 为什么这是架构而不是配置技巧 | |---|---|---| | 内部会话亲和 | 同一任务( X-Job-Id ,退回 X-Session-Key )的每一轮钉回同一条道 | 轮询把第二轮甩给别家,前缀缓存就废了。实测散装轮转下同前缀簇命中率 51~56%,恰好等于池内抽取概率,轮询就是天花板;钉回去后实测 27%→51%、峰值 69% | | 外部会话头透传 | 按后端配 session header (如 x-opencode-session ),把会话值写进厂商自己的头 | 有些厂商的缓存是认自家头的:不配这个字段,厂商侧永远不命中。配字段即可,不用改代码( attempt.go 一行) | | 怪癖层 | 某家专属的报错体 / 用量键 / 出口定制,写进独立包( core/quirk ),内核零按名分支 | 没怪癖的上游零配置;有怪癖的只改一个文件。默认行为 = 通用行为,不接 quirk 层与不接它的上游逐位一致 | | 每个订阅独立计量 + 额度窗自动摘除 | 探针按后端名分组查每家的 5h/7d/30d 额度窗、每 180s 写一份 quota.json 快照;sidecar 每 60s 判一次:窗打满就摘掉这个后端,回落到阈值下再开回来 | 加一个订阅不用改任何映射表。到期换订阅是"禁一个、开一个"的轮转,周期错开即可保证每个都被烧干净、并发不塌 | | 按请求大小分流(池子配置层,不在内核) | 小请求走按 Token 池、大请求走按次池(22,222 Token 平衡点) | 两种计费制的单位成本不同,按体量选池比随机发便宜得多,实测省 35%。这一条靠权重与多后端配置实现,网关内核只管把它们各自发稳 | | 免费/低档通道混编 | Free 赠送池与商业通道同池轮转 | 免费额度最不稳,但只要它塌了不影响主业务(见上),就有资格当正规军 | 接一个订阅两步,其余全自动。① accounts 加一条:唯一名字 + api keys (调用用)+ 配额查询凭据(AK/SK,随部署配置下发;缺了看板报「缺凭据」、不动作,绝不借别人的凭据);② backends 加一条后端, base url 指该家的套餐端点、 account 指上面那条。30s 内热加载上线,探针按后端名开始查它的额度窗,sidecar 开始自动管它的"打满就摘、回落就开"。到期换订阅:新建账号+后端、把旧的禁掉即可(周期错开,每个都能烧干净)。 声明,请连着数字一起读。 ① 活动期价格有保质期:券与半价随时会退,回归常态约 ¥340/月,降本仍 98%+。 ② "免费/低档承接 63%"随上游给量与赚取速率波动(实测区间 37%~~57%,把缓存读计入则更高),不承诺具体额度。 ③ 免费/赠送额度是上游给的,不是本项目给的。本项目永久开源的是缓存亲和、换道重试、每个订阅独立计量额度并自动摘除。 ④ 数字为 2026-10 单机生产实测(日耗 4.4~~13 亿 Token),口径与上表一致,测量方法见随版本发布的标定记录。 ⑤ 不锁死任何厂商:主力池占比三天内在 11%↔43% 之间摆动(换的是配置里的账号与权重),路由 / 缓存亲和 / 兜底逻辑一行没改。 你只想有个能用的网关 :Docker 三条命令起一个 OpenAI 兼容端点,把 base url 换掉就能跑。权重、冷却、亲和全部开箱默认,不用读架构章节。 你要把它钉进产线,或者魔改路由 :架构图、包职责表、八条运维规则、怪癖层接口、captures 消费契约都在下面。每个魔法数字为什么是这个值,都有事故出处。 git clone https://github.com/ffseika0304/shanhai-gateway.git && cd shanhai-gateway 1. 准备真实配置(进挂载卷,不进 git) mkdir -p runtime/config runtime/data cp config/backends.example.json runtime/config/backends.json 改成你的后端 cp config/keys.example.json runtime/config/keys.json 改成你的下游 key 2. 起容器(宿主侧默认 :18016,SHG PORT 可覆盖) docker compose -f deploy/docker-compose.yml up -d --build 3. 冒烟 curl -s http://127.0.0.1:18016/health {"status":"ok","version":"0.5.1"} curl -s http://127.0.0.1:18016/api/stats | head -c 400 要求 Go ≥ 1.27 : make build 等价于 go build -o shg ./cmd/shg cp config/backends.example.json config/backends.json 改字段,凭据全假也能先跑 export LISTEN=":18006" LAN 免账口 export ROUTER VALIDATE=0 跳过启动外网自检(配真 key 时删掉) ./shg & curl -s http://127.0.0.1:18006/v1/chat/completions \ -H 'Content-Type: application/json' \ -d '{"model":"demo-model","messages": {"role":"user","content":"hi"} }' 200 即通。健康检查 GET /health;流量看板 GET /api/stats LAN 口 POST /v1/chat/completions 不强制 key(免账通道);下游 key 计费只在公网口/用户中心启用。 | | 裸跑 | Docker | |---|---|---| | 配置 |