为什么要关心 API 网关?
当单体应用拆分成微服务之后,第一个扑面而来的问题就是:客户端到底该访问谁?
举个我实际遇到过的场景。一套电商系统拆成了三个服务:
- 认证服务:负责登录、发 token;
- 用户服务:管理会员资料;
- 订单服务:处理下单、查询。
如果让 App 和网页端直接记住三个服务的地址,会立刻陷入混乱:每个服务的地址、端口、协议都不一致,前端得写死一堆 URL;服务一旦扩容或迁移,前端还得跟着改;更别说鉴权、限流这些横切逻辑要在每个服务里各写一遍。
这时候,在客户端和各服务之间架一层"入口"------也就是 API 网关------就成了标准解法。所有请求先打到网关,由网关根据规则把请求转给对应的后端服务。客户端只认一个地址,后端的任何变动都被挡在网关后面。
在 .NET 生态里,微软开源的 YARP(Yet Another Reverse Proxy) 是个非常轻量好用的选择:它本身就是一个反向代理组件,直接作为 ASP.NET Core 中间件跑在你自己的进程里,用配置文件就能完成路由、负载均衡等核心能力,几乎没有额外的心智负担。
一、准备工作:环境与项目骨架
动手之前,先确认机器上有 .NET 6.0 或更高版本的运行时。以 Linux 为例,安装方式有很多,官方提供了一键脚本,大致流程是下载安装脚本后以管理员权限执行,具体命令在 .NET 官方文档里有完整说明,这里不展开。
接着创建一个空 Web 项目,并把 YARP 包引进来:
bash
dotnet new web -n GatewayDemo
cd GatewayDemo
dotnet add package Yarp.ReverseProxy
两步做完,网关工程就算立起来了。
二、跑通第一个转发规则
YARP 的使用模式非常固定:在代码里注册反向代理服务,在配置文件里描述转发规则,两者配合即可。
Program.cs 里只需三行核心代码:
csharp
var builder = WebApplication.CreateBuilder(args);
// 注册反向代理,并从配置文件的 "ReverseProxy" 节点读取规则
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// 挂载代理处理管线
app.MapReverseProxy();
app.Run();
真正的转发规则写在 appsettings.json 里。YARP 有两个核心概念需要先理解:
- Routes(路由):描述"什么请求该被转发",通过匹配路径来决定;
- Clusters(集群):描述"转发到哪儿去",即后端服务(Destinations)的地址集合。
举个最基础的例子:把所有 /pay/* 开头的请求转发给支付服务:
json
{
"ReverseProxy": {
"Routes": {
"pay-route": {
"ClusterId": "pay-cluster",
"Match": {
"Path": "/pay/{**catch-all}"
}
}
},
"Clusters": {
"pay-cluster": {
"Destinations": {
"pay-service": {
"Address": "https://localhost:5101/"
}
}
}
}
}
}
启动之后,客户端请求 http://网关/pay/xxx,网关就会原样转发到 https://localhost:5101/xxx。第一座"桥"就这样通了。
三、让路由更聪明:优先级与负载均衡
3.1 多条路由,谁先匹配?
当网关上有多个服务、多条路由时,匹配顺序就变得重要。YARP 用 Order 字段控制优先级,数字越小越先被尝试。
举个例子:我想把 /api/users/* 精确地转给用户服务,而其余所有请求都转给一个默认服务:
json
"Routes": {
"user-route": {
"ClusterId": "user-cluster",
"Order": 1,
"Match": { "Path": "/api/users/{**catch-all}" }
},
"default-route": {
"ClusterId": "default-cluster",
"Order": 2,
"Match": { "Path": "/{**catch-all}" }
}
}
没有 Order 时,YARP 会按自身规则排序;一旦路由数量多了,显式声明优先级是避免"请求被错误路由"的最可靠手段。
3.2 权重负载均衡:灰度发布的利器
生产环境里,一个服务通常不只部署一个实例。YARP 允许在一个集群下配置多个目标地址,并用 LoadBalancingPolicy 决定分发策略;其中 WeightedRoundRobin(加权轮询)配合 Weight 权重字段,能实现很有意思的流量控制。
还是用支付服务举例。假设支付服务要上线 v2 版本,为了稳妥起见,希望先让一小部分流量(比如 25%)打到新版本上观察效果:
json
"Clusters": {
"pay-cluster": {
"Destinations": {
"pay-v1": { "Address": "https://localhost:5101/", "Weight": 3 },
"pay-v2": { "Address": "https://localhost:5102/", "Weight": 1 }
},
"LoadBalancingPolicy": "WeightedRoundRobin"
}
}
新版本跑一段时间确认没问题后,把权重调成 3 : 3 甚至关掉旧实例即可,整个过程不写一行代码。这就是典型的灰度发布/金丝雀发布实践,非常适合用来逐步放量验证新版本。
四、生产环境加固:HTTPS 与 Kestrel
网关作为流量的总入口,安全性和性能都马虎不得。
4.1 强制 HTTPS
在管线中启用 HTTPS 重定向,让所有 HTTP 请求自动跳转到 HTTPS:
csharp
app.UseHttpsRedirection();
一句话,就挡住了"明文传输"这个最常见的隐患。
4.2 自定义 Kestrel 监听
默认情况下,Kestrel 的监听配置可能不满足生产需求。YARP 场景下,我们常常需要网关同时提供 HTTP 和 HTTPS 两个入口,可以用 ConfigureKestrel 精确控制:
csharp
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000); // HTTP
options.ListenAnyIP(5001, o => o.UseHttps()); // HTTPS
});
另外,如果客户端环境支持,Kestrel 也支持 HTTP/3 协议,能带来更低延迟的连接体验,配置方法在微软官方文档里有详细说明,按需开启即可。
五、看得见的网关:日志与请求头透传
网关一旦出问题,最痛苦的就是"不知道请求到底发生了什么"。所以可观测性配置必须一开始就做好。
5.1 开启细粒度日志
YARP 自身的日志默认不会全量输出,排查问题时可以把它的日志级别调到 Debug:
json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Yarp": "Debug"
}
}
}
这样每条转发的匹配过程、目标选择、耗时都会有记录,定位"请求为什么没走我预期的路由"会轻松很多。
5.2 透传真实客户端 IP
网关转发请求时,后端服务拿到的连接来源是网关本身,而不是真实客户端。要保留真实 IP,常规做法是往请求头里塞一个自定义字段,后端再从中读取:
csharp
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(transformBuilder =>
{
transformBuilder.AddRequestTransform(context =>
{
var remoteIp = context.HttpContext.Connection.RemoteIpAddress?.ToString();
context.ProxyRequest.Headers.Add("X-Forwarded-For", remoteIp);
return ValueTask.CompletedTask;
});
});
这样日志、风控、限流逻辑都能拿到客户端真实地址,而不是清一色的网关 IP。
六、实战串联:多服务统一入口
把前面所有能力串起来,一个典型的微服务网关配置长这样------认证、用户、订单三个服务统一收口:
json
{
"ReverseProxy": {
"Routes": {
"auth-route": {
"ClusterId": "auth-cluster",
"Match": { "Path": "/auth/{**catch-all}" }
},
"user-route": {
"ClusterId": "user-cluster",
"Match": { "Path": "/users/{**catch-all}" }
},
"order-route": {
"ClusterId": "order-cluster",
"Match": { "Path": "/orders/{**catch-all}" }
}
},
"Clusters": {
"auth-cluster": {
"Destinations": { "auth-service": { "Address": "https://localhost:6001/" } }
},
"user-cluster": {
"Destinations": { "user-service": { "Address": "https://localhost:6002/" } }
},
"order-cluster": {
"Destinations": { "order-service": { "Address": "https://localhost:6003/" } }
}
}
}
}
客户端只需要记住网关这一个地址:
GET https://gateway.example.com/auth/login
GET https://gateway.example.com/users/profile
POST https://gateway.example.com/orders/create
每个服务的地址、端口、是否扩容,对客户端完全透明。这就是网关带来的核心价值。
七、写在最后:下一步往哪儿走
本文覆盖了 YARP 从环境搭建、基础路由、负载均衡到 HTTPS、日志监控的完整主链路,足够支撑一个中小规模的微服务网关跑起来。
如果要把网关推向企业级,还有几个值得深入的方向:
- 路由动态化:把路由规则从配置文件迁移到数据库,支持运行时动态调整,不用重启网关;
- 熔断与限流:当某个下游服务故障时自动熔断,避免雪崩;对关键接口做速率限制;
- 分布式追踪:结合 OpenTelemetry 等方案,让一次跨服务的请求链路可完整追踪。
网关不是终点,而是微服务治理的起点。把这层入口做扎实,后面的服务治理会顺畅很多。
补充一句:YARP 的完整 API 与更多高级特性,建议直接查阅微软官方文档和 GitHub 仓库,那里始终是最权威的信息来源。