.net core使用RPC方式进行高效的HTTP服务访问
传统的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机。