Skip to content
EngineeringJuly 12, 2026

在 Cloudflare Workers 上做 LLM 网关

AnyRouter 跑在一组 Cloudflare Worker 上,不是常驻服务器。这套设计被具体限制卡住:128MB 内存上限禁止缓冲整段响应,3MiB 脚本上限逼出多 Worker 拆分,以及硬规则——每条上游调用都必须走 Cloudflare AI Gateway。下面是这套架构实际长什么样。

为什么用 Worker 而不是一台服务器

Cloudflare Worker 是 isolate,不是进程——请求来了才启动,处理完不在调用之间占内存或连接。没有要保活的服务器,没有要扩的集群,也没有要选的区域;同一份脚本在每个 Cloudflare 边缘节点跑。对网关这种「接请求、挑上游、把响应流回去」的活,按请求作用域的模型很合适——但也意味着每条架构决策都得尊重 isolate 限制,传统服务器没有这些上限。

其中两条几乎决定了后面所有事:每个 isolate 128MB 内存上限,以及每个 Worker 3MiB 压缩脚本上限。

只流式,不缓冲

128MB 上限让一种常见服务器写法变得危险:先把整段上游响应读进内存再转发。LLM 补全可以任意长,对无界流做 await response.text() 自己没有上限——会一直涨到 isolate 内存耗尽。

反模式为什么会炸正确写法
对无界上游数据 await response.text()顶满 128MB isolate 内存流式:return new Response(readableStream, { headers })
用模块级可变状态存请求数据不同用户的 isolate 之间串数据请求级状态放在 c.set(...) 或闭包里
后台工作用漂浮的 Promise响应返回后结果丢失、错误被吞c.executionCtx.waitUntil(promise)

实践里,chat completions、Anthropic messages 和 Responses API 都返回原生 Response,包住上游的 SSE 流,按块做格式转换,而不是攒完再回放:

export default (async (c) => {
  const upstream = await fetch(upstreamUrl, { method: "POST", body, headers })

  // Never buffer: pipe the upstream stream straight through,
  // translating SSE dialect chunk-by-chunk as it passes.
  return new Response(upstream.body.pipeThrough(dialectTranslator), {
    status: upstream.status,
    headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache" },
  })
}) satisfies HonoHandler
Streaming pattern used across the executor's chat/messages/responses paths.

3MiB 上限逼出多 Worker 拆分

路由引擎、模型目录、Durable Objects、MCP 服务、OAuth 和整套 Hono API 挤在一份脚本里时,单个 Worker 会超过 Cloudflare 3MiB 压缩脚本上限。解法是中心加辐条:一个 web Worker 管整区路由,要么经 service binding 转给专用辐条,要么自己出页面。

WorkerHostOwns
anyrouter (web hub)anyrouter.dev (apex)SSR landing pages; routes and forwards everything else
anyrouter-apiservice binding onlyHono app, executor, catalog, Durable Objects
anyrouter-dashboarddash.anyrouter.devDashboard app
anyrouter-adminadmin.anyrouter.devAdmin app
anyrouter-mcpservice binding onlyMCP server (JSON-RPC + OAuth)
anyrouter-flowcron + service bindingWorkflows and scheduled jobs

每个辐条把自己同源的 /api/* 经 service binding 代理到 anyrouter-api,浏览器从不跨域请求——不需要 CORS,也不会给每个 Worker 多开一块公网面。

每条上游都走 AI Gateway

真正打到模型供应商时,只走三种传输形态之一,全部经 Cloudflare AI Gateway,而不是直连供应商 API:

TransportReachesHow
Workers AI bindingFirst-party and partner-hosted Workers AI catalog modelsc.env.AI.run(model, body, { gateway: { id }, returnRawResponse: true })
Unified AI Gateway RESTCF unified-billing catalog, third-party modelsOne API token, cf-aig-gateway-id header, usage bills via Unified Billing
Per-provider AI Gateway RESTBYOK and direct providers (OpenAI, Anthropic, xAI, Groq, DeepInfra, ...)cf-aig-authorization header plus the provider's own key in Authorization

这层拆分对终端用户不可见——不管实际用了哪种传输,界面上都是同一枚「Cloudflare AI Gateway」徽章。背后的规则比听起来更严:每条上游都必须走 gateway.ai.cloudflare.com,文档里只有一个例外:第一方后端根本没有可代理的 HTTP origin(它用出站 WebSocket 打到用户自己的设备)。

边缘平台白送的安全原语

跑在 Workers 上,平台的加密原语就是默认能力,不是额外依赖:

  • crypto.getRandomValues() or crypto.randomUUID() — never Math.random() for tokens or ids
  • crypto.subtle for all cryptographic operations, including per-row encryption at rest
  • crypto.subtle.timingSafeEqual for secret comparison, so key checks aren't timing-attackable
  • Secrets live in wrangler secret put, never hardcoded in source or committed config

显式错误处理也比有全局异常页的框架更重要——故意不用 passThroughOnException(),因为它会把真 bug 藏在静默回退后面,而不是交给 Hono 的错误处理器去记日志、修掉。

边缘换走了什么

这些都不是免费的。上面每条上限都是架构在绕开的约束,不是被消掉的约束——3MiB 上限意味着新功能得想清楚属于哪个 Worker,只流式意味着没法在到达客户端之前对完整响应做捷径后处理。换回来的是:同一份脚本在每个边缘节点同样跑,后面没有一队服务器要运维。

AnyRouter live network stats dashboard showing aggregate request and token volume
The same Worker split serves this traffic — no dedicated servers behind it.

在自己的账号里看完整请求生命周期、模型目录和实时路由。

打开控制台

两分钟发第一笔请求

用自己的 key 免费开始,或充值按 token 计费。Go 每月 $4 额度 — $2/月,或捐一把 provider key 免费开通。

开始使用