WebApiClient效能參考

jiulang發表於2020-05-21

1 文章目的

昨天寫了走進WebApiClientCore的設計,介紹了WebApiClient的變化與設計之後,收到大家支援的、讚許的,還有好的建議和顧慮,比如WebApiClient效能怎麼樣,有沒有一些對比參考值?我一直有個不好毛病,憑直接感觀程式碼的執行效率,直覺裡WebApiClient的程式碼都是優化的,就算效能不好,也沒有更好的效能彌補方案!但真要這樣和使用這個庫的開發者說,相信要一秒內被打屎的,所以今天使用了BenchmarkDotNet對WebApiClient.JIT最新穩定版、WebApiClientCore預覽版和原生的HttpClient做了請求和響應處理耗時對比。

2 排除網路IO等待干擾

我們必須要想辦法去掉真實網路請求的時間干擾,因為一個請求回合中,網路等待時間可能會佔用到整個流程的99.99%以上的時間。最好的辦法就是沒有請求,而是模擬一個響應內容。

System.Net.Http支援管道式包裝請求處理物件,叫DelegatingHandler,我們可以自定義一個DelegatingHandler,重寫其SendAsync()方法,然後直接返回模擬的響應訊息HttpResponseMessage,使用這個DelegatingHandler的HttpClient,在請求時不會真實將資料傳送到目標伺服器去。

/// <summary>
/// 無真實http請求的Handler
/// </summary>
class NoneHttpDelegatingHandler : DelegatingHandler
{
    private readonly HttpResponseMessage benchmarkModelResponseMessage;

    public NoneHttpDelegatingHandler()
    {
        var model = new Model { A = "A", B = 2, C = 3d };
        var json = JsonSerializer.SerializeToUtf8Bytes(model);
        this.benchmarkModelResponseMessage = new HttpResponseMessage(HttpStatusCode.OK) { Content = new JsonContent(json) };
    }

    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(this.benchmarkModelResponseMessage);
    }
}

3 Benchmark測試

3.1 註冊到DI

為了公平,三種對比物件都註冊到到DI裡,使用時從DI獲取例項

var services = new ServiceCollection();

// 原生HttpClient
services
    .AddHttpClient(typeof(HttpClient).FullName)
    .AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());

// WebApiClientCore
services
    .AddHttpApi<IWebApiClientCoreApi>(o => o.HttpHost = new Uri("http://webapiclient.com/"))
    .AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());

// WebApiClient
WebApiClient.Extension
    .AddHttpApi<IWebApiClientApi>(services, o => o.HttpHost = new Uri("http://webapiclient.com/"))
    .AddHttpMessageHandler(() => new NoneHttpDelegatingHandler());

3.2 Benchmark方法

我們為三種客戶端每個比較專案各寫一個Benchmark方法:從DI獲取例項,然後發起Get請求,最後將響應的內容反序列化為強模型。

/// <summary>
/// 使用WebApiClient.JIT請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> WebApiClient_GetAsync()
{
    using var scope = this.serviceProvider.CreateScope();
    var banchmarkApi = scope.ServiceProvider.GetRequiredService<IWebApiClientApi>();
    return await banchmarkApi.GetAsyc(id: "id");
}

/// <summary>
/// 使用WebApiClientCore請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> WebApiClientCore_GetAsync()
{
    using var scope = this.serviceProvider.CreateScope();
    var banchmarkApi = scope.ServiceProvider.GetRequiredService<IWebApiClientCoreApi>();
    return await banchmarkApi.GetAsyc(id: "id");
}

/// <summary>
/// 使用原生HttpClient請求
/// </summary>
/// <returns></returns>
[Benchmark]
public async Task<Model> HttpClient_GetAsync()
{
    using var scope = this.serviceProvider.CreateScope();
    var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient(typeof(HttpClient).FullName);

    var id = "id";
    var request = new HttpRequestMessage(HttpMethod.Get, $"http://webapiclient.com/{id}");
    var response = await httpClient.SendAsync(request);
    var json = await response.Content.ReadAsByteArrayAsync();
    return JsonSerializer.Deserialize<Model>(json);
}

3.3 Benchmark執行與結果

原於程式碼篇幅,實際中我們還加入了Post請求測試,所以最後的結果如下:

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.836 (1909/November2018Update/19H2)
Intel Core i7-8565U CPU 1.80GHz (Whiskey Lake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.201
  [Host]     : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT
  DefaultJob : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT


|                     Method |      Mean |     Error |    StdDev |
|--------------------------- |----------:|----------:|----------:|
|      WebApiClient_GetAsync | 25.716 us | 0.5106 us | 0.9838 us |
|  WebApiClientCore_GetAsync | 16.047 us | 0.1231 us | 0.0961 us |
|        HttpClient_GetAsync |  2.028 us | 0.0240 us | 0.0224 us |
|     WebApiClient_PostAsync | 18.712 us | 0.3707 us | 0.3641 us |
| WebApiClientCore_PostAsync |  8.799 us | 0.1696 us | 0.1666 us |
|       HttpClient_PostAsync |  3.673 us | 0.0710 us | 0.0972 us |

總結

不管是成熟穩定如狗的WebApiClient,還是新秀WebApiClientCore,其效能和原生的HttpClient在一個數量級,大家放心地使用就是了。

相關文章