一、背景
目前我們項目是采用的 Ocelot 作為 API 網關,並且在其基礎上結合 IdentityServer4 開發了一套 API 開放平台。由於部分項目是基於 ABP 框架進行開發的,接口的平均 QPS 基本是在 2K~3K /S 左右 (E3 1231 16G)。采用 Ocelot 進行請求轉發之后,前端反饋接口調用速度變慢了,也沒有太過在意,以為是項目接口的問題,一直在接口上面嘗試進行優化。
極限優化接口后仍然沒有顯著改善,故針對 Ocelot 的性能進行壓力測試,得到的結果也是讓我比較驚訝。
二、准備工作
2.1 測試項目准備
首先新建了一個解決方案,其名字為 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": "API 1 服務器IP",
"Port": 6000
},
{
"Host": "API 2 服務器IP",
"Port": 7000
}
],
"UpstreamPathTemplate": "/{everything}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"LoadBalancerOptions": {
"Type": "RoundRobin"
}
}
],
"GlobalConfiguration": {
}
}
測試項目的編寫:
兩個測試項目的監聽端口分別為 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 實現機制的原因,還是我的使用方法不對。這樣的性能測試結果數據對於 API 網關來說確實不太好看,但也希望今后 Ocelot 能夠繼續努力。
如果大家對於我的測試方式有疑問的話,可以在評論區指出,我將按照你所提供的方法再次進行測試。(PS: 我也不想換啊,多希望是我測錯了)
五、原生 API、Ocelot、Kong API 性能比較
針對於評論區各位朋友所提出的建議,以及我最近針對 Ocelot 的再次測試,整理出來一份測試結果的表格。以下結果均是單節點進行部署,如果使用 LB + API 集群的話是可以有效提升吞吐量。
使用的網關 | 測試時間 | 並發數 | 總請求 | QPS | 超時數量 | 平均響應 |
---|---|---|---|---|---|---|
沒有使用網關 | 1 分鍾 | 15000 | 2495541 | 41518.07 | 1592 | 115.98 ms |
Kong API | 1 分鍾 | 15000 | 690141 | 11478.06 | 43997 | 860.31 ms |
Ocelot | 1 分鍾 | 15000 | 277627 | 4618.98 | 1795 | 1.7 s |
沒有使用網關 | 1 分鍾 | 5000 | 2530107 | 42111.44 | 0 | 115.48 ms |
Kong API | 1 分鍾 | 5000 | 866449 | 14418.25 | 3090 | 383.75 ms |
Ocelot | 1 分鍾 | 5000 | 307226 | 5113.09 | 78 | 932.19 ms |
沒有使用網關 | 10 分鍾 | 8000 | 13080964 | 21797.98 | 103 | 364.97 ms |
Kong API | 10 分鍾 | 8000 | 4809613 | 8014.7 | 305503 | 636.41 ms |
Ocelot | 10 分鍾 | 8000 | 2558431 | 4263.34 | 2137 | 1.84 s |
Ocelot 與 Kong 均部署在一台 32G 12C 3.0Ghz 的 CentOS 服務器上,API 1 與 API 2 部署在 8C 16G 3.0 Ghz 的服務器上,所有環境都是基於 Docker CE 進行部署。
另外針對於 Kong API 不知道是我使用方式不對還是什么情況,其網絡吞吐量經常波動,如下圖,還請不吝賜教。
網關服務器:
API 1 與 API 2 服務器: