1 前言
WebApiClient已成熟穩定,釋出了WebApiClient.JIT和WebApiClient.AOT兩個nuget包,累計近10w次下載。我對它的高可擴充套件性設計相當滿意和自豪,但WebApiClient並不因此而停下腳步,在一年前,我產生了編寫其core版本的想法,將asp.netcore服務端先進的思想融入到core版本,在效能與擴充套件性上得到進一步昇華。
對應的,給它叫了WebApiClientCore的名字,為了對得起名字裡面的Core字,我在框架設計、效能優化上佔用整體開發時間一半以上。
2 框架設計
IActionInvoker
WebApiClient時還沒有IActionInvoker概念,對應的執行邏輯直接在ApiActionContext上實現。現在我覺得,Context應該是一個狀態資料類,而不能也成為一個執行者,因為一個執行者的例項可以無限次地執行多個Context例項。
Refit則更簡單粗暴,將所有實現都在一個RequestBuilderImplementation的類上:你們只要也只能使用我內建的Attribute宣告,一切執行在我這個類裡面包辦,因為我是一個萬能類。
Core版本增加了IActionInvoker概念,從中Context分開,用於執行Context,職責分明。在實現上又分為多種Invoker:Task宣告返回執行者ActionInvoker、ITask宣告返回處理處理者ActionTask,以及聚合的MultiplexedActionInvoker。
Middleware思想
WebApiClient時在處理各個特性、引數驗證、返回值驗證時沒有使用Middleware思想,特別是在處理響應結果和異常短路邏輯難以維護。
Refit還是簡單粗暴,將所有特性的解釋實現都在這個RequestBuilderImplementation的類上,因為我還是一個萬能類。
Core版本增加中介軟體Builder,將請求前的相關Attribute的執行編排Build為一個請求處理委託,將請求後相關Attribute的執行編排Build為一個響應處理委託,然後把兩個委託與真實http請求串在一起,Build出一個完整的請求響應委託。
得益於Middleware,流程中的請求前引數值驗證、結果處理特性短路、異常短路、請求後結果值驗和無條件執行IApiFilterAtrribue等這些複雜的流程變成簡單的管道處理;另外介面也變成支援服務端響應多種格式內容,每種格式內容在一個IApiReturnAttribute上實現和處理,比如請求為Accept: application/json, application/xml
,不管伺服器返回xml或json都能處理。
/// <summary>
/// 建立執行委託
/// </summary>
/// <param name="apiAction">action描述器</param>
/// <returns></returns>
public static Func<ApiRequestContext, Task<ApiResponseContext>> Build(ApiActionDescriptor apiAction)
{
var requestHandler = BuildRequestHandler(apiAction);
var responseHandler = BuildResponseHandler(apiAction);
return async request =>
{
await requestHandler(request).ConfigureAwait(false);
var response = await HttpRequest.SendAsync(request).ConfigureAwait(false);
await responseHandler(response).ConfigureAwait(false);
return response;
};
}
Context思想
WebApiClient只有一個ApiActionContext,其Result和Exception屬性在請求前就可以訪問或設定,但實際上就算設定了值,流程也不會短路和中斷,屬於設計失誤。
Refit沒有相關Context概念,因為它不提供給使用者自定義擴充套件Attribute的能力,它內建的Attribute也沒有執行能力,一個RequestBuilderImplementation類夠了。
Core版本將設計了多個Context概念,不同階段有不同的Context,如同asp.netcore不同Filter的Context也不同一樣。對於一個Action,請求階段對應是ApiRequestContext,響應階段是ApiResponseContext;對於Action的引數,對應是ApiParameterContext。每種Context裡面都包含核心的HttpContext屬性,HttpContext包含請求訊息、響應訊息和介面配置選項等。
Interface思想
輸入WebApiClientCore名稱空間,會發現定義了很多Interface,這些Interface都是為了使用者實現自定義特性用的,當然內建的所有特性,都是實現了這些介面而已。如果一個特性實現了多個介面,它就有多種能力,比如內建的HeaderAttribute,它既可以修飾於Interface和Method,也可以修飾引數。
WebApiClientCore的Attribute描述的邏輯,是由Attribute自我實現,所以整個請求的資料裝配邏輯是分散為各個Attribute上,用什麼Attribute就有什麼邏輯,包含框架之外的非內建的自定義Attribute。
Refit的內建Attribute只有欲描述邏輯,沒有實現邏輯,實現邏輯由RequestBuilderImplementation包辦,所以它不需要介面也沒有介面。
3 效能優化
更快的字串替換
像[HttpGet("objects/{id}")]這樣的path引數,在restful中常常遇到,通過Span優化,Core版本在替換path引數值cpu佔用時間降低為原版本的十分之一。
更快的json序列化
得益於Sytem.Text.Json,json序列化和反序列化上效能顯明提升。
更少的緩衝區分配
WebApiClientCore使用了可回收複用的IBufferWriter,在json序列化得到json、json裝配為HttpContent只申請一次Buffer,而且HttpContent在傳送之後,這個Buffer被回收複用。IBufferWriter還於用表單的uri編碼,編碼產生的Buffer不用申請新的記憶體內容,直接寫入表單的HttpContent。
更少的編碼操作
WebApiClientCore的json不再使用utf16的string中間型別,直接將物件序列化為網路請求需要的utf8編碼二進位制json;表單的key和Value編碼時,也不產生string中間型別,而是編碼後的二進位制資料內容,然後寫入表單的IBufferWriter。
更快的快取查詢
WebApiClient建立代理類例項來執行一個請求時,要查詢兩次快取:通過介面型別查詢字典快取的介面代理類,然後例項化代理類;在ApiInterceptor裡面通過MethodInfo查詢字典快取的ApiActionDescriptor。
Refit執行同樣邏輯也使用了兩次字典快取,介面和介面代理類安全字典快取TypeMapping,介面和介面方法描述的字典快取interfaceHttpMethods。
WebApiClientCore取消了字典快取,使用靜態泛型類的欄位作快取,因為欄位訪問遠比字典查詢高效。同時通過巧妙的設計,在代理類攔截方法執行時,直接回傳IActionInvoker替換原來的MethodInfo,IActionInvoker包含了ApiActionDescriptor,而IActionInvoker與代理型別都一起快取在靜態泛型類的欄位,減少了一次必須的字典快取查詢過程。
效能對比
排除掉真實的網路請求IO等待時間,WebApiClientCore使用的cpu時間僅僅為WebApiClient.JIT和Refit的三分之一。
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.836 (1903/May2019Update/19H1)
Intel Core i3-4150 CPU 3.50GHz (Haswell), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.1.202
[Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT
Method | Mean | Error | StdDev |
---|---|---|---|
HttpClient_GetAsync | 3.146 μs | 0.0396 μs | 0.0370 μs |
WebApiClientCore_GetAsync | 12.421 μs | 0.2324 μs | 0.2174 μs |
Refit_GetAsync | 43.241 μs | 0.6713 μs | 0.6279 μs |
Method | Mean | Error | StdDev |
---|---|---|---|
HttpClient_PostJsonAsync | 5.263 μs | 0.0784 μs | 0.0733 μs |
WebApiClientCore_PostJsonAsync | 13.823 μs | 0.1874 μs | 0.1753 μs |
Refit_PostJsonAsync | 45.218 μs | 0.8166 μs | 0.7639 μs |
4 Nuget包與文件
Nuget包
<PackageReference Include="WebApiClientCore" Version="1.0.0" />
專案地址與文件
點選專案連結,帶你GET到N種使用技能,不求star,只求提供良好建議。