一、准备
由于没有采用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