手写分布式负载均衡算法(一)
负载均衡在实际工作项目中是很普遍的,主要分为硬件负载均衡和软件负载均衡。硬件负载均衡主要有F5、Array等。都是一些商用的负载均衡器。软件负载均衡主要包括耳熟能详的Nginx、LVS、Tengine等,使用起来成本比较低,但是也要去维护。
今天讨论的是常用的负载均衡算法。
随机算法:顾名思义 根据服务器列表大小值随机选取一台服务器访问。
带权重的随机算法实现:
通过生成的随机数,计算这个随机数所落入的区间,即可知道选中哪台服务器,比如如果随机数3,则在区间[0,5)选中服务器A,随机数为7则选中B服务器。
import java.util.HashMap;
import java.util.Map;
public class IpMap {
static Map<String,Integer> ipAddrs = new HashMap();
static {
ipAddrs.put("A",5);
ipAddrs.put("B",3);
ipAddrs.put("C",2);
}
}
import java.util.*;
public class Random01 {
//不考虑权重
public static String getIpNoWeight(){
Set ipSet = new HashSet();
for(String ip:IpMap.ipAddrs.keySet()){
ipSet.add(ip);
}
List<String> ipList = new ArrayList<>();
ipList.addAll(ipSet);
Integer random = new Random().nextInt(ipList.size());
return ipList.get(random);
}
//考虑权重
public static String getIpWithWeight01(){
List<String> ipList = new ArrayList<>();
for(String ip:IpMap.ipAddrs.keySet()){
Integer weight = IpMap.ipAddrs.get(ip);
for(int i=0;i<weight;i++){
ipList.add(ip);
}
}
Integer random = new Random().nextInt(ipList.size());
return ipList.get(random);
}
public static String getIpWithWeight02(){
int totalweight = 0;
boolean sameweight = true;
Object[] weights = IpMap.ipAddrs.values().toArray();
for(int i=0;i<weights.length;i++){
totalweight +=(int) weights[i];
if(sameweight && i>0 && !weights[i].equals(weights[i-1])){
sameweight = false;
}
}
Integer random = new Random().nextInt(totalweight);
if(!sameweight){
for(String ip:IpMap.ipAddrs.keySet()){
Integer value = IpMap.ipAddrs.get(ip);
if(random<value){
return ip;
}
random = random - value;
}
}
//权重相同,相当于不考虑权重完全随机
return getIpNoWeight();
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
//System.out.println(getIpNoWeight());
System.out.println(getIpWithWeight02());
}
}
}
轮询算法:
有一种方法,调用编号,比如第1次调用为1,第2次调用为2,第100次调用为100,调用的编号是递增的,所以可以根据调用的编号推算出服务器。
Server=[]A,B,C]对应权重weights=[2,3,5]权重和为10;
如果调用10次,调用顺序为AABBBCCCCC
可以对权重和取余。
1号调用,1%10=1;2号调用,2%10=2......100号调用,100%10=0。可以发现编号在0-9之间。可以根据这10个数字找到对应的服务器。方法与上面类似,可以把权重想象为坐标轴“0-2-5-10”。
上面的方法,其实有一个弊端,如果一台服务器的权重特别大,他就会连续处理请求,此时可以使用平滑加权轮询。
每个服务器对应两个权重,分别为weight和currentWeight。weight固定,currentWeight动态变化,初始值为0。有新的请求进来时,遍历服务器列表,让它的currentWeight加上自身权重,遍历完后,找到最大的currentWeight,将其减去权重,然后返回对应服务器。
请求编号1中,初始值为[5, 1, 1],数组中最大的值5,那么选择结果为A,选择后最大的值5减去权重和7,变成了[-2, 1, 1];
请求编号2来的时候,[-2, 1, 1]加上[5, 1, 1],那么就变成编号2中的初始值为[3, 2, 2],数组中最大的值3,那么选择结果为A,最大的值3减去权重和7,变成了[-4, 2, 2];
请求编号3来的时候,[-4, 2, 2]加上[5, 1, 1],那么就变成编号2中的初始值为[1, 3, 3],数组中最大的值3,那么选择结果为B,最大的值3减去权重和7,变成了[1, -4, 3];
以此类推。。。。。。。。
package fuzaijunheng;
import java.util.*;
public class Round02 {
private static Integer pos =0;
//不考虑权重
public static String getIpNoWeight(){
String serverIp = null;
synchronized (pos){
if(pos>=IpMap.ipAddrs.size()){
pos=0;
}
HashSet<String> ipSet = new HashSet<>();
for(String ip:IpMap.ipAddrs.keySet()){
ipSet.add(ip);
}
ArrayList<String> ipList = new ArrayList<>();
ipList.addAll(ipSet);
serverIp = ipList.get(pos);
pos++;
}
return serverIp;
}
//考虑权重(简单的就不演示了)
static int num=0;
public static String getIpWithWeight(){
int totalWeight=0;
boolean sameWeight=true;
Object[] weights = IpMap.ipAddrs.values().toArray();
for(int i=0;i<weights.length;i++){
Integer weight=(Integer)weights[i];
totalWeight +=weight;
if(sameWeight&&i>0&& !weight.equals(weights[i-1])){
sameWeight=false;
}
}
Integer sequenceNum = ++num;
Integer offset = sequenceNum % totalWeight;
offset = offset==0?totalWeight: offset;
if(!sameWeight){
for(String ip:IpMap.ipAddrs.keySet()){
Integer weight = IpMap.ipAddrs.get(ip);
if(offset<=weight){
return ip;
}
offset = offset - weight;
}
}
return null;
}
//平滑加权
private static Map<String,Weight> weightMap = new HashMap<>();
public static String getIpwithCurrentWeight(){
int totalWeight = 0;
for(int weigh: IpMap.ipAddrs.values()){
totalWeight+=weigh;
}
//初始化weightMap,初始化currentWeight复制为weight
if(weightMap.isEmpty()){
IpMap.ipAddrs.forEach((key, value)->{
weightMap.put(key,new Weight(key,value,value));
});
}
//找出currentWeight最大值
Weight maxCurrentWeight = null;
for(Weight weight:weightMap.values()){
if(maxCurrentWeight==null || weight.getCurrentWeight()>maxCurrentWeight.getCurrentWeight()){
maxCurrentWeight = weight;
}
}
//将maxcurrentWeight减去权重和
maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() -totalWeight);
//所有ip的currentWeight统一加上权重
for(Weight weight1:weightMap.values()){
weight1.setCurrentWeight(weight1.getCurrentWeight()+weight1.getWeight());
}
return maxCurrentWeight.getIp();
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
System.out.println(getIpwithCurrentWeight());
}
}
}
class Weight{
private String ip;
private Integer weight;
private Integer currentWeight;
public Weight(String ip, Integer weight, Integer currentWeight) {
this.ip = ip;
this.weight = weight;
this.currentWeight = currentWeight;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public Integer getCurrentWeight() {
return currentWeight;
}
public void setCurrentWeight(Integer currentWeight) {
this.currentWeight = currentWeight;
}
}