.NET ReverseProxy反向代理无法同时命中双目标接口解决方案
问题描述
我在项目中使用.NET ReverseProxy搭建反向代理服务,希望单次请求可以同时命中TeaController、BiscuitController两个控制器的API,但当前配置仅会随机命中其中一个接口,请问该如何实现该需求?
现有实现代码
TeaController
[Route("api/[controller]")] [ApiController] public class TeaController : ControllerBase { [HttpGet("Test")] public IActionResult Test() { return Ok("Tea"); } }
BiscuitController
[Route("api/[controller]")] [ApiController] public class BiscuitController : ControllerBase { [HttpGet("Test")] public IActionResult Test() { return Ok("Biscuit"); } }
appsettings.json 配置
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "AllowedHosts": "*", "UseCodeBasedConfig": "true", "ReverseProxy": { "Routes": { "ReverseProxy-route": { "ClusterId": "ReverseProxy-cluster", "Match": { "Path": "ReverseProxy/{**catch-all}" }, "Transforms": [ { "PathPattern": "{**catch-all}" } ] } }, "Clusters": { "ReverseProxy-cluster": { "Destinations": { "LoadBalancingPolicy": "Random", "ReverseProxy-cluster/destination1": { "Address": "https://localhost:7044/api/biscuit" }, "ReverseProxy-cluster/destination2": { "Address": "https://localhost:7044/api/tea" } } } } } }
Program.cs 启动配置
var builder = WebApplication.CreateBuilder(args); var proxyBuilder = builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapReverseProxy(); }); app.Run();
解决方案
.NET ReverseProxy(YARP)默认负载均衡逻辑是单个入站请求只会转发到集群内的一个目标节点,你配置的Random策略就是每次随机选一个目标转发,自然不可能同时命中两个接口,这是组件原生设计逻辑,不是配置错误。
要实现单请求同时调用两个接口,有两种可落地的实现方式:
方案1:自定义中间件实现请求扇出(跨服务场景适用)
不依赖YARP内置的转发逻辑,在代理管道新增自定义中间件,收到匹配请求后手动并行发起两个对内接口请求,聚合结果后返回给客户端。
- 修改Program.cs注册相关服务,新增扇出中间件:
var builder = WebApplication.CreateBuilder(args); // 注册YARP和HttpClient用于内部转发 builder.Services.AddReverseProxy() .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); builder.Services.AddHttpClient(); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseRouting(); // 自定义扇出中间件,匹配/ReverseProxy开头的请求 app.Use(async (context, next) => { if (context.Request.Path.StartsWithSegments("/ReverseProxy", out var remainingPath)) { var httpClientFactory = context.RequestServices.GetRequiredService<IHttpClientFactory>(); var httpClient = httpClientFactory.CreateClient(); // 构造两个内部接口请求地址 const string baseAddr = "https://localhost:7044/api"; var teaReq = new HttpRequestMessage(HttpMethod.Get, $"{baseAddr}/tea{remainingPath}"); var biscuitReq = new HttpRequestMessage(HttpMethod.Get, $"{baseAddr}/biscuit{remainingPath}"); // 并行发送两个请求 var responseTasks = Task.WhenAll( httpClient.SendAsync(teaReq, context.RequestAborted), httpClient.SendAsync(biscuitReq, context.RequestAborted) ); try { var responses = await responseTasks; // 读取两个接口返回结果 var teaResult = await responses[0].Content.ReadAsStringAsync(context.RequestAborted); var biscuitResult = await responses[1].Content.ReadAsStringAsync(context.RequestAborted); // 按业务需求聚合结果返回,示例为组合JSON格式 context.Response.ContentType = "application/json"; await context.Response.WriteAsJsonAsync(new { TeaRes = teaResult, BiscuitRes = biscuitResult }, context.RequestAborted); return; } catch (Exception ex) { context.Response.StatusCode = 500; await context.Response.WriteAsJsonAsync(new { Error = ex.Message }, context.RequestAborted); return; } } // 不匹配的请求走原有YARP转发逻辑 await next(); }); app.UseEndpoints(endpoints => { endpoints.MapReverseProxy(); }); app.Run();
- 简化appsettings.json中的YARP配置,删除原有多目标集群配置即可,其他不需要扇出的路由可以正常保留YARP规则。
方案2:业务层直接做接口聚合(同服务场景推荐)
从你贴的代码看,两个Controller接口都属于https://localhost:7044这个同应用,完全没必要走反向代理转发,直接新增聚合接口,在内部调用两个业务逻辑合并返回即可,性能远高于代理转发:
// 建议把两个Test方法的业务逻辑抽到独立Service层注入调用,更符合分层规范,以下为简化示例 [Route("api/[controller]")] [ApiController] public class AggregateController : ControllerBase { private readonly TeaService _teaService; private readonly BiscuitService _biscuitService; public AggregateController(TeaService teaService, BiscuitService biscuitService) { _teaService = teaService; _biscuitService = biscuitService; } [HttpGet("TestAll")] public async Task<IActionResult> TestAll() { var teaRes = await _teaService.Test(); var biscuitRes = await _biscuitService.Test(); return Ok(new { Tea = teaRes, Biscuit = biscuitRes }); } }
内容的提问来源于stack exchange,提问作者Faizan Khan
相关产品推荐
相关产品推荐

