[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

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