針對 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": "API 1 服務器IP",
          "Port": 6000
        },
        {
          "Host": "API 2 服務器IP",
          "Port": 7000
        }
      ],
      "UpstreamPathTemplate": "/{everything}",
      "UpstreamHttpMethod": [ "Get", "Post" ],
      "LoadBalancerOptions": {
        "Type": "RoundRobin"
      }
    }
  ],
  "GlobalConfiguration": {
    
  }
}

測試項目的編寫:

兩個測試項目的監聽端口分別為 60007000 ,都建立一個 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.csProgram.cs 文件內容基本一致,區別只是監聽的端口分別是 60007000 而已。

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 服務器:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM