傳統的HTTP接口調用是一件比較繁瑣的事情,特別是在Post數據的時候;不僅要拼訪問的URL還是把數據序列化成流的方式給Request進行提交,獲取Respons后還要對流進行解碼。在實際應用雖然可以對HttpClient進行一個簡單的封裝,一旦到了上層大量的API調用還是不方便和不好維護。但如果在不改變HTTP接口服務的情況可以通過RPC的方式來調用HTTP服務那在使用和修改上都會變得更簡單和便於維護了; 接下來講解一下如何使用FastHttpApi通過接口描述的方式來訪問HTTP接口服務!
引用組件
在這里簡單地介紹一下FastHttpApi,它是一個輕量級高性的能的HTTP通訊組件,除了可以構建高性的HTTP服務外,還可以通過它來實現基於RPC的方式來訪問第三方HTTP服務。可以到GitHub了解。如果需要通過接口的方式訪問通第三方HTTP服務,首先要在項目用引用FastHttpApi,可以在Nuget上找到它,命令安裝如下 Install-Package BeetleX.FastHttpApi -Version 1.0.2.6也可以直接在VS中添加Nuget引用。
使用組件
在定義接口前了解第三方的HTTP服務結構是必須的(當然如果選擇FastHttpApi構建webapi會得到一下更高效的性能支持),下面主要講解通過組件定議接口來訪問asp.net mvc api的接口服務,先看一下服務的代碼
public class HomeController : Controller { public DateTime GetTime() { return DateTime.Now; } public IActionResult Hello(string name) { return new JsonResult($"hello {name}"); } public IEnumerable<Order> ListOrders(int employee, string customer) { Func<Order, bool> exp = o => (employee == 0 || o.EmployeeID == employee) && (string.IsNullOrEmpty(customer) || o.CustomerID == customer); var result = DataHelper.Orders.Where(exp); return result; } public Employee GetEmployee(int id) { Employee result = DataHelper.Employees.Find(e => e.EmployeeID == id); return result; } [HttpPost] public int AddEmployee([FromBody] List<Employee> items) { if (items == null) return 0; return items.Count; } [HttpPost] public Employee EditEmployee(int id, [FromBody]Employee employee) { employee.EmployeeID = id; return employee; } public bool Login(string name, string pwd) { if (name == "admin" && pwd == "123456") return true; return false; } }
以上是一個簡單的asp.net mvc api的代碼,接下來用接口來描述對應調用方法
[JsonFormater] [Controller(BaseUrl = "Home")] public interface IDataService { [Get] DateTime GetTime(); [Get] string Hello(string name); [Get] IList<Order> ListOrders(); [Get] IList<Order> ListOrders(int employee, string customer); [Get] Employee GetEmployee(int id); [Post] Employee EditEmployee([CQuery]int id, Employee employee); [Get] bool Login(string name, string pwd); [Post] int AddEmployee(params Employee[] items); }
是不是非常簡單,簡單地通過接口方法就可以描述對應HTTP請求,為了達到更好的應用性還可以重載不同版本來訪問同一服務接口,這樣在使用的時候就變得更方便靈活。再往下看代碼了解一下是如何使用這接口的。
HttpApiClient client = new HttpApiClient(Host); IDataService service = client.CreateWebapi<IDataService>(); DateTime dt = service.GetTime(); Console.WriteLine($"get time:{dt}"); string hello = service.Hello("henry"); Console.WriteLine($"hello :{hello}"); var orders = service.ListOrders(3, null); if (orders != null) Console.WriteLine($"list orders: {orders.Count}"); orders = service.ListOrders(); if (orders != null) Console.WriteLine($"list orders: {orders.Count}"); var emp = service.GetEmployee(7); Console.WriteLine($"get employee id 7:{emp?.FirstName} {emp?.LastName}"); emp = service.EditEmployee(5, new Employee { FirstName = "fan", LastName = "henry" }); Console.WriteLine($"edit employee :{emp.EmployeeID} {emp?.FirstName} {emp?.LastName}"); var count = service.AddEmployee(null); Console.WriteLine($"add employee :{count}"); count = service.AddEmployee(new Employee { EmployeeID = 3 }, new Employee { EmployeeID = 5 }); Console.WriteLine($"add employee :{count}"); var login = service.Login("admin", "123456"); Console.WriteLine($"login status:{login}");
首先是定義一個HttpApiClient對象指向一個服務地址,在這個代碼里的訪問地址是http://localhost:8080;接下來就可以通過HttpApiClient創建指定接口的操作對象,創建對象后就可以進行方法調用。那在多線程下是怎樣處理呢?其實HttpApiClient是線程安全的,所以不用擔心多線程下的操作,對於網絡連接處理則內部通過連接池實現。
組件的優勢和缺點
其實dotnet core已經存在這樣一個功能的組件Refit,它的功能比較完善支持的版本也比較多,而FastHttpApi則只支持.net core.其實FastHttpApi的定義是針對服務與服務之間的通訊,由於它是基於自有實現的一個輕量化'HttpClient'所以在性能上要出色於'Refit',還有內部集成了基於Host的連接池所以在處理性能和連接管理上相對有着自己的優勢;而這些的特點更適合內部服務之間的通訊需求。以下是組件和Refit的性能測試對比,由於網絡間的延時會抵銷具休處理的效率,所以測試是基於localhost進行,這樣BenchmarkDotNet的結果好反映實際代碼情況,測試結果如下:
測試代碼
[Benchmark] public void RefitAddEmployee() { var gitHubApi = Refit.RestService.For<IRefitEmployeeApi>(Host); for (int i = 0; i < Count; i++) { var octocat = gitHubApi.AddEmployee(Employee.GetEmployee()); octocat.Wait(); var id = octocat.Result.EmployeeID; } } [Benchmark] public void FastApiAddEmployee() { BeetleX.FastHttpApi.HttpApiClient client = new BeetleX.FastHttpApi.HttpApiClient(Host); var api = client.CreateWebapi<IFastHttpEmployeeApi>(); for (int i = 0; i < Count; i++) { var items = api.AddEmployee(Employee.GetEmployee()); var id = items.EmployeeID; } } [Benchmark] public void RefitGetEmployees() { var gitHubApi = Refit.RestService.For<IRefitEmployeeApi>(Host); for (int i = 0; i < Count; i++) { var octocat = gitHubApi.ListEmployees(5); octocat.Wait(); var count = octocat.Result.Count; } } [Benchmark] public void FastApiGetEmployees() { BeetleX.FastHttpApi.HttpApiClient client = new BeetleX.FastHttpApi.HttpApiClient(Host); var api = client.CreateWebapi<IFastHttpEmployeeApi>(); for (int i = 0; i < Count; i++) { var items = api.ListEmployees(5); var count = items.Count; } client.Dispose(); }
測試結果

雖然Refit采用靜態編譯的方式來處理請求,最終測試下來的結構還是FastHttpApi代理調用有着更出色的性能優勢。
實際並發測試
由於對Refit了解不深入所以並沒有把它引入進來做多線程並發測試,接下來進行一個多線程的並發測試,測試的硬件是一台4核發的開發機作為測試服務器。服務測試代碼如下:
[BeetleX.FastHttpApi.Controller(BaseUrl = "Employee")] class Program { static HttpApiServer mApiServer; static void Main(string[] args) { mApiServer = new HttpApiServer(); mApiServer.ServerConfig.WriteLog = true; mApiServer.ServerConfig.LogToConsole = true; mApiServer.ServerConfig.Port = 8007; mApiServer.ServerConfig.LogLevel = BeetleX.EventArgs.LogType.Warring; mApiServer.ServerConfig.UrlIgnoreCase = true; mApiServer.Register(typeof(Program).Assembly); mApiServer.Open(); Console.Write(mApiServer.BaseServer); Console.WriteLine(Environment.ProcessorCount); Console.Read(); } public object Get(int count) { return new JsonResult(Employee.GetEmployees(count)); } [Post] public object Add(Employee item) { return new JsonResult(item); } public object GetTime() { return new JsonResult(DateTime.Now); } }
測試的服務並沒有使用asp.net core作為服務,而是使用FastHttpApi作為測試服務,主要原因是有着更輕量級的性能優勢。接下是看一下測試結果:
*********************************************************************** * https://github.com/IKende/ConcurrentTest.git * Copyright ? ikende.com 2018 email:henryfan@msn.com * ServerGC:True *********************************************************************** * AddEmployee test prepping completed ----------------------------------------------------------------------- * [10000000/10000000]|threads:[20] * Success:[ 0/s]|total:[ 10000000][min:60087/s max:82394/s] * Error:[ 0/s]|total:[ 0][min:0/s max:0/s] ----------------------------------------------------------------------- * 0ms-0.1ms:[ 203] 0.1ms-0.5ms:[ 9,956,907] * 0.5ms-1ms:[ 15,796] 1ms-5ms:[ 26,184] * 5ms-10ms:[ 681] 10ms-50ms:[ 70] * 50ms-100ms:[ 1] 100ms-1000ms:[ 158] * 1000ms-5000ms:[ ] 5000ms-10000ms:[ ] *********************************************************************** *********************************************************************** * ListEmployees test prepping completed ----------------------------------------------------------------------- * [10000000/10000000]|threads:[20] * Success:[ 0/s]|total:[ 10000000][min:57709/s max:83524/s] * Error:[ 0/s]|total:[ 0][min:0/s max:0/s] ----------------------------------------------------------------------- * 0ms-0.1ms:[ 504] 0.1ms-0.5ms:[ 9,978,394] * 0.5ms-1ms:[ 4,114] 1ms-5ms:[ 16,732] * 5ms-10ms:[ 98] 10ms-50ms:[ 3] * 50ms-100ms:[ 20] 100ms-1000ms:[ 135] * 1000ms-5000ms:[ ] 5000ms-10000ms:[ ] *********************************************************************** *********************************************************************** * GetTime test prepping completed ----------------------------------------------------------------------- * [10000000/10000000]|threads:[20] * Success:[ 0/s]|total:[ 10000000][min:65740/s max:95669/s] * Error:[ 0/s]|total:[ 0][min:0/s max:0/s] ----------------------------------------------------------------------- * 0ms-0.1ms:[ 77,060] 0.1ms-0.5ms:[ 9,904,465] * 0.5ms-1ms:[ 4,612] 1ms-5ms:[ 13,510] * 5ms-10ms:[ 173] 10ms-50ms:[ 31] * 50ms-100ms:[ ] 100ms-1000ms:[ 149] * 1000ms-5000ms:[ ] 5000ms-10000ms:[ ] ***********************************************************************
客戶端開啟了20個線程同步調用服務,得到的結果峰值大概在8萬每秒的http請求響應,這樣的性能指標相信完全能滿足普通業務的需求,畢竟這台測試服務用的只是一台5-6年前的4核PC機。
