目前我们项目是采用的 Ocelot 作为 API 网关,并且在其基础上结合 IdentityServer4 开发了一套 API 开放平台。由于部分项目是基于 ABP 框架进行开发的,接口的平均 QPS 基本是在 2K~3K /S 左右 (E3 1231 16G)。采用 Ocelot 进行请求转发之后,前端反馈接口调用速度变慢了,也没有太过在意,以为是项目接口的问题,一直在接口上面尝试进行优化。
极限优化接口后仍然没有显著改善,故针对 Ocelot 的性能进行压力测试,得到的结果也是让我比较惊讶。
首先新建了一个解决方案,其名字为 OcelotStudy
,其下面有三个项目,分别是两个 API 项目和一个网关项目。
网关项目编写:
Ϊ OcelotStudy
项目引入 Ocelot 的 NuGet 包。
在 OcelotStudy
项目的 Program.cs
文件当中显式指定我们网关的监听端口。
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace OcelotStudy { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { // 指定监听端口为 5000 webBuilder.UseStartup<Startup>() .UseKestrel(x=>x.ListenAnyIP(5000)); }); } }
在 Startup.cs
类当中注入 Ocelot 的服务,并应用 Ocelot 的中间件。
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Ocelot.DependencyInjection; using Ocelot.Middleware; namespace OcelotStudy { public class Startup { public void ConfigureServices(IServiceCollection services) { // 禁用日志的控制台输出,防止由于线程同步造成的性能损失 services.AddLogging(op => op.ClearProviders()); services.AddMvc(); services.AddOcelot(new ConfigurationBuilder().AddJsonFile("Ocelot.json").Build()); } public async void Configure(IApplicationBuilder app, IHostingEnvironment env) { await app.UseOcelot(); app.UseMvc(); } } }
在 OcelotStudy
项目下建立 Ocelot.json
文件,内容如下。
{ "ReRoutes": [ { "DownstreamPathTemplate": "/api/{everything}", "DownstreamScheme": "http", "DownstreamHostAndPorts": [ { "Host": "localhost", "Port": 6000 }, { "Host": "localhost", "Port": 7000 } ], "UpstreamPathTemplate": "/{everything}", "UpstreamHttpMethod": [ "Get", "Post" ], "LoadBalancerOptions": { "Type": "RoundRobin" } } ], "GlobalConfiguration": { // "BaseUrl": "https://api.yilezhu.cn" } }
测试项目的编写:
两个测试项目的监听端口分别为 6000
与 7000
,都建立一个 ValuesController
控制器,返回一个字符串用于输出当前请求的 API 服务器信息。
ApiService01
的文件信息:
using Microsoft.AspNetCore.Mvc; namespace ApiService01.Controllers { [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult<string> Get() { return "当前请求的 API 接口是 1 号服务器。"; } // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } }
ApiService02
的文件信息:
using Microsoft.AspNetCore.Mvc; namespace ApiService02.Controllers { [Route("api/[controller]")] [ApiController] public class ValuesController : ControllerBase { // GET api/values [HttpGet] public ActionResult<string> Get() { return "当前请求的 API 接口是 2 号服务器。"; } // GET api/values/5 [HttpGet("{id}")] public ActionResult<string> Get(int id) { return "value"; } // POST api/values [HttpPost] public void Post([FromBody] string value) { } // PUT api/values/5 [HttpPut("{id}")] public void Put(int id, [FromBody] string value) { } // DELETE api/values/5 [HttpDelete("{id}")] public void Delete(int id) { } } }
他们两个的 Startup.cs
与 Program.cs
文件内容基本一致,区别只是监听的端口分别是 6000
和 7000
而已。
using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; namespace ApiService02 { public class Program { public static void Main(string[] args) { CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); webBuilder.UseKestrel(x => x.ListenAnyIP(6000)); // 或者 7000 }); } }
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace ApiService02 { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // 禁用日志的控制台输出,防止由于线程同步造成的性能损失 services.AddLogging(op => op.ClearProviders()); services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseRouting(routes => { routes.MapApplication(); }); } } }
以上三个项目都采用 Release
版本进行发布。
dotnet publish -c Release
ApiService01 部署在单独的 E3 1231 v3 16G DDR3 服务器。
ApiService02 部署在单独的 i3-7100 16G DDR4 服务器。
OcelotStudy 部署在单独的 E3 1231 v3 16G DDR3 服务器。
这里我使用的是 WRK 来进行压力测试,OcelotStudy 网关项目的 IP 地址为 172.31.61.41:5000
,故使用以下命令进行测试。
./wrk -t 10 -c 10000 -d 20s --latency --timeout 3s "http://172.31.61.41:5000/values"
测试结果:
我将 ApiService01 项目放在网关的服务器,直接调用 ApiService01 的接口,其压力测试情况。
最后 Ocelot 的 QPS 结果为:3461.53
直接请求 API 接口的 QPS 结果为:38874.50
这样的结果让我感到很意外,不知道是由于 Ocelot 实现机制的原因,还是我的使用方法不对。这样的性能测试结果数据确实不太好看,但也希望今后 Ocelot 能够继续努力。
如果有任何疑问也请在评论区指出。