大型 ASP-NET 项目中的 API 网关

大型 ASP-NET 项目中的 API 网关:作用、实践与必要性分析

在现代大型软件架构(尤其是微服务架构)中,Gateway 项目(API 网关)通常扮演着“守门人”和“交通枢纽”的关键角色。本文将深入探讨其核心作用、技术选型及实施策略。

1. Gateway 项目起什么作用?

API 网关不仅仅是一个反向代理,它负责处理非业务逻辑的 横切关注点 ,将客户端与后端服务的内部复杂性隔离开来。

1.1 核心功能

  1. 请求路由 (Routing / Reverse Proxy)

    • 作用: 将外部请求(如 /api/orders)转发到内部特定的微服务地址(如 10.0.0.5:5000)。
    • 价值: 客户端无需知道后端服务的具体 IP 和端口,实现了后端架构的透明化。
  2. 统一认证与授权 (Authentication & Authorization)

    • 作用: 在请求到达具体业务服务之前,验证用户身份(如校验 JWT Token)。
    • 价值: 避免在每个微服务中重复编写认证代码。网关验证通过后,通常将用户信息(User Claims)通过 HTTP Header 传递给下游服务。
  3. 服务聚合 (Response Aggregation)

    • 作用: 客户端发起一个请求,网关向后端的 Service A、Service B、Service C 分别发起请求,并将结果合并后返回给客户端。
    • 价值: 减少客户端与服务器之间的网络往返次数(Chattiness),优化移动端体验。
  4. 协议转换 (Protocol Translation)

    • 作用: 对外暴露 RESTful API (HTTP/JSON),对内使用性能更高的 gRPC 或 TCP 通信。
  5. 流量控制与熔断 (Rate Limiting & Circuit Breaking)

    • 作用: 限制特定 IP 或用户的请求频率;当下游服务不可用时,直接返回错误或缓存数据,防止级联雪崩。
  6. 日志与监控 (Logging & Observability)

    • 作用: 作为流量入口,网关是做集中式访问日志、请求耗时统计和分布式追踪(Tracing)的最佳位置。

2. 是否有必要引入 Gateway?

引入网关是一个权衡(Trade-off)的过程。

2.1 什么时候 必须强烈建议 使用?

  • 微服务架构: 如果你有超过 3-5 个独立的服务,客户端直接管理这些端点会变成噩梦。
  • 多端适配: 需要为 Web 端、iOS、Android 提供不同粒度的 API(即 BFF - Backend for Frontend 模式)。
  • 安全隔离: 内网服务不希望直接暴露公网 IP,需要一个统一入口进行防火墙配置。
  • 协议异构: 后端使用了 gRPC、WebSocket 等多种协议,但前端只希望使用标准 HTTP。

2.2 什么时候 不需要 使用?

  • 单体应用 (Monolithic): 如果你的应用就是一个庞大的 ASP-NET Core Web API 项目,直接使用 Nginx 或 IIS 做反向代理即可,单独写一个 Gateway 项目属于过度设计。
  • 极低延迟要求: 网关增加了一层网络跳转,必然带来毫秒级的延迟。如果是对实时性要求极高的内部高频交易系统,直连可能更好。

3. 如何在 ASP-NET 中实践?

在 .NET 生态系统中,目前主要有两种主流的技术方案来实现代码级网关。

3.1 技术选型

A. YARP (Yet Another Reverse Proxy) —— 推荐

微软官方推出的高性能反向代理库。

  • 优点: 完全基于 ASP-NET Core 构建,性能极高,高度可定制(C# 代码控制),支持 HTTP/2 和 gRPC,微软官方支持。
  • 适用场景: 构建现代化的、高性能的自定义网关。

B. Ocelot

社区长期维护的老牌网关库。

  • 优点: 功能丰富(内置简单的聚合、限流),配置简单(基于 json)。
  • 缺点: 性能略逊于 YARP,目前维护活跃度不如以往,在复杂的 gRPC 场景下支持有限。
  • 适用场景: 中小型微服务项目,或者遗留系统。

C. 云原生网关 (Azure API Management / AWS API Gateway)

  • 优点: 全托管,无需维护代码,功能极其强大(计费、开发者门户)。
  • 缺点: 贵,且被云厂商绑定。

3.2 实践架构:基于 YARP 的网关实现

假设我们使用 YARP 来构建网关。

步骤 1: 创建项目

创建一个空的 ASP-NET Core Web API 项目(去掉 Controller 支持,因为网关主要是中间件)。

dotnet new web -n MyApiGateway
dotnet add package Yarp.ReverseProxy

步骤 2: 配置 Program.cs

var builder = WebApplication.CreateBuilder(args);

// 1. 注册 YARP 服务
// 从配置文件加载路由和集群信息
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));

var app = builder.Build();

// 2. 添加中间件
// 可以在这里添加全局异常处理、跨域(CORS)、认证(Auth)等
app.UseAuthentication();
app.UseAuthorization();

// 3. 映射 YARP
app.MapReverseProxy();

app.Run();

步骤 3: 配置 appsettings.json

这是核心配置,定义了 路由 (Routes)集群 (Clusters)

{
  "ReverseProxy": {
    "Routes": {
      "user-service-route": {
        "ClusterId": "user-cluster",
        "Match": {
          "Path": "/api/users/{**remainder}"
        },
        "Transforms": [
          { "PathPattern": "{**remainder}" } // 路径重写,去掉 /api/users 前缀
        ]
      },
      "order-service-route": {
        "ClusterId": "order-cluster",
        "Match": {
          "Path": "/api/orders/{**remainder}"
        }
      }
    },
    "Clusters": {
      "user-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://localhost:5001"
          }
        }
      },
      "order-cluster": {
        "Destinations": {
          "destination1": {
            "Address": "http://localhost:5002"
          }
        }
      }
    }
  }
}

3.3 最佳实践建议

  1. 采用 BFF 模式 (Backend for Frontend):

    • 不要试图用一个超级网关处理所有客户端。
    • 建议为不同的客户端建立独立的网关,例如 MobileGatewayWebGateway。Web端可能需要聚合更多数据,而移动端更在意流量精简。
  2. 认证卸载 (Auth Offloading):

    • 在网关层解析 JWT Token。
    • 如果 Token 有效,解析出 UserId 和 Role。
    • 关键点: 将 UserId 放入 HTTP Header(例如 X-User-Id)转发给下游服务。下游服务不再解析 Token,只读取 Header(需确保内网安全,防止 Header 伪造)。
  3. 集成 Polly 做弹性设计:

    • 虽然 YARP 处理了连接,但建议结合 Polly 实现复杂的重试策略和超时控制。
  4. 使用分布式追踪 (OpenTelemetry):

    • 网关是请求链路的第一环。必须在网关生成 TraceId,并确保它传递到所有下游服务,否则排查问题会极其困难。
  5. 避免包含业务逻辑:

    • 切记: 网关不应该包含复杂的业务规则(如“如果订单金额大于100则如何如何”)。网关应该只处理路由、协议和安全。业务逻辑一旦渗入网关,会导致网关变成难以维护的“单体巨石”。

4. 总结

在大型 ASP-NET 项目中,Gateway 是微服务架构落地的基础设施

  • 作用: 解耦前后端,统一流量入口,集中处理非业务逻辑(安全、流控)。
  • 必要性: 对于分布式、微服务系统是 必需的 ;对于单体系统是 不必要的
  • 选型: 首选 YARP (Microsoft) 进行 C# 代码级控制,或者使用云厂商的 PaaS 网关。

通过合理配置网关,你的后端服务将变得更加纯粹(专注于业务),而前端的调用逻辑也将变得更加简单和统一。

Ocelot vs YARP 深度解析

在 .NET Core 微服务发展的早期,Ocelot 是事实上的标准。然而,随着微软官方推出 YARP (Yet Another Reverse Proxy) ,格局发生了变化。了解两者的核心差异对于架构设计至关重要。

5. Ocelot:老牌社区强者的前世今生

Ocelot 是一个纯社区驱动的开源项目,它的初衷非常简单:让微服务之间的 HTTP 请求转发变得容易,并且 主要通过 JSON 配置 来实现,尽量少写代码。

5.1 核心特性

  • 请求聚合 (Request Aggregation): 这是 Ocelot 最大的卖点之一。允许客户端发送一个请求到网关,网关并发请求后端的 Service A 和 Service B,将两者的 JSON 响应合并后返回。
  • 服务发现集成: 内置了对 Consul, Eureka 等服务注册中心的支持,可以自动根据服务名解析地址。
  • 内置功能全家桶: 自带限流、熔断(基于 Polly)、缓存、QoS 等功能,配置即可用。

5.2 典型配置 (ocelot.json)

Ocelot 的核心在于配置文件的编写,术语为 Upstream (客户端请求) 和 Downstream (后端服务)。

{
  "Routes": [
    {
      "DownstreamPathTemplate": "/api/posts/{id}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "localhost",
          "Port": 5001
        }
      ],
      "UpstreamPathTemplate": "/posts/{id}",
      "UpstreamHttpMethod": [ "Get" ],
      "QoSOptions": {
        "ExceptionsAllowedBeforeBreaking": 3, // 熔断配置
        "DurationOfBreak": 10000
      }
    }
  ],
  "GlobalConfiguration": {
    "BaseUrl": "https://api.mygateway.com"
  }
}

5.3 局限性与现状

  • 性能瓶颈: Ocelot 基于较早期的 .NET Core 管道设计,处理高并发和大量吞吐时的性能不如 YARP。
  • gRPC 支持有限: 虽然支持简单的 gRPC 转发,但对双向流(Bi-directional Streaming)和 HTTP/2 的细节处理不如原生支持完美。
  • 维护状态: 作为一个社区项目,更新速度相对较慢。

6. YARP:微软官方的高性能工具包

YARP 不是一个开箱即用的“产品”,而是一个 构建反向代理的库(Toolkit) 。它由 ASP-NET Core 核心团队开发,意在解决微软内部(如 Azure App Service, Bing 等)对高性能定制化代理的需求。

6.1 核心特性

  • 极致性能: 基于 .NET 最新的高性能网络库(System.Net.Http 和 Kestrel),针对内存分配和吞吐量进行了极致优化。
  • 原生协议支持: 完美支持 HTTP/1.1, HTTP/2, HTTP/3 以及 gRPC(包括流式处理)。
  • 配置灵活 (Code + Config): 既支持 appsettings.json 配置,也支持通过 C# 代码动态提供路由(IProxyConfigProvider)。这对于需要从数据库或 Kubernetes CRD 动态加载路由的场景非常有用。
  • 强大的转换 (Transforms): 这是 YARP 最强大的地方。你可以对请求头、路径、查询参数进行极其复杂的编程化修改。
  • 零侵入架构: YARP 只是一个中间件,你可以轻松地在 UseReverseProxy 之前或之后插入标准的 ASP-NET Core 中间件(如 Authorization, CORS)。

6.2 典型配置 (appsettings.json + Transforms)

YARP 的配置逻辑清晰地分为 Routes (路由规则)Clusters (后端集群)

"ReverseProxy": {
  "Routes": {
    "route1": {
      "ClusterId": "cluster1",
      "Match": {
        "Path": "/api/service1/{**remainder}"
      },
      "Transforms": [
        { "PathRemovePrefix": "/api/service1" }, // 去除前缀
        { "RequestHeader": "X-Gateway-Time", "Set": "TIME_NOW" } // 添加自定义头
      ]
    }
  },
  "Clusters": {
    "cluster1": {
      "LoadBalancingPolicy": "RoundRobin", // 负载均衡策略
      "Destinations": {
        "destination1": { "Address": "https://localhost:10001" },
        "destination2": { "Address": "https://localhost:10002" }
      }
    }
  }
}

6.3 为什么 YARP 没有“请求聚合”?

微软团队认为:网关不应该做复杂的业务逻辑聚合 。在 JSON 配置文件中定义复杂的聚合逻辑极其难以调试和测试。

  • YARP 的建议: 如果你需要聚合,请创建一个标准的 WebAPI Controller(即 BFF 层),在 C# 代码中调用多个后端服务并聚合数据。YARP 可以将请求路由到这个 Controller,而不是直接转发到后端。

7. Ocelot vs YARP 对比矩阵

特性 Ocelot YARP (推荐)
开发者 社区 (Tom Pallister 等) Microsoft ASP-NET Core 团队
定位 功能丰富的 API 网关 构建高性能代理的 基础库
性能 良好 (但在高吞吐下有开销) 极高 (企业级/云原生级)
gRPC 支持 有限 原生完美支持 (含流媒体)
配置方式 主要是 JSON (ocelot.json) JSON 或 C# 代码动态配置
请求聚合 支持 (内置功能) 不支持 (建议写代码实现 BFF)
扩展性 基于 DelegatingHandler 基于标准 Middleware 和 Transforms
维护活跃度 一般 非常活跃
适用场景 中小型项目,快速实现聚合 大型高并发项目,gRPC 项目,需深度定制

8. 实战:如何选择与迁移?

8.1 场景决策指南

  1. 如果你的项目重度依赖 gRPC:

    • 必须选 YARP 。它对 HTTP/2 的透传处理是目前 .NET 生态中最好的。
  2. 如果你需要极其灵活的路由策略(例如 A/B 测试,金丝雀发布):

    • 选 YARP 。你可以通过编写 C# 代码实现自定义的 LoadBalancingPolicy 或在 MapReverseProxy 时拦截请求,根据 Cookie 将 5% 的流量分发到新版本的 Cluster。
  3. 如果你有一个遗留系统,已经使用了 Ocelot:

    • 保持现状 ,除非遇到性能瓶颈。
    • 升级路径: Ocelot 的较新版本实际上引入了 YARP 作为其底层的转发器(Forwarder)。你可以配置 Ocelot 使用 YARP 核心来提升性能,同时保留 Ocelot 的配置方式。
  4. 如果你是从零开始的新项目:

    • 强烈建议直接使用 YARP 。它是未来的方向,且与 ASP-NET Core 生态(如 Authentication, RateLimiting)融合得更好。

8.2 YARP 的高级代码扩展示例

YARP 的强大在于你可以像写普通后端代码一样写网关逻辑。

需求: 根据用户 JWT 中的 TenantId(租户ID),将请求转发到不同的数据库实例或服务地址。

// 在 Program.cs 中自定义转换逻辑
builder.Services.AddReverseProxy()
    .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
    .AddTransforms(builderContext =>
    {
        // 为每个路由添加转换逻辑
        builderContext.AddRequestTransform(transformContext =>
        {
            // 从当前 HttpContext 中获取用户 Claims
            var tenantId = transformContext.HttpContext.User.FindFirst("TenantId")?.Value;

            if (!string.IsNullOrEmpty(tenantId))
            {
                // 动态修改请求头,传给下游服务
                transformContext.ProxyRequest.Headers.Add("X-Tenant-ID", tenantId);
            }
            
            // 甚至可以根据 TenantId 动态修改请求路径
            // transformContext.ProxyRequest.RequestUri = ...
            
            return ValueTask.CompletedTask;
        });
    });

9. 总结

  • Ocelot 像是“瑞士军刀”,功能多,拿来就用,适合快速搭建和中小规模应用,但刀刃(核心性能)可能不够锋利。
  • YARP 像是“工业级激光切割机”,专注于最核心的转发任务,性能强悍,稳定可靠,适合构建承载核心流量的大型网关。

对于大型 ASP-NET 项目,现在的行业共识是:拥抱 YARP。