YARP 反向代理网关
大约 13 分钟约 3758 字
YARP 反向代理网关
简介
YARP(Yet Another Reverse Proxy)是微软基于 ASP.NET Core 构建的开源反向代理库。它直接复用 ASP.NET Core 的中间件管道、认证授权、限流、健康检查等基础设施,让你可以用 C# 代码或配置文件灵活定义路由规则和代理策略。相比 Nginx、Envoy 等独立代理,YARP 的优势在于与 .NET 生态无缝集成,适合作为微服务 API 网关、K8s Ingress 替代方案或 BFF(Backend for Frontend)层。
YARP 从 2020 年开始由 Azure 团队开发,经历了多年的生产验证。它不是从零造轮子,而是在 ASP.NET Core 的 HTTP 管道之上做了一层代理转发抽象。这意味着你可以用熟悉的中间件模式来扩展代理行为,比如在转发前做限流、认证、请求改写,在转发后做响应缓存、日志记录等。
特点
基础配置
安装与启动
/// <summary>
/// YARP 最小配置 — Program.cs
/// </summary>
var builder = WebApplication.CreateBuilder(args);
// 添加 YARP 服务
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
var app = builder.Build();
// 映射 YARP 中间件
app.MapReverseProxy();
app.Run();appsettings.json 路由配置
{
"ReverseProxy": {
"Routes": {
"user-service-route": {
"ClusterId": "user-cluster",
"Match": {
"Path": "/api/users/{**catch-all}"
}
},
"order-service-route": {
"ClusterId": "order-cluster",
"Match": {
"Path": "/api/orders/{**catch-all}"
}
},
"product-service-route": {
"ClusterId": "product-cluster",
"Match": {
"Path": "/api/products/{**catch-all}",
"Methods": [ "GET", "POST" ]
}
}
},
"Clusters": {
"user-cluster": {
"Destinations": {
"user-api-1": {
"Address": "https://localhost:5001"
},
"user-api-2": {
"Address": "https://localhost:5002"
}
},
"LoadBalancingPolicy": "RoundRobin"
},
"order-cluster": {
"Destinations": {
"order-api": {
"Address": "https://localhost:6001"
}
}
},
"product-cluster": {
"Destinations": {
"product-api": {
"Address": "https://localhost:7001"
}
}
}
}
}
}负载均衡
负载均衡策略
{
"ReverseProxy": {
"Clusters": {
"round-robin-cluster": {
"LoadBalancingPolicy": "RoundRobin",
"Destinations": {
"dest1": { "Address": "https://localhost:5001" },
"dest2": { "Address": "https://localhost:5002" },
"dest3": { "Address": "https://localhost:5003" }
}
},
"least-connections-cluster": {
"LoadBalancingPolicy": "LeastConnections",
"Destinations": {
"dest1": { "Address": "https://localhost:6001" },
"dest2": { "Address": "https://localhost:6002" }
}
},
"random-cluster": {
"LoadBalancingPolicy": "Random",
"Destinations": {
"dest1": { "Address": "https://localhost:7001" },
"dest2": { "Address": "https://localhost:7002" }
}
},
"power-of-two-cluster": {
"LoadBalancingPolicy": "PowerOfTwoChoices",
"Destinations": {
"dest1": { "Address": "https://localhost:8001" },
"dest2": { "Address": "https://localhost:8002" },
"dest3": { "Address": "https://localhost:8003" }
}
}
}
}
}负载均衡策略说明
1. RoundRobin(轮询)
- 依次将请求分配到每个目标
- 适合后端实例性能一致的场景
- 最简单、最常用的策略
2. LeastConnections(最少连接)
- 将请求发送到当前连接数最少的目标
- 适合请求处理时间差异大的场景
- 需要代理跟踪每个目标的活跃连接数
3. Random(随机)
- 随机选择一个目标
- 在大量请求下近似均匀分布
- 适合无状态、简单的场景
4. PowerOfTwoChoices(二选一)
- 随机选两个目标,挑连接数少的那个
- 性能接近 LeastConnections,开销更低
- 推荐的大规模部署默认策略
5. ConsistentHash(一致性哈希)
- 基于 Header/Cookie/IP 做一致性哈希
- 配合会话亲和使用健康检查
主动健康检查
{
"ReverseProxy": {
"Clusters": {
"user-cluster": {
"HealthCheck": {
"Active": {
"Enabled": true,
"Interval": "00:00:30",
"Timeout": "00:00:05",
"Policy": "ConsecutiveFailures",
"Path": "/health"
},
"Passive": {
"Enabled": true,
"Policy": "TransportFailureRate",
"ReactivationPeriod": "00:01:00"
}
},
"Destinations": {
"user-api-1": {
"Address": "https://localhost:5001",
"Health": "https://localhost:5001/health"
},
"user-api-2": {
"Address": "https://localhost:5002",
"Health": "https://localhost:5002/health"
}
}
}
}
}
}健康检查代码配置
/// <summary>
/// YARP 健康检查 — 代码配置
/// </summary>
builder.Services.AddHealthChecks()
.AddUrlGroup(new Uri("https://localhost:5001/health"), "user-api-1")
.AddUrlGroup(new Uri("https://localhost:5002/health"), "user-api-2");
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddHealthChecks();
// 自定义健康检查策略
builder.Services.AddSingleton<IActiveHealthCheckPolicy, CustomHealthPolicy>();
public class CustomHealthPolicy : IActiveHealthCheckPolicy
{
private readonly ILogger<CustomHealthPolicy> _logger;
public string Name => "CustomHealth";
public CustomHealthPolicy(ILogger<CustomHealthPolicy> logger)
{
_logger = logger;
}
public void ProbingCompleted(
ClusterConfig cluster,
IReadOnlyList<DestinationProbingResult> probingResults)
{
foreach (var result in probingResults)
{
var response = result.Response;
if (response != null && response.IsSuccessStatusCode)
{
_logger.LogDebug("健康检查通过:{Destination}",
result.Destination.DestinationId);
}
else
{
_logger.LogWarning("健康检查失败:{Destination}",
result.Destination.DestinationId);
}
}
}
}会话亲和
Cookie/Header 粘性会话
{
"ReverseProxy": {
"Clusters": {
"sticky-cluster": {
"SessionAffinity": {
"Enabled": true,
"Policy": "Cookie",
"FailurePolicy": "Redistribute",
"Settings": {
"CustomCookieName": "YARP.Affinity",
"CustomCookieDomain": ".example.com",
"CustomCookiePath": "/",
"CustomCookieHttpOnly": true,
"CustomCookieSecure": "SameAsRequest",
"CustomCookieSameSite": "Lax",
"AffinityKeyName": "YARP.Affinity"
}
},
"Destinations": {
"server-1": { "Address": "https://localhost:5001" },
"server-2": { "Address": "https://localhost:5002" }
}
}
}
}
}会话亲和策略说明
/// <summary>
/// 会话亲和配置说明
/// </summary>
// Policy 类型:
// - Cookie:通过 Set-Cookie 头绑定用户到特定后端
// - Header:通过自定义 Header 绑定
// - Custom:自定义亲和策略(实现 ISessionAffinityPolicy)
// FailurePolicy 类型:
// - Redistribute:亲和失败时重新分配(推荐)
// - Return503Error:直接返回 503
// 代码配置方式
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddSessionAffinityConfigurer();请求变换
Header、Path、Query 变换
{
"ReverseProxy": {
"Routes": {
"transform-route": {
"ClusterId": "target-cluster",
"Match": {
"Path": "/api/v1/{**catch-all}"
},
"Transforms": [
{
"PathPattern": "/api/v2/{**catch-all}"
},
{
"RequestHeader": "X-Forwarded-For",
"Set": "{RemoteIpAddress}"
},
{
"RequestHeader": "X-Request-Id",
"Set": "{Guid}"
},
{
"RequestHeader": "X-Source",
"Set": "yarp-gateway"
},
{
"ResponseHeader": "X-Response-Time",
"Set": "{DateTime}"
},
{
"QueryValueParameter": "api_key",
"Set": "secret-key-123"
},
{
"RequestHeadersCopy": "true"
},
{
"ResponseTrailers": "X-Trailer",
"Append": "processed"
}
]
}
}
}
}代码配置变换
/// <summary>
/// 代码方式配置请求变换
/// </summary>
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.AddTransforms(transformBuilderContext =>
{
// 添加自定义请求头
transformBuilderContext.AddRequestHeader("X-Gateway", "YARP");
transformBuilderContext.AddRequestHeader("X-Request-Id", Guid.NewGuid().ToString());
// 路径前缀重写
transformBuilderContext.AddPathPrefix("/internal");
// 响应头变换
transformBuilderContext.AddResponseHeader("X-Served-By", "yarp-gateway");
// 自定义变换逻辑
transformBuilderContext.AddRequestTransform(async transformContext =>
{
// 添加认证信息
var token = transformContext.HttpContext.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(token))
{
transformContext.ProxyRequest.Headers.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
}
// 记录请求开始时间
transformContext.HttpContext.Items["RequestStartTime"] = DateTime.UtcNow;
});
transformBuilderContext.AddResponseTransform(async transformContext =>
{
// 计算响应时间
if (transformContext.HttpContext.Items["RequestStartTime"] is DateTime startTime)
{
var elapsed = DateTime.UtcNow - startTime;
transformContext.HttpContext.Response.Headers.Append(
"X-Response-Time-Ms", elapsed.TotalMilliseconds.ToString("F2"));
}
});
});认证与授权
网关层认证
/// <summary>
/// YARP 网关层认证授权
/// </summary>
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-server.com";
options.Audience = "api-gateway";
options.RequireHttpsMetadata = true;
});
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AuthenticatedOnly", policy =>
policy.RequireAuthenticatedUser())
.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// 路由级授权
app.MapReverseProxy(proxyApp =>
{
// 全局认证中间件
proxyApp.Use(async (context, next) =>
{
if (!context.User.Identity?.IsAuthenticated ?? true)
{
// 公开接口白名单
var publicPaths = new[] { "/api/public/", "/health" };
var isPublic = publicPaths.Any(p => context.Request.Path.StartsWithSegments(p));
if (!isPublic)
{
context.Response.StatusCode = 401;
return;
}
}
await next();
});
});路由级授权配置
{
"ReverseProxy": {
"Routes": {
"admin-route": {
"ClusterId": "admin-cluster",
"AuthorizationPolicy": "AdminOnly",
"Match": {
"Path": "/api/admin/{**catch-all}"
}
},
"user-route": {
"ClusterId": "user-cluster",
"AuthorizationPolicy": "AuthenticatedOnly",
"Match": {
"Path": "/api/users/{**catch-all}"
}
},
"public-route": {
"ClusterId": "public-cluster",
"AuthorizationPolicy": "Default",
"Match": {
"Path": "/api/public/{**catch-all}"
}
}
}
}
}限流
YARP 集成限流
/// <summary>
/// YARP 网关限流配置
/// </summary>
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("gateway-fixed", opt =>
{
opt.PermitLimit = 100;
opt.Window = TimeSpan.FromMinutes(1);
opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
opt.QueueLimit = 50;
});
options.AddSlidingWindowLimiter("gateway-sliding", opt =>
{
opt.PermitLimit = 60;
opt.Window = TimeSpan.FromMinutes(1);
opt.SegmentsPerWindow = 6;
});
options.AddTokenBucketLimiter("gateway-token", opt =>
{
opt.TokenLimit = 200;
opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
opt.TokensPerPeriod = 40;
});
options.AddConcurrencyLimiter("gateway-concurrency", opt =>
{
opt.PermitLimit = 50;
opt.QueueLimit = 20;
});
options.OnRejected = async (context, ct) =>
{
context.HttpContext.Response.StatusCode = 429;
await context.HttpContext.Response.WriteAsJsonAsync(new
{
Error = "请求过于频繁",
RetryAfter = context.HttpContext.Response.Headers.RetryAfter
}, ct);
};
});
var app = builder.Build();
app.UseRateLimiter();
app.MapReverseProxy();路由级限流配置
{
"ReverseProxy": {
"Routes": {
"rate-limited-route": {
"ClusterId": "target-cluster",
"Match": {
"Path": "/api/limited/{**catch-all}"
},
"RateLimiterPolicy": "gateway-token",
"Timeout": "00:00:30",
"TimeoutPolicy": "RequestCanceled"
}
}
}
}熔断器
集成 Polly 熔断
/// <summary>
/// YARP + Polly 熔断器
/// </summary>
builder.Services.AddReverseProxy()
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
.Add Polly(resilienceBuilder =>
{
resilienceBuilder.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage>
{
FailureRatio = 0.5,
MinimumThroughput = 10,
SamplingDuration = TimeSpan.FromSeconds(30),
BreakDuration = TimeSpan.FromSeconds(30),
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(r => (int)r.StatusCode >= 500),
OnOpened = args =>
{
Console.WriteLine($"熔断器打开:{args.BreakDuration}");
return default;
},
OnClosed = args =>
{
Console.WriteLine("熔断器关闭,恢复正常");
return default;
},
OnHalfOpened = args =>
{
Console.WriteLine("熔断器半开,尝试恢复");
return default;
}
});
resilienceBuilder.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromMilliseconds(500),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder<HttpResponseMessage>()
.Handle<HttpRequestException>()
.HandleResult(r => (int)r.StatusCode >= 500),
OnRetry = args =>
{
Console.WriteLine($"重试 {args.AttemptNumber}:{args.Outcome.Result?.StatusCode}");
return default;
}
});
resilienceBuilder.AddTimeout(TimeSpan.FromSeconds(10));
});自定义熔断中间件
/// <summary>
/// 自定义代理中间件 — 实现简单熔断逻辑
/// </summary>
public class CircuitBreakerMiddleware
{
private readonly RequestDelegate _next;
private readonly CircuitBreakerState _state;
private readonly ILogger<CircuitBreakerMiddleware> _logger;
public CircuitBreakerMiddleware(RequestDelegate next,
CircuitBreakerState state, ILogger<CircuitBreakerMiddleware> logger)
{
_next = next;
_state = state;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
var clusterId = context.GetRouteValue("clusterId")?.ToString();
if (_state.IsOpen(clusterId))
{
_logger.LogWarning("熔断器打开,拒绝请求:{Cluster}", clusterId);
context.Response.StatusCode = 503;
await context.Response.WriteAsJsonAsync(new
{
Error = "服务暂时不可用",
RetryAfter = 30
});
return;
}
try
{
await _next(context);
// 请求成功,重置计数
if (context.Response.StatusCode < 500)
{
_state.RecordSuccess(clusterId);
}
else
{
_state.RecordFailure(clusterId);
}
}
catch (Exception ex)
{
_state.RecordFailure(clusterId);
_logger.LogError(ex, "代理请求失败");
throw;
}
}
}
public class CircuitBreakerState
{
private readonly ConcurrentDictionary<string, CircuitState> _circuits = new();
public bool IsOpen(string clusterId)
{
var state = _circuits.GetOrAdd(clusterId, _ => new CircuitState());
if (state.State == CircuitStateEnum.Open)
{
if (DateTime.UtcNow - state.OpenedAt > TimeSpan.FromSeconds(30))
{
state.State = CircuitStateEnum.HalfOpen;
return false;
}
return true;
}
return false;
}
public void RecordFailure(string clusterId)
{
var state = _circuits.GetOrAdd(clusterId, _ => new CircuitState());
if (Interlocked.Increment(ref state.FailureCount) >= 5)
{
state.State = CircuitStateEnum.Open;
state.OpenedAt = DateTime.UtcNow;
}
}
public void RecordSuccess(string clusterId)
{
var state = _circuits.GetOrAdd(clusterId, _ => new CircuitState());
Interlocked.Exchange(ref state.FailureCount, 0);
state.State = CircuitStateEnum.Closed;
}
}
public class CircuitState
{
public CircuitStateEnum State = CircuitStateEnum.Closed;
public int FailureCount;
public DateTime OpenedAt;
}
public enum CircuitStateEnum { Closed, Open, HalfOpen }缓存
响应缓存
/// <summary>
/// YARP 响应缓存中间件
/// </summary>
public class ProxyCacheMiddleware
{
private readonly RequestDelegate _next;
private readonly IMemoryCache _cache;
private readonly ILogger<ProxyCacheMiddleware> _logger;
public ProxyCacheMiddleware(RequestDelegate next,
IMemoryCache cache, ILogger<ProxyCacheMiddleware> logger)
{
_next = next;
_cache = cache;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// 只缓存 GET 请求
if (context.Request.Method != "GET")
{
await _next(context);
return;
}
var cacheKey = GenerateCacheKey(context.Request);
if (_cache.TryGetValue(cacheKey, out CachedResponse cached))
{
_logger.LogDebug("缓存命中:{Key}", cacheKey);
context.Response.StatusCode = cached.StatusCode;
foreach (var header in cached.Headers)
{
context.Response.Headers[header.Key] = header.Value;
}
context.Response.Headers["X-Cache"] = "HIT";
await context.Response.Body.WriteAsync(cached.Body);
return;
}
// 捕获原始响应
var originalBody = context.Response.Body;
using var memoryStream = new MemoryStream();
context.Response.Body = memoryStream;
await _next(context);
// 缓存成功的响应
if (context.Response.StatusCode == 200)
{
var body = memoryStream.ToArray();
var headers = new Dictionary<string, string>();
foreach (var header in context.Response.Headers)
{
headers[header.Key] = header.Value;
}
_cache.Set(cacheKey, new CachedResponse
{
StatusCode = context.Response.StatusCode,
Headers = headers,
Body = body
}, TimeSpan.FromMinutes(5));
context.Response.Headers["X-Cache"] = "MISS";
}
memoryStream.Position = 0;
await memoryStream.CopyToAsync(originalBody);
context.Response.Body = originalBody;
}
private static string GenerateCacheKey(HttpRequest request)
{
return $"proxy:{request.Path}:{request.QueryString}";
}
}
public class CachedResponse
{
public int StatusCode { get; set; }
public Dictionary<string, string> Headers { get; set; }
public byte[] Body { get; set; }
}HTTPS 终止
HTTPS 与 TLS 配置
/// <summary>
/// YARP HTTPS 终止配置
/// </summary>
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(443, listenOptions =>
{
listenOptions.UseHttps(options =>
{
options.ServerCertificate = LoadCertificate();
// 或者使用 Let's Encrypt 自动证书
});
});
options.ListenAnyIP(80); // HTTP 重定向
});
var app = builder.Build();
// HTTP -> HTTPS 重定向
app.UseHttpsRedirection();
// HSTS
app.UseHsts();
app.MapReverseProxy();
// 客户端证书转发
app.MapReverseProxy(proxyApp =>
{
proxyApp.Use(async (context, next) =>
{
// 转发客户端证书信息到后端
var clientCert = context.Connection.ClientCertificate;
if (clientCert != null)
{
context.Request.Headers["X-Client-Cert-Thumbprint"] =
clientCert.Thumbprint;
context.Request.Headers["X-Client-Cert-Subject"] =
clientCert.Subject;
}
await next();
});
});动态配置
运行时动态更新路由
/// <summary>
/// 动态路由配置 — 运行时增删改路由
/// </summary>
public class DynamicConfigService
{
private readonly IProxyConfigProvider _configProvider;
private readonly ILogger<DynamicConfigService> _logger;
public DynamicConfigService(IProxyConfigProvider configProvider,
ILogger<DynamicConfigService> logger)
{
_configProvider = configProvider;
_logger = logger;
}
/// <summary>
/// 添加新的后端目标
/// </summary>
public void AddDestination(string clusterId, string destinationId, string address)
{
// YARP 支持通过 IProxyConfigProvider 实现动态配置
// 自定义实现 InMemoryConfigProvider
_logger.LogInformation("添加后端目标:{Cluster}/{Dest} -> {Address}",
clusterId, destinationId, address);
}
}
/// <summary>
/// 内存中的动态配置提供器
/// </summary>
public class InMemoryConfigProvider : IProxyConfigProvider
{
private volatile ProxyConfig _config;
private readonly CancellationTokenSource _cts = new();
public InMemoryConfigProvider()
{
_config = new ProxyConfig(
new List<RouteConfig>(),
new List<ClusterConfig>());
}
public IProxyConfig GetConfig() => _config;
public void Update(List<RouteConfig> routes, List<ClusterConfig> clusters)
{
_config = new ProxyConfig(routes, clusters);
// 触发配置变更通知
}
}
// 在 Controller 中动态管理路由
[ApiController]
[Route("api/gateway/[controller]")]
public class RoutesController : ControllerBase
{
private readonly InMemoryConfigProvider _configProvider;
[HttpPost("routes")]
public IActionResult AddRoute([FromBody] RouteConfigDto routeConfig)
{
// 动态添加路由
_configProvider.Update(
_configProvider.GetConfig().Routes.Append(routeConfig.ToRouteConfig()).ToList(),
_configProvider.GetConfig().Clusters.ToList());
return Ok();
}
[HttpDelete("routes/{routeId}")]
public IActionResult RemoveRoute(string routeId)
{
var routes = _configProvider.GetConfig().Routes
.Where(r => r.RouteId != routeId).ToList();
_configProvider.Update(routes, _configProvider.GetConfig().Clusters.ToList());
return Ok();
}
}K8s Ingress 替代
YARP 作为 K8s 入口
/// <summary>
/// YARP 作为 Kubernetes Ingress Controller 的思路
/// </summary>
public class K8sIngressController
{
private readonly IKubernetes _k8sClient;
private readonly InMemoryConfigProvider _configProvider;
private readonly ILogger<K8sIngressController> _logger;
/// <summary>
/// Watch Kubernetes Endpoints 变化,自动更新 YARP 配置
/// </summary>
public async Task WatchEndpointsAsync()
{
var watcher = await _k8sClient.CoreV1
.ListEndpointForAllNamespacesWithHttpMessagesAsync(
watch: true);
watcher.Watch<Corev1Endpoint, Corev1EndpointList>(
onEvent: (type, endpoint) =>
{
_logger.LogInformation("Endpoint 变化:{Type} - {Name}", type, endpoint.Metadata.Name);
RefreshProxyConfig();
},
onError: ex =>
{
_logger.LogError(ex, "K8s Endpoint watch 异常");
});
}
private void RefreshProxyConfig()
{
// 根据 K8s Services/Endpoints 重建 YARP 路由和集群配置
var services = _k8sClient.CoreV1.ListServiceForAllNamespaces();
var routes = new List<RouteConfig>();
var clusters = new List<ClusterConfig>();
foreach (var svc in services.Items)
{
// 根据注解生成路由规则
var annotations = svc.Metadata.Annotations;
if (annotations != null && annotations.TryGetValue("yarp/route", out var path))
{
var clusterId = $"k8s-{svc.Metadata.Name}";
routes.Add(new RouteConfig
{
RouteId = clusterId,
ClusterId = clusterId,
Match = new RouteMatch { Path = path }
});
}
}
_configProvider.Update(routes, clusters);
}
}优点
缺点
性能注意事项
- YARP 基于 Kestrel,单实例可处理数万 RPS
- 启用 HTTP/2 连接池减少后端连接开销
- 使用 PowerShell 或 BenchmarkDotNet 进行压测
- 限流和缓存中间件的位置影响性能
- 健康检查间隔不要设置太短,避免给后端造成额外压力
- 连接复用:HttpClientLifetime 设置合理的过期时间
总结
YARP 是 .NET 微服务架构中 API 网关的最佳选择之一。它复用了 ASP.NET Core 的全部基础设施,让团队可以用熟悉的 C# 代码扩展代理行为。对于纯 .NET 技术栈的团队,YARP 比 Nginx + Lua 或 Kong 更容易上手和维护。对于混合技术栈,YARP 也可以作为 BFF 层与 Nginx 配合使用。
关键知识点
- YARP 的核心概念是 Route(路由匹配)+ Cluster(后端集群)+ Transform(请求变换)
- 负载均衡支持 RoundRobin、LeastConnections、Random、PowerOfTwoChoices
- 健康检查分为 Active(主动探测)和 Passive(被动熔断)两种
- 会话亲和通过 Cookie 或 Header 实现粘性会话
- 动态配置通过 IProxyConfigProvider 接口实现运行时更新
项目落地视角
- 先确定网关的职责边界:只做路由转发还是承担认证、限流、缓存
- 路由规则用配置文件管理,配合配置中心实现动态更新
- 健康检查和熔断是生产环境的必需配置
- 监控每个路由的延迟、错误率和吞吐量
- 考虑网关的单点故障风险,至少部署两个实例
常见误区
- 把所有业务逻辑放在网关,导致网关变得臃肿
- 不配置健康检查,后端挂掉后网关还在继续转发
- 忘记配置请求变换,导致后端拿不到真实客户端 IP
- 限流策略过于激进,导致正常请求被拒绝
- 动态配置没有持久化,重启后路由丢失
进阶路线
- 研究基于 YARP 的灰度发布和金丝雀部署方案
- 结合 Consul/Eureka 实现服务发现驱动的动态路由
- 探索基于 WebSocket/gRPC 的代理转发
- 学习分布式追踪(OpenTelemetry)与 YARP 集成
- 研究 K8s 中 YARP Ingress Controller 的完整方案
适用场景
- 微服务 API 网关,统一入口认证和路由
- BFF(Backend for Frontend)层,为不同前端聚合后端 API
- K8s 集群入口,替代 Nginx Ingress
- 遗留系统的 API 代理和逐步迁移
- 多租户 SaaS 的请求路由和隔离
落地建议
- 从配置文件方式开始,稳定后再考虑动态配置
- 网关层只做横切关注点(认证、限流、日志),不做业务逻辑
- 生产环境至少部署两个网关实例,前面加 LB
- 为每个路由配置超时和重试策略
- 配合 OpenTelemetry 建立全链路追踪
排错清单
- 502 错误:检查后端地址是否正确,后端是否启动
- 503 错误:检查健康检查配置,可能所有目标都被标记不健康
- 路由不匹配:检查 Path 模式、Methods 限制和优先级
- Header 丢失:检查 Transforms 配置是否正确转发
- 性能差:检查连接池配置、DNS 解析和后端响应时间
- 配置不生效:检查 JSON 配置的层级结构是否正确
复盘问题
- 网关的请求延迟 P99 是多少?是否满足 SLA?
- 单个网关实例能支撑多少 RPS?是否需要水平扩展?
- 后端故障时,熔断和降级策略是否按预期工作?
- 路由规则的变更频率如何?是否需要自动化管理?
- 网关的监控和告警覆盖了哪些场景?
