.NET负载均衡
菜鸟厚非 人气:5一、简介
负载均衡(Load Balance),简称 LB,就是将并发的用户请求通过规则后平衡、分摊到多台服务器上进行执行,以此达到压力分摊、数据并行的效果。常见的算法也有许多随机、轮询、加权等,今天我们就使用 C# 来实现这几种算法,并讲解在实际项目中的使用。
二、应用场景
负载均衡算法在开发层面,使用场景其实并不多。通常在项目重构、转型、上线大版本新功能等,为了避免上线出现 Bug 应用功能 100% 的挂掉。可以在程序中使用负载均衡,将部分 HTTP 流量,打入项目中新的功能模块,然后进行监控,出现问题可以及时进行调整。
这样 AB 测试的场景,也可以在运维或者网关等其他层面实现流量分配。但现实是大多数公司项目因为一些原因没有这样的支持,这时开发就可以在项目中使用代码进行实现。
三、实际案例
有这样一个需求,电商系统中,有一个预估运费的微服务(ShippingCharge )。此时上面领导来了需求,预估运费要改版,开发预估了一下改动不小。经过两周的奋斗 ShippingCharge 需求终于开发测试好了,此时要上线,但是掐指一算,万一有问题不就死翘翘了,而且还和钱相关。
此时负载均衡算法就派上用场了,我们可以让 10% 的流量打入这次的改动,可以先进行监控,可以再全部切过来。实际项目中,使用的肯定是权重的,后面随机、轮询也简单进行介绍一下其实现。
假设在改动 ShippingCharge 时,没有修改旧的功能,是在 controller 下面,对 call business 层换成了这次需求的,这样我们就可以使用负载均衡,让 10% 的流量打入新的 business,其余的依然走老的 business。
四、算法实现
这里不会说的太精细,会将核心实现代码做介绍,实际项目中使用需要自己进行一下结合,举一反三哈
下面定义了一个 ServiceCenterModel 主要用作承载需要负载均衡的对象信息,可以是 call 下游的 url,也可以是程序内的某一算法标
4.1 随机
随机算法的先对来讲,较为简单一些,主要根据 Random 与 ServiceList 的数量结合实现。
如下:
/// <summary> /// 随机 /// </summary> public class RandomAlgorithm { /// <summary> /// Random Function /// </summary> private static readonly Random random = new Random(); /// <summary> /// serviceList /// </summary> /// <param name="serviceList">service url set</param> /// <returns></returns> public static string Get(List<ServiceCenterModel> serviceList) { if (serviceList == null) return null; if (serviceList.Count == 1) return serviceList[0].Service; // 返回一个小于所指定最大值的非负随机数 int index = random.Next(serviceList.Count); string url = serviceList[index].Service; return url; } }
模拟 10 次 http request,可以看到对OldBusiness、NewBusiness进行了随机的返回
public static void Main(string[] args) { // 模拟从配置中心读取 Service var serviceList = new List<ServiceCenterModel>() { new ServiceCenterModel { Service ="OldBusiness"}, new ServiceCenterModel { Service ="NewBusiness"}, }; // 模拟 Http 请求次数 for (int i = 0; i < 10; i++) { Console.WriteLine(RandomAlgorithm.Get(serviceList)); } }
4.2 轮询
轮询的实现思路,将每次读取 ServiceList 的 Index 放到静态全局变量中,当到 ServiceList 最后一个时从0开始读取。
如下:
/// <summary> /// 轮询 /// </summary> public class PollingAlgorithm { private static Dictionary<string, int> _serviceDic = new Dictionary<string, int>(); private static SpinLock _spinLock = new SpinLock(); /// <summary> /// Get URL From Service List /// </summary> /// <param name="serviceList">Service URL Set</param> /// <param name="serviceName">Service Name</param> /// <returns></returns> public static string Get(List<ServiceCenterModel> serviceList, string serviceName) { if (serviceList == null || string.IsNullOrEmpty(serviceName)) return null; if (serviceList.Count == 1) return serviceList[0].Service; bool locked = false; _spinLock.Enter(ref locked);//获取锁 int index = -1; if (!_serviceDic.ContainsKey(serviceName)) // Not Exist _serviceDic.TryAdd(serviceName, index); else _serviceDic.TryGetValue(serviceName, out index); string url = string.Empty; ++index; if (index > serviceList.Count - 1) //当前索引 > 最新服务最大索引 { index = 0; url = serviceList[0].Service; } else { url = serviceList[index].Service; } _serviceDic[serviceName] = index; if (locked) //释放锁 _spinLock.Exit(); return url; } }
模拟 10 次 http request,可以看到对OldBusiness、NewBusiness进行了轮询返回
public static void Main(string[] args) { // 模拟从配置中心读取 Service var serviceList = new List<ServiceCenterModel>() { new ServiceCenterModel { Service ="OldBusiness"}, new ServiceCenterModel { Service ="NewBusiness"}, }; // 模拟 Http 请求次数 for (int i = 0; i < 10; i++) { Console.WriteLine(PollingAlgorithm.Get(serviceList, "ShippingChargeBusiness")); } }
4.3 权重
权重的实现思路,将配置权重的 Service 按照数量放置在一个集合中,然后按照轮询的方式进行读取,需要注意的是这的 weight 只能配置大于 0 的整数。
如下:
/// <summary> /// 权重 /// </summary> public class WeightAlgorithm { private static ConcurrentDictionary<string, WeightAlgorithmItem> _serviceDic = new ConcurrentDictionary<string, WeightAlgorithmItem>(); private static SpinLock _spinLock = new SpinLock(); public static string Get(List<ServiceCenterModel> serviceList, string serviceName) { if (serviceList == null) return null; if (serviceList.Count == 1) return serviceList[0].Service; bool locked = false; _spinLock.Enter(ref locked);//获取锁 WeightAlgorithmItem weightAlgorithmItem = null; if (!_serviceDic.ContainsKey(serviceName)) { weightAlgorithmItem = new WeightAlgorithmItem() { Index = -1, Urls = new List<string>() }; BuildWeightAlgorithmItem(weightAlgorithmItem, serviceList); _serviceDic.TryAdd(serviceName, weightAlgorithmItem); } else { _serviceDic.TryGetValue(serviceName, out weightAlgorithmItem); weightAlgorithmItem.Urls.Clear(); BuildWeightAlgorithmItem(weightAlgorithmItem, serviceList); } string url = string.Empty; ++weightAlgorithmItem.Index; if (weightAlgorithmItem.Index > weightAlgorithmItem.Urls.Count - 1) //当前索引 > 最新服务最大索引 { weightAlgorithmItem.Index = 0; url = serviceList[0].Service; } else { url = weightAlgorithmItem.Urls[weightAlgorithmItem.Index]; } _serviceDic[serviceName] = weightAlgorithmItem; if (locked) //释放锁 _spinLock.Exit(); return url; } private static void BuildWeightAlgorithmItem(WeightAlgorithmItem weightAlgorithmItem, List<ServiceCenterModel> serviceList) { serviceList.ForEach(service => //有几个权重就加几个实例 { for (int i = 0; i < service.Weight; i++) { weightAlgorithmItem.Urls.Add(service.Service); } }); } } public class WeightAlgorithmItem { public List<string> Urls { get; set; } public int Index { get; set; } }
模拟 10 次 http request,可以看到对 OldBusiness 返回了 9 次,NewBusiness 返回了一次
public static void Main(string[] args) { // 模拟从配置中心读取 Service var serviceList = new List<ServiceCenterModel>() { new ServiceCenterModel { Service ="OldBusiness",Weight = 9 }, new ServiceCenterModel { Service ="NewBusiness",Weight = 1 }, }; // 模拟 Http 请求次数 for (int i = 0; i < 10; i++) { Console.WriteLine(WeightAlgorithm.Get(serviceList, "ShippingChargeBusiness")); } }
加载全部内容