.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機。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM