1. 从单体到网关:.NET 6 + YARP 到底解决什么问题
如果你手上有一堆 .NET 6 的 Web API 项目,每个服务各自监听不同端口,前端调用时要在http://localhost:5001、http://localhost:5002之间来回切换,那 API 网关就是绕不过去的一环。YARP(Yet Another Reverse Proxy)是微软官方开源的反向代理库,它不是一个独立进程,而是一组可以塞进 ASP.NET Core 管道的中间件。这意味着你可以用写 Controller 的方式去写代理逻辑,路由、集群、转换规则都能用 C# 或 JSON 配置。
它适合谁?适合已经在用 .NET 6、想给微服务加统一入口、又不想引入 Nginx 或 Envoy 这类外部组件的团队。YARP 的核心概念只有两个:Route(路由)负责匹配进来的请求,Cluster(集群)负责决定转发到哪些后端。一个 Route 绑定一个 Cluster,Cluster 里可以放多个 Destination,负载均衡策略就作用在这些 Destination 上。
我试过把三个本地 API 服务挂到同一个 YARP 网关后面,前端只需要记住http://localhost:8000一个地址,剩下的路径分发、后端选择全部由网关处理。下面从零开始,把反向代理、负载均衡、多后端路由三类场景的配置和验证步骤完整走一遍,最后再把上游 endpoint 切到 TaoToken 的统一通道,方便后续按场景扩展。
2. TaoToken 前置准备:统一 Key 与 API 通道
在把 YARP 的上游地址指向 TaoToken 之前,需要先拿到可用的 API Key 和 Base URL。TaoToken 的 API 入口是https://taotoken.net/api,控制台里可以创建和管理 Key。这一步不复杂,但有几个细节容易踩坑。
首先,注册或登录后进入控制台,找到 API Keys 页面,新建一个 Key。建议按用途命名,比如yarp-gateway-dev,这样后面在 YARP 配置里看到这个 Key 就知道是给网关用的。Key 只在创建时完整显示一次,复制后先存到本地环境变量或密钥管理工具里,不要直接硬编码进appsettings.json提交到仓库。
其次,确认你要调用的模型 ID。TaoToken 的模型对话页面可以查看当前可用的模型列表,选一个你打算在网关后面代理的模型,记下它的 Model ID。YARP 本身不关心模型,它只负责转发 HTTP 请求,但你的后端服务或调用方需要知道往请求体里填哪个模型名。
然后,把 Base URL 和 Key 准备好。YARP 的 Cluster Destination 地址填https://taotoken.net/api,认证头通过 Transform 或后端服务自行添加。如果你希望网关统一注入 Authorization 头,可以在 YARP 的 Transform 里配置RequestHeader设置,把Bearer <你的Key>加到转发请求上。这样后端服务不需要各自管理 Key,网关层统一处理。
最后,验证 Key 是否可用。可以用 curl 直接打一次模型对话接口,确认返回正常。这一步做完,再进入 YARP 的配置环节,避免后面排查问题时分不清是网关配置错了还是 Key 本身有问题。
3. 可复制配置:反向代理、负载均衡与多后端路由
这一节给出三套可以直接粘贴的配置片段,分别对应反向代理、负载均衡和多后端路由。所有配置都基于 .NET 6 的appsettings.json和Program.cs,路径和字段名与 YARP 官方文档一致。
3.1 反向代理:按路径分发到不同服务
先看最基础的反向代理。假设你有两个后端服务:用户服务跑在http://localhost:5001,订单服务跑在http://localhost:5002。你希望外部访问http://localhost:8000/api/user/...时转发到用户服务,访问http://localhost:8000/api/order/...时转发到订单服务。
appsettings.json配置如下:
{ "ReverseProxy": { "Routes": { "user-api": { "ClusterId": "user-cluster", "Match": { "Path": "api/user/{**catch-all}" }, "Transforms": [ { "PathRemovePrefix": "/api/user" } ] }, "order-api": { "ClusterId": "order-cluster", "Match": { "Path": "api/order/{**catch-all}" }, "Transforms": [ { "PathRemovePrefix": "/api/order" } ] } }, "Clusters": { "user-cluster": { "Destinations": { "user-service": { "Address": "http://localhost:5001" } } }, "order-cluster": { "Destinations": { "order-service": { "Address": "http://localhost:5002" } } } } } }Program.cs里只需要三行核心代码:
using Yarp.ReverseProxy; var builder = WebApplication.CreateBuilder(args); builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); var app = builder.Build(); app.MapReverseProxy(); app.Run();这里的关键是PathRemovePrefix转换。外部请求/api/user/users进入网关后,前缀/api/user被去掉,转发到用户服务的路径变成/users。如果你的后端服务本身已经带了/api/user前缀,那就不需要这个转换,直接转发即可。
3.2 负载均衡:同一集群多实例分流
当同一个服务有多个实例时,把它们的地址都放进同一个 Cluster 的 Destinations 里,然后指定LoadBalancingPolicy。YARP 内置了RoundRobin、Random、LeastRequests等策略。
{ "ReverseProxy": { "Routes": { "default": { "ClusterId": "backend-cluster", "Match": { "Path": "{**catch-all}" } } }, "Clusters": { "backend-cluster": { "Destinations": { "server1": { "Address": "http://localhost:5001" }, "server2": { "Address": "http://localhost:5002" }, "server3": { "Address": "http://localhost:5003" } }, "LoadBalancingPolicy": "RoundRobin" } } } }把LoadBalancingPolicy改成Random就是随机分流。实测下来,RoundRobin在实例性能相近时最稳,LeastRequests适合实例处理能力不一致的场景。注意 Destination 的 key 名字只是标识,不影响转发,但建议起有意义的名字方便排查。
3.3 多后端路由:按请求头选择集群
多租户或多环境场景下,可以根据请求头来动态选择后端。比如请求里带X-Tenant: tenant1就走租户一的集群,带X-Tenant: tenant2就走租户二。
{ "ReverseProxy": { "Routes": { "tenant1": { "ClusterId": "tenant1-cluster", "Match": { "Headers": { "X-Tenant": { "Values": [ "tenant1" ] } } } }, "tenant2": { "ClusterId": "tenant2-cluster", "Match": { "Headers": { "X-Tenant": { "Values": [ "tenant2" ] } } } } }, "Clusters": { "tenant1-cluster": { "Destinations": { "server1": { "Address": "http://localhost:5001" } } }, "tenant2-cluster": { "Destinations": { "server2": { "Address": "http://localhost:5002" } } } } } }这种配置下,同一个路径/api/data会根据请求头转发到不同后端。如果你要把上游 endpoint 统一改到 TaoToken,只需要把 Cluster 里的 Address 换成https://taotoken.net/api,然后在 Transform 里加上 Authorization 头。这样网关层就变成了一个统一的 API 通道入口,后面按场景扩展时只需要增删 Route 和 Cluster。
4. 验证请求:从 curl 到成功结果
配置写完后,启动网关和各个后端服务,用 curl 逐个验证。先确认网关本身在监听http://localhost:8000,然后按场景测试。
反向代理场景下,启动用户服务和订单服务,分别监听 5001 和 5002。然后执行:
curl http://localhost:8000/api/user/users curl http://localhost:8000/api/order/orders如果返回的是用户服务和订单服务的正常响应,说明路径匹配和前缀移除都生效了。如果返回 404,先检查Match.Path的写法,{**catch-all}必须放在路径末尾,且前缀不要带多余的斜杠。
负载均衡场景下,启动三个后端实例,每个实例在响应里带上自己的端口号。然后连续请求多次:
for i in {1..6}; do curl -s http://localhost:8000/api/values; echo; done如果看到端口号按顺序轮换,说明RoundRobin生效。如果每次都打到同一个实例,检查LoadBalancingPolicy的拼写,以及 Destinations 里是否确实配置了多个地址。
多后端路由场景下,用请求头区分:
curl -H "X-Tenant: tenant1" http://localhost:8000/api/data curl -H "X-Tenant: tenant2" http://localhost:8000/api/data两次请求应该返回不同后端的数据。如果不带X-Tenant头,YARP 会因为没有任何 Route 匹配而返回 404,这是预期行为。你可以加一个默认 Route 来兜底。
当上游地址切到 TaoToken 后,验证方式类似,只是后端变成了https://taotoken.net/api。你可以用模型对话接口做一次端到端测试,确认网关转发、认证头注入、响应回传整条链路通畅。
5. 常见报错排查:401、local proxy failed 与 reading choices
这一节整理几个实际会遇到的报错和排查思路。
401 Unauthorized:如果网关转发到 TaoToken 后返回 401,先检查 Authorization 头有没有正确注入。YARP 默认不会自动加认证头,你需要在 Transform 里配置RequestHeader,或者在后端服务里统一处理。另外确认 Key 没有过期,以及请求头格式是Bearer <Key>,中间有一个空格。
local proxy failed:这个报错通常出现在网关无法连接到后端 Destination。检查 Address 是否可达,本地服务是否真的在监听对应端口。如果是 Docker 环境,localhost在容器里指向容器自身,需要用宿主机的实际 IP 或服务名。另外确认没有防火墙拦截。
reading choices 相关报错:如果你代理的是模型对话接口,返回体里解析choices字段时报错,先确认响应体是不是完整的 JSON。YARP 默认会缓冲响应,但如果后端返回的是流式数据,需要检查是否开启了流式转发。另外确认请求体里的 Model ID 是 TaoToken 支持的模型,模型名写错时上游可能返回错误结构,导致下游解析失败。
OAuth 或认证跳转问题:如果后端服务本身有 OAuth 流程,经过 YARP 代理后回调地址可能不对。需要在 Transform 里重写Location头或Host头,确保回调地址指向网关而不是后端服务。
排查时建议先绕过网关,直接用 curl 打后端地址,确认后端本身正常。然后再经过网关打一次,对比两次的请求头和响应体差异。YARP 的日志级别调到Debug可以看到详细的路由匹配和转发信息,对定位问题很有帮助。
6. 把上游 endpoint 切到 TaoToken:统一 Key 与后续扩展
前面所有场景的 Cluster Destination 都可以指向 TaoToken 的 API 地址。以反向代理场景为例,把user-cluster的 Address 改成https://taotoken.net/api,然后在 Route 的 Transforms 里加上认证头:
{ "Transforms": [ { "PathRemovePrefix": "/api/user" }, { "RequestHeader": "Authorization", "Set": "Bearer <你的TaoToken Key>" } ] }这样外部请求进入网关后,网关统一加上 Key 再转发到 TaoToken。后端服务不需要各自管理 Key,换 Key 时只改网关配置一处。如果你用的是 Coding Plan 或需要长期跑 Agent 任务,建议把 Key 放在环境变量里,通过builder.Configuration读取,避免明文写在 JSON 里。
后续按场景扩展时,新增一个后端只需要在 Clusters 里加一个 Destination,或者在 Routes 里加一条匹配规则。比如你要加一个按模型 ID 分流的场景,可以用Match.QueryParameters匹配请求参数里的model字段,转发到不同的 Cluster。YARP 的配置是热加载的,改完appsettings.json后不需要重启进程,网关会自动应用新配置。
验证整条链路时,用模型对话接口发一次请求,确认返回正常。如果遇到问题,回到第 5 节的排查步骤,先确认 Key 和 Base URL 正确,再检查 Transform 有没有生效。接入文档里有更详细的参数说明,API Keys 页面可以随时新建或吊销 Key。整套配置跑通后,你就有了一个基于 .NET 6 + YARP 的 API 网关,既能做反向代理和负载均衡,也能作为统一的上游通道按场景灵活扩展。