为.netcore助力--WebApiClient正式发布core版本
1、前言
NCC WebApiClient 已成熟穩(wěn)定,發(fā)布了WebApiClient.JIT 和 WebApiClient.AOT 兩個(gè) NuGet 包,累計(jì)近 10w 次下載。
我對(duì)它的高可擴(kuò)展性設(shè)計(jì)相當(dāng)滿意和自豪,但 WebApiClient 并不因此而停下腳步,在一年前,我產(chǎn)生了編寫其 Core 版本的想法,將 ASP.NET Core 服務(wù)端先進(jìn)的思想融入到 Core 版本,在性能與擴(kuò)展性上得到進(jìn)一步升華。
對(duì)應(yīng)的,給它叫了?WebApiClientCore?的名字,為了對(duì)得起名字里面的 Core 字,我在框架設(shè)計(jì)、性能優(yōu)化上占用整體開發(fā)時(shí)間一半以上。
2、框架設(shè)計(jì)
IActionInvoker
WebApiClient 時(shí)還沒有?IActionInvoker?概念,對(duì)應(yīng)的執(zhí)行邏輯直接在?ApiActionContext?上實(shí)現(xiàn)。現(xiàn)在我覺得,Context 應(yīng)該是一個(gè)狀態(tài)數(shù)據(jù)類,而不能也成為一個(gè)執(zhí)行者,因?yàn)橐粋€(gè)執(zhí)行者的實(shí)例可以無限次地執(zhí)行多個(gè) Context 實(shí)例。
Refit 則更簡(jiǎn)單粗暴,將所有實(shí)現(xiàn)都在一個(gè)?RequestBuilderImplementation?的類上:你們只要也只能使用我內(nèi)置的 Attribute 聲明,一切執(zhí)行在我這個(gè)類里面包辦,因?yàn)槲沂且粋€(gè)萬能類。
Core 版本增加了?IActionInvoker?概念,從中 Context 分開,用于執(zhí)行 Context,職責(zé)分明。在實(shí)現(xiàn)上又分為多種 Invoker:Task?聲明返回執(zhí)行者?ActionInvoker、ITask?聲明返回處理處理者?ActionTask,以及聚合的?MultiplexedActionInvoker。
Middleware思想
WebApiClient 時(shí)在處理各個(gè)特性、參數(shù)驗(yàn)證、返回值驗(yàn)證時(shí)沒有使用 Middleware 思想,特別是在處理響應(yīng)結(jié)果和異常短路邏輯難以維護(hù)。
Refit 還是簡(jiǎn)單粗暴,將所有特性的解釋實(shí)現(xiàn)都在這個(gè)?RequestBuilderImplementation?的類上,因?yàn)槲疫€是一個(gè)萬能類。
Core 版本增加中間件 Builder,將請(qǐng)求前的相關(guān) Attribute 的執(zhí)行編排 Build 為一個(gè)請(qǐng)求處理委托,將請(qǐng)求后相關(guān) Attribute 的執(zhí)行編排 Build 為一個(gè)響應(yīng)處理委托,然后把兩個(gè)委托與真實(shí) http 請(qǐng)求串在一起,Build 出一個(gè)完整的請(qǐng)求響應(yīng)委托。
得益于 Middleware,流程中的請(qǐng)求前參數(shù)值驗(yàn)證、結(jié)果處理特性短路、異常短路、請(qǐng)求后結(jié)果值驗(yàn)和無條件執(zhí)行?IApiFilterAtrribue?等這些復(fù)雜的流程變成簡(jiǎn)單的管道處理;另外接口也變成支持服務(wù)端響應(yīng)多種格式內(nèi)容,每種格式內(nèi)容在一個(gè)?IApiReturnAttribute?上實(shí)現(xiàn)和處理,比如請(qǐng)求為?Accept: application/json, application/xml,不管服務(wù)器返回xml或json都能處理。
/// <summary> /// 創(chuàng)建執(zhí)行委托 /// </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 只有一個(gè) ApiActionContext,其 Result 和 Exception 屬性在請(qǐng)求前就可以訪問或設(shè)置,但實(shí)際上就算設(shè)置了值,流程也不會(huì)短路和中斷,屬于設(shè)計(jì)失誤。
Refit 沒有相關(guān) Context 概念,因?yàn)樗惶峁┙o用戶自定義擴(kuò)展 Attribute 的能力,它內(nèi)置的 Attribute 也沒有執(zhí)行能力,一個(gè)?RequestBuilderImplementation?類夠了。
Core 版本將設(shè)計(jì)了多個(gè) Context 概念,不同階段有不同的 Context,如同 ASP.NET Core 不同 Filter 的 Context 也不同一樣。對(duì)于一個(gè) Action,請(qǐng)求階段對(duì)應(yīng)是?ApiRequestContext,響應(yīng)階段是?ApiResponseContext;對(duì)于 Action 的參數(shù),對(duì)應(yīng)是?ApiParameterContext。每種 Context 里面都包含核心的?HttpContext?屬性,HttpContext?包含請(qǐng)求消息、響應(yīng)消息和接口配置選項(xiàng)等。
Interface 思想
輸入 WebApiClientCore 命名空間,會(huì)發(fā)現(xiàn)定義了很多 Interface,這些 Interface 都是為了用戶實(shí)現(xiàn)自定義特性用的,當(dāng)然內(nèi)置的所有特性,都是實(shí)現(xiàn)了這些接口而已。如果一個(gè)特性實(shí)現(xiàn)了多個(gè)接口,它就有多種能力,比如內(nèi)置的?HeaderAttribute,它既可以修飾于 Interface 和 Method,也可以修飾參數(shù)。
WebApiClientCore 的 Attribute 描述的邏輯,是由 Attribute 自我實(shí)現(xiàn),所以整個(gè)請(qǐng)求的數(shù)據(jù)裝配邏輯是分散為各個(gè) Attribute 上,用什么 Attribute 就有什么邏輯,包含框架之外的非內(nèi)置的自定義 Attribute。
Refit 的內(nèi)置 Attribute 只有欲描述邏輯,沒有實(shí)現(xiàn)邏輯,實(shí)現(xiàn)邏輯由?RequestBuilderImplementation?包辦,所以它不需要接口也沒有接口。
3、性能優(yōu)化
更快的字符串替換
像[HttpGet("objects/{id}")]這樣的path參數(shù),在 RESTFul 中常常遇到,通過Span?優(yōu)化,Core 版本在替換?path?參數(shù)值 CPU 占用時(shí)間降低為原版本的十分之一。
更快的 JSON 序列化
得益于?Sytem.Text.Json,JSON 序列化和反序列化上性能顯明提升。
更少的緩沖區(qū)分配
WebApiClientCore 使用了可回收復(fù)用的?IBufferWriter,在 JSON 序列化得到 Json、Json 裝配為?HttpContent?只申請(qǐng)一次 Buffer,而且?HttpContent?在發(fā)送之后,這個(gè) Buffer 被回收復(fù)用。IBufferWriter?還于用表單的 URI 編碼,編碼產(chǎn)生的 Buffer 不用申請(qǐng)新的內(nèi)存內(nèi)容,直接寫入表單的?HttpContent。
更少的編碼操作
WebApiClientCore 的 JSON 不再使用 UTF16 的 string 中間類型,直接將對(duì)象序列化為網(wǎng)絡(luò)請(qǐng)求需要的 UTF8 編碼二進(jìn)制 JSON;表單的 Key 和 Value 編碼時(shí),也不產(chǎn)生 string 中間類型,而是編碼后的二進(jìn)制數(shù)據(jù)內(nèi)容,然后寫入表單的?IBufferWriter。
更快的緩存查找
WebApiClient 創(chuàng)建代理類實(shí)例來執(zhí)行一個(gè)請(qǐng)求時(shí),要查找兩次緩存:通過接口類型查找字典緩存的接口代理類,然后實(shí)例化代理類;在?ApiInterceptor?里面通過?MethodInfo?查找字典緩存的?ApiActionDescriptor。
Refit 執(zhí)行同樣邏輯也使用了兩次字典緩存,接口和接口代理類安全字典緩存?TypeMapping,接口和接口方法描述的字典緩存?InterfaceHttpMethods。
WebApiClientCore 取消了字典緩存,使用靜態(tài)泛型類的字段作緩存,因?yàn)樽侄卧L問遠(yuǎn)比字典查找高效。同時(shí)通過巧妙的設(shè)計(jì),在代理類攔截方法執(zhí)行時(shí),直接回傳?IActionInvoker?替換原來的?MethodInfo,IActionInvoker?包含了ApiActionDescriptor,而?IActionInvoker?與代理類型都一起緩存在靜態(tài)泛型類的字段,減少了一次必須的字典緩存查找過程。
性能對(duì)比
排除掉真實(shí)的網(wǎng)絡(luò)請(qǐng)求IO等待時(shí)間,WebApiClientCore 使用的 CPU 時(shí)間僅僅為 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 RyuJITDefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT| 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 |
| 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" />項(xiàng)目地址與文檔
點(diǎn)擊項(xiàng)目鏈接,帶你 GET 到 N 種使用技能,不求 star,只求提供良好建議。
https://github.com/dotnetcore/WebApiClient
總結(jié)
以上是生活随笔為你收集整理的为.netcore助力--WebApiClient正式发布core版本的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASP.NET Core使用Nacos
- 下一篇: 一个static和面试官扯了一个小时,舌