一、背景 目前我們項目是採用的 Ocelot 作為 API 網關,並且在其基礎上結合 IdentityServer4 開發了一套 API 開放平臺。由於部分項目是基於 ABP 框架進行開發的,介面的平均 QPS 基本是在 2K~3K /S 左右 (E3 1231 16G)。採用 Ocelot 進行請 ...
一、背景
目前我們項目是採用的 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": "localhost",
"Port": 6000
},
{
"Host": "localhost",
"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: 我也不想換啊,多希望是我測錯了)