一、準備
由於沒有采用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