负载均衡(Load Balance),简称 LB,就是将并发的用户请求通过规则后平衡、分摊到多台服务器上进行执行,以此达到压力分摊、数据并行的效果。常见的算法也有许多随机、轮询、加权等,今天我们就使用 C# 来实现这几种算法,并讲解在实际项目中的使用。
负载均衡算法在开发层面,使用场景其实并不多。通常在项目重构、转型、上线大版本新功能等,为了避免上线出现 Bug 应用功能 100% 的挂掉。可以在程序中使用负载均衡,将部分 HTTP 流量,打入项目中新的功能模块,然后进行监控,出现问题可以及时进行调整。
这样 AB 测试的场景,也可以在运维或者网关等其他层面实现流量分配。但现实是大多数公司项目因为一些原因没有这样的支持,这时开发就可以在项目中使用代码进行实现。
有这样一个需求,电商系统中,有一个预估运费的微服务(ShippingCharge )。此时上面领导来了需求,预估运费要改版,开发预估了一下改动不小。经过两周的奋斗 ShippingCharge 需求终于开发测试好了,此时要上线,但是掐指一算,万一有问题不就死翘翘了,而且还和钱相关。
此时负载均衡算法就派上用场了,我们可以让 10% 的流量打入这次的改动,可以先进行监控,可以再全部切过来。实际项目中,使用的肯定是权重的,后面随机、轮询也简单进行介绍一下其实现。
假设在改动 ShippingCharge 时,没有修改旧的功能,是在 controller 下面,对 call business 层换成了这次需求的,这样我们就可以使用负载均衡,让 10% 的流量打入新的 business,其余的依然走老的 business。
这里不会说的太精细,会将核心实现代码做介绍,实际项目中使用需要自己进行一下结合,举一反三哈
下面定义了一个 ServiceCenterModel 主要用作承载需要负载均衡的对象信息,可以是 call 下游的 url,也可以是程序内的某一算法标识
public class ServiceCenterModel
{
/// <summary>
/// Service
/// 1. call 下游 server,可以放 url
/// 2. 在同一个程序内,可以放一个业务标识
/// </summary>
public string Service { get; set; }
public int Weight { get; set; }
}
随机算法的先对来讲,较为简单一些,主要根据 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));
}
}
轮询的实现思路,将每次读取 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"));
}
}
权重的实现思路,将配置权重的 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"));
}
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/weixin_46785144/article/details/125323707
内容来源于网络,如有侵权,请联系作者删除!