在dotnet core下去中心化訪問HTTP服務集群


一般應用服務都會部署到多台服務器之上,一、可以通過硬件得到更多的並發處理能力;二、可以避免單點太故障的出現,從而確保服務7X24有效運作。當訪問這些HTTP服務的情況一般都是經過反向代理服務進行統一處理,這樣的好處就訪問透明化,統一管理和控制。但存在的問題就是服務處理延時加大,還有就是對小團或公司來說可能沒有專門的技術人來規划和管理這些代理服務。接下一來講一下在.net core下更輕更的一種處理方案,這種方案通過Client自身的功能實現集群化的HTTP服務訪問,通過故障遷移和權重分配達到一個無中心化靈活的HTTP集群服務訪問(通過它還能訪問所有非.net core的HTTP服務)

場景定義

首先有以下一個asp.net core mvc服務

    public class HomeController : Controller
    {
        [HttpPost]
        public int EmployeeAdd([FromBody]List<Employee> items)
        {
            return items == null ? 0 : items.Count;
        }

        [HttpPost]
        public Employee EmployeeEdit(int id, [FromBody]Employee emp)
        {
            Employee record = DataHelper.Employees.Find(e => e.EmployeeID == id);
            if (record != null)
            {
                record.City = emp.City;
                record.Address = emp.Address;
                record.Title = emp.Title;
                record.HomePhone = emp.HomePhone;
                return record;
            }
            return null;
        }

        public object EmployeesGetName()
        {
            return from e in DataHelper.Employees select new { ID = e.EmployeeID, Name = e.FirstName + " " + e.LastName };
        }

        public object Customers(int count)
        {
            List<Customer> result = new List<Customer>();
            if (count > DataHelper.Customers.Count)
                count = DataHelper.Customers.Count;
            for (int i = 0; i < count; i++)
            {
                result.Add(DataHelper.Customers[i]);
            }
            return result;
        }

        public object CustomersGetName()
        {
            return from c in DataHelper.Customers select new { ID = c.CustomerID, Name = c.CompanyName };
        }

    }

代碼簡化了一下,正常API的服務都部署在多台服務器構建應用集群,一般情況都是通過nginx或其他反向代理服務器接管Client的請求,然后針對負載配置進行轉發處理。但接下來需要講解的是通過開源組件實現無中心化的集群負載調用。

引用組件

首先net core的http client組件並不具這一功能,所以需要引用第三方的一個開源組件BeetleX(組件暫只支持net core 2.1或更高版本) 

定義訪問接口

組件支持通過接口的方式來描述HTTP接口服務,接口的訪問方式對使用和維護都具有着極大的便利性,以下是針對以上服務描述的接口

    [JsonFormater]
    [Controller(BaseUrl = "Home")]
    public interface IDataService
    {
        [Get]
        Task<DateTime> GetTime();
        [Get]
        Task<string> Hello(string name);
        [Get]
        Task<bool> Login(string name, string pwd);
        [Get]
        Task<List<Employee>> Employees();
        [Get]
        Task<Employee> EmployeeGet(int id);
        [Post]
        Task<int> EmployeeAdd(params Employee[] items);
        [Post]
        Task<Employee> EmployeeEdit([CQuery]int id, Employee emp);
        [Get]
        Task<List<EmployeeName>> EmployeesGetName();
        [Get]
        Task<List<Customer>> Customers(int count);
        [Get]
        Task<List<CustomerName>> CustomersGetName();
        [Get]
        Task<List<Order>> Orders(int? employeeid, string customerid, int index, int size);
    }

組件支持Task和非Task返回值的方法定義,由於基礎網絡訪問是基於異步,所以最好還是定義Task返回值的方法。如果定義了非Task返回值訪問組件內部會同步等待完成返回,在並發服務下這不利於線程資源的利用。

集群化訪問定義

接口定義好之后,就可以通過接口來調用並把請求調用負載到不同服務器的服務上。這樣做首先需要定義一個集群化服務訪問對象:

private static HttpClusterApi HttpClusterApi = new HttpClusterApi();

HttpClusterApi對象是線程安全的,所以定義成靜態即可;也可以根據服務分類來定義不同的HttpClusterApi(之於內部的工作原理這里就不詳細解說了,可以到GitHub上過一步了解)。創建了集群接口對象之后就可以用它來創建接口實例。

private static IDataService DataService;

DataService = HttpClusterApi.Create<IDataService>();

同樣接口實例也是線程安的,只需要創建一個即可在不同線程和方法里同時調用。其實這樣創建接口后還沒能正常使用,因為沒有定義相應服務地址,可以通過HttpClusterApi添加不同服務地址:

HttpClusterApi.AddHost("*", Host25, Host29);

以上是所有請求都負載到Host25和Host29,一般情況都不會直這樣定義;*的優先級是最低的,只有沒有匹配到其他url描述的情況才會匹配*

平均負載

平均負載是一種最常用的方式,主要是把並發請求平均到不同的服務器上;以下是針對employee相關請求的地址負載到5個服務上.

HttpClusterApi.AddHost("employee.*", Host26, Host27, Host28, Host29, Host30);

接下簡單地測試一下

            System.Threading.ThreadPool.QueueUserWorkItem(async (o) =>
            {
                while (true)
                {
                    try
                    {
                        await DataService.EmployeeGet(2);
                    }
                    catch (Exception e_)
                    {
                        Console.WriteLine(e_.Message);
                    }
                }
            });

測試結果:

從測試打印信息來看,基本上把請求平均到不同的服務器上,5台服務器的權重值都是10.

權重定義

現實中由於服務器配置不同,服務器運行的服務也有所差異,所以一般情況都不會平均化負載;這時候權重定義就起到一個非常重要的作用,針對不同地址的服務制定不同的重權值這樣可以讓服務資源得到更好地利用!接下來還是針對以上測試代碼,針對不同的服務器設置不同的權重

HttpClusterApi.GetUrlNode("employee.*").Add(new string[] { Host26, Host27, Host28 }).Add(Host29,5).Add(Host30,5);

以上代碼分別把Host29和Host30的權重設置為5其他均為10.

定義備份節點

所謂的備份節點,其實就是不參與負載處理,但所有可用點都故障的情況,備份節點就生效。其實在組件中沒有所謂的備份節點,不過可以通過0權重這個值來實現。當存在其他重權的節點可用時,0權重的節點是不會存在於重權表中的,只有當所有節點不可用的情況下,0權重的節點才會移入到權重表;當有效節點恢復后0權重的節點也會被移出權重表。

故障遷移和恢復

組件會自動把存在N次連接錯的服務地址在權重表中排除出去,所以當服務不可用的情況會短暫有部分請求會引發訪問異常;接下來組件會每隔一段時間去檢測有問題的服務地址,直到服務連接可用的情況才會把服務地址重新添加回重權表里接受負載輪循處理。

自定義集群信息源

如果在代碼中把負載設置寫死了,那到增加節點的時候就非常麻煩需要重新發布程序;組件為了解決這一問題制定了一個接口方便使用者制定自己的集群信息來源配置。通過實現INodeSourceHandler並設置到HttpClusterApi即可以動態更新節點信息便於更改和管理。以下是針對負載源實現的一個HTTP獲取負載源信息實現

    public class HTTPRemoteSourceHandler : INodeSourceHandler
    {
        private HttpClusterApi mHttpClusterApi = new HttpClusterApi();

        private IHttpSourceApi mRemoteSourceApi;

        public HTTPRemoteSourceHandler(string name, params string[] hosts)
        {
            mHttpClusterApi.AddHost("*", hosts);
            mRemoteSourceApi = mHttpClusterApi.Create<IHttpSourceApi>();
            Name = name;
        }

        public string Name { get; set; }

        public int UpdateTime { get; set; } = 5;

        public Task<ApiClusterInfo> Load()
        {
            return mRemoteSourceApi._GetCluster(Name);
        }
    }

實現是每5秒鍾檢則一下信息源,集成到HttpClusterApi如下:

     HTTPRemoteSourceHandler remoteSourceHandler = new HTTPRemoteSourceHandler("default", "http://localhost:8080");
     HttpClusterApi.NodeSourceHandler = remoteSourceHandler;
     var result = await HttpClusterApi.LoadNodeSource();

設置INodeSourceHandler后組件就會檢配置信息,檢測錯誤或版本沒有變化的情況就不會更新配置。以下是針對測編寫的一個HTTP配置服務:

如果想得到更詳細的代碼可以訪問 https://github.com/IKende/FastHttpApi


免責聲明!

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



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