一、准备
由于没有采用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
标签:负载,2core,get,tmpIndex,tmpList,item,HttpClient,public,httpclient From: https://www.cnblogs.com/Jkinbor/p/16784652.html