用 YARP 为 .NET 微服务搭一座“立交桥“:路由转发与生产配置实践

为什么要关心 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、日志监控的完整主链路,足够支撑一个中小规模的微服务网关跑起来。

如果要把网关推向企业级,还有几个值得深入的方向:

  1. 路由动态化:把路由规则从配置文件迁移到数据库,支持运行时动态调整,不用重启网关;
  2. 熔断与限流:当某个下游服务故障时自动熔断,避免雪崩;对关键接口做速率限制;
  3. 分布式追踪:结合 OpenTelemetry 等方案,让一次跨服务的请求链路可完整追踪。

网关不是终点,而是微服务治理的起点。把这层入口做扎实,后面的服务治理会顺畅很多。

补充一句:YARP 的完整 API 与更多高级特性,建议直接查阅微软官方文档和 GitHub 仓库,那里始终是最权威的信息来源。

相关推荐
点纭4 小时前
C 语言 第七章 常用函数(3)
c语言·开发语言·算法·oracle·c#
用户788477316345 小时前
用 .NET MAUI 一套 C# 同时出 Windows 和 Android:哪些是真能共享,哪些是 marketing
c#
Lost of 程序猿6 小时前
用 Quartz.NET 优雅地处理 .NET 定时任务:从选型到 Docker 部署全记录
后端·c#·asp.net
林川~0116 小时前
Unity 万能物理检测工具:射线检测 / 范围检测 / 层级过滤 / 编辑器可视化(可直接拿去用)
游戏·unity·c#·射线检测·通用工具
He BianGu20 小时前
【WPF-VisionMaster】机器视觉通用平台V5.0版本发行说明
opencv·c#·wpf·halcon·机器视觉·visionmaster
tang_042720 小时前
【Hi.Ltd 专题】第9期:Interop 配置互操作(JSON/INI/XML/YAML/注册表/扫码)
经验分享·c#·hi.ltd系列
tang_04271 天前
【Hi.Ltd 专题】第7期:Threading 采集线程、锁与 LRU 缓存
经验分享·c#·hi.ltd系列
A_nanda1 天前
C# 界面卡顿:从定位到根治
c#
2501_930707781 天前
使用C#代码为新创建的 Word 文档创建目录
c#·word