[2core]基於httpclient實現負載均衡

一、準備

由於沒有采用asp.net core框架提供的HttpClientFactory和依賴注入方式使用HttpClient,所以從asp.net遷移到asp.net core變化並不大,而且HttpClient的使用也基本沒有變化。因此本文重點記錄的是自己設計的基於工廠模式創建和管理HttpClient的過程,以及負載均衡的實現。

 

二、設計

經過對比知道ASP.NET Core中的HttpClient使用與ASP.NET中的HttpClient沒有差異,當然底層實現變化不小,因此,就把重心放在瞭如何實現負載均衡上。設計思路如下:

1)HttpClient對象有工廠負責創建和維護。
2)HttpClient對象目標服務器通過appsettings.json配置方式管理。
3)編程時程序員僅需通過HttpFactory.Create("服務器名稱")就可以獲取HttpClient對象。
4)HttpFactory創建HttpClient對象後,按照配置文件中設定的負載均衡模式管理。
5)HttpFactory輸出HttpClient對象時,按照HttpClient對象集所述負載均衡模式,有效選擇HttpClient。
6)負載均衡支持“輪詢模式”和“加權平均模式”。

 

三、實現
列出上述設計思路就可以進行編碼了

1.配置文件和Options定義

{
  "ApiServers": [
    {
      "Name": "Did",
      "Mode": "Poll",
      "ApiEndPoints": [
        {
          "Address": "http://localhost:10100/",
          "Weight": 1
        }
      ]
    }
  ]
}
    public class ApiServerOptions
    {
        /// <summary>
        /// 服務名稱,不允許重複,使用名稱獲取服務
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// 服務模式(Poll|Weight),Poll表示通過輪詢模式實現負載均衡(普通),Weight表示權重模式實現負載均衡(加權平均)
        /// </summary>
        public string Mode { get; set; }
        /// <summary>
        /// 服務集合,提供相同服務的服務器集合
        /// </summary>
        public List<ApiEndPointOptions> ApiEndPoints { get; set; }
    }

    public class ApiEndPointOptions
    {
        /// <summary>
        /// 終結點服務地址
        /// </summary>
        public string Address { get; set; }
        /// <summary>
        /// 權重值,僅用於Weight模式,取值範圍[1-100],默認1
        /// </summary>
        public int Weight { get; set; } = 1;
    }

2.實現默認工廠

2.1.定義類模型

        private class ApiServerObject
        {
            public string Name { get; set; }
            public string Mode { get; set; } = LB_MODE_POLL;
            public List<ApiServerEndPointObject> EndPoints { get; set; }
        }
        private class ApiServerEndPointObject
        {
            public string Address { get; set; }
            public int Weight { get; set; } = 0;
            public int WeightCurrent { get; set; } = 0;
            public bool IsActive { get; set; } = true;
            public TestHttpHelper Helper { get; set; }
        }

2.2.依據配置文件生成HttpClient對象集合

internal class TestDefaultHttpFactory
    {
        private static object _objLocker = new object();
        private const string LB_MODE_WEIGHT = "weight";
        private const string LB_MODE_POLL = "poll";

        private static TestDefaultHttpFactory _instance = null!;
        public static TestDefaultHttpFactory Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (_objLocker)
                    {
                        if (_instance == null)
                        {
                            _instance = new TestDefaultHttpFactory();
                        }
                    }
                }
                return _instance;
            }
        }
        private TestDefaultHttpFactory()
        {
            Generate();
        }

        private void Generate()
        {
            var _tmpStr = JsonConvert.SerializeObject(AppSetting.Instance.ApiServers);
            var _tmpList = JsonConvert.DeserializeObject<List<ApiServerOptions>>(_tmpStr);
            foreach (var item in _tmpList)
            {
                if (!apiServerList.ContainsKey(item.Name))
                {
                    if (LB_MODE_WEIGHT == item.Mode.ToLower())
                    {
                        GenerateWeight(item);
                    }
                    else
                    {
                        GeneratePoll(item);
                    }
                }
            }
        }
        private void GeneratePoll(ApiServerOptions item)
        {
            var _tmpList = new List<ApiServerEndPointObject>();
            foreach (var subItem in item.ApiEndPoints)
            {
                if (!_tmpList.Exists(o => o.Address == subItem.Address))
                {
                    _tmpList.Add(new ApiServerEndPointObject
                    {
                        Address = subItem.Address,
                        Helper = new TestHttpHelper(subItem.Address)
                    });
                }
            }
            apiServerList.TryAdd(item.Name.ToLower(), new ApiServerObject
            {
                Name = item.Name.ToLower(),
                Mode = item.Mode.ToLower(),
                EndPoints = _tmpList
            });
        }
        private void GenerateWeight(ApiServerOptions item)
        {
            var _tmpList = new List<ApiServerEndPointObject>();
            if (item.ApiEndPoints != null && item.ApiEndPoints.Count > 0)
            {
                foreach (var subItem in item.ApiEndPoints)
                {
                    if (subItem.Weight < 1) subItem.Weight = 1;
                    if (subItem.Weight > 100) subItem.Weight = 100;

                    if (!_tmpList.Exists(o => o.Address == subItem.Address))
                    {
                        _tmpList.Add(new ApiServerEndPointObject
                        {
                            Address = subItem.Address,
                            Weight = subItem.Weight,
                            Helper = new TestHttpHelper(subItem.Address)
                        });
                    }
                }
            }
            apiServerList.TryAdd(item.Name.ToLower(), new ApiServerObject
            {
                Name = item.Name.ToLower(),
                Mode = item.Mode.ToLower(),
                EndPoints = _tmpList
            });
        }
}

上述代碼首先由單例模式創建了工廠對象,然後通過Generate方法分別創建“輪詢模式”和“加權平均模式”的HttpClient對象。

3.負載均衡

        public TestHttpHelper Create(string apiServerName)
        {
            ApiServerObject _tmpApiServer;
            if (apiServerList.TryGetValue(apiServerName, out _tmpApiServer))
            {
                if (LB_MODE_WEIGHT == _tmpApiServer.Mode)
                {
                    return ComputeWeight(_tmpApiServer);
                }
                else//poll
                {
                    return ComputePoll(_tmpApiServer);
                }
            }
            return null;
        }

外部請求者通過調用DefaultHttpFactory.Instance.Create(“服務器名稱”),得到HttpHelper對象,HttpHelper封裝了HttpClient對象。

3.1.輪詢模式

private TestHttpHelper ComputePoll(ApiServerObject apiServer)
        {
            TestHttpHelper result = null;
            if (apiServer.EndPoints != null && apiServer.EndPoints.Count > 0)
            {
                var _tmpList = apiServer.EndPoints.FindAll(o => o.IsActive);
                if (_tmpList != null && _tmpList.Count > 0)
                {
                    int _tmpIndex = 0;
                    if (pollList.ContainsKey(apiServer.Name))
                    {
                        pollList.TryGetValue(apiServer.Name, out _tmpIndex);
                        if (_tmpIndex >= _tmpList.Count)
                        {
                            _tmpIndex = 0;
                        }

                        var _tmpIndex2 = (_tmpIndex + 1) >= _tmpList.Count ? 0 : (_tmpIndex + 1);
                        pollList.TryUpdate(apiServer.Name, _tmpIndex2, _tmpIndex);
                    }
                    else
                    {
                        var _tmpIndex2 = (_tmpIndex + 1) >= _tmpList.Count ? 0 : (_tmpIndex + 1);
                        pollList.TryAdd(apiServer.Name, _tmpIndex2);
                    }
                    result = _tmpList[_tmpIndex].Helper;
                }
            }
            return result;
        }

此模式的實現過程是,通過定義一個集合,記錄目標服務器使用HttpClient的順序,然後按照順序依次分配。

3.2.負載均衡 - 加權平均模式

private TestHttpHelper ComputeWeight(ApiServerObject apiServer)
        {
            TestHttpHelper result = null;
            if (apiServer.EndPoints != null && apiServer.EndPoints.Count > 0)
            {
                var _tmpList = apiServer.EndPoints.FindAll(o => o.IsActive);
                if (_tmpList != null && _tmpList.Count > 0)
                {
                    if (_tmpList.Count == 1)
                    {
                        result = _tmpList[0].Helper;
                    }
                    else
                    {
                        var _tmpIndex = -1;
                        var _tmpSumWeight = 0;
                        for (int i = 0; i < _tmpList.Count; i++)
                        {
                            _tmpSumWeight += _tmpList[i].Weight;

                            _tmpList[i].WeightCurrent += _tmpList[i].Weight;
                            if (_tmpIndex == -1 || _tmpList[_tmpIndex].WeightCurrent < _tmpList[i].WeightCurrent)
                            {
                                _tmpIndex = i;
                            }
                        }
                        _tmpList[_tmpIndex].WeightCurrent -= _tmpSumWeight;
                        result = _tmpList[_tmpIndex].Helper;
                    }
                }
            }
            return result;
        }

此模式的實現過程,主要參考Nignx實現平滑加權平均的原理,俗稱割韭菜法。

 

四、使用

    public class TestHttpFactory
    {
        public static TestHttpHelper Create(string apiServerName)
        {
            if (apiServerName.IsNotNullEmptyWhiteSpace())
            {
                return TestDefaultHttpFactory.Instance.Create(apiServerName.ToLower());
            }
            return null;
        }
    }

 

五、總結

通過上述方案就完成了HttpClient的技術遷移。

 

測試代碼:https://gitee.com/kinbor/jks.core.test.httpclient.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章