.net core使用RPC方式进行高效的HTTP服务访问

C#

浏览数:94

2019-5-12

传统的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机。

作者:smark