四种负载均衡算法思想
- 随机法
- 轮询
- 一致hash法
- 最小活跃数
-------------随机
若只是单纯的使用Random进行随机的话,可以实现,但是如果服务器的负载能力大小不一样,
就会造成配置高的服务器处于空闲或者负载量小,而配置低的服务器任务繁重。因此为每台服务器
设置一个权重来表明负载的能力大小。这是如何选取就有两个方式,一个是根据权重大小,向服务
器List集合中添加多少个,这样做的缺点就在于当服务器过多,或者权重值过大,那这个list集合就
会很大,并且每更改一次就要重新维护一次,不适合动态。这是可以采用命中机制
》假如现在有一个Map<ip,weight>,里面的weight集合为【10,5,10】
》这时的请求数量为6
》6<10~~~~~~~~~~~>返回当前权重为10的ip
》若请求数为11;
》11>10,进行相减得1,再判断1<5命中,返回权重为5的ip地址
实现代码如下:
public class Randomsolution {
public static String getserver() {
/*//在服务器的负载能力差不多的情况下合适,可一但服务器的性能此时差别比较大,就会造成性能强的服务器处于空闲或者低载,请求的分配就不均衡
Random r = new Random();
return ServerIps.iplist.get(r.nextInt(ServerIps.iplist.size()));*/
/*为每一台服务器设置权重,然后按权重向list集合中增加几个,在执行上述的随机算法,但是如果权重过大,就会形成一个很大的list,一定会影响速度,并且也过于。。。*/
/*根据当前的请求数来判断,即如果权重数组为【2,5,3】,在进行命中处理,假如为3<2,则进行3-2处理后;1<5则返回权重为5的ip
* 如果权重一样的话,直接采用随机*/
int totalWeight = 0;
for (Integer weight : ServerIps.WEIGHT_IPMAP.values()) {
totalWeight += weight;
}
int requestsize = new Random().nextInt(totalWeight);
System.out.println(requestsize);
for (String ip : ServerIps.WEIGHT_IPMAP.keySet()) {
int weight = ServerIps.WEIGHT_IPMAP.get(ip);
if (requestsize < weight) {
return ip;
}
requestsize = requestsize - weight;
}
return null;
}
---------------------轮询
轮询就是按照当前的请求id经过取模和命中机制返回服务器的ip,具体的实现如下:
》若请求的id为5;此时服务器的权重列表为【5,3,1】;
》计算出服务器的权重的总和为9
》5%9=5(这一步是为了保证id过大的时候,始终能够命中)
》和上面的随机的命中算法是一样的
》如果id从0开始,则最终的结果会是A服务器五次,B服务器三次,C服务器一次,按照此规律进行循环
实现代码如下:
public class RoundRobin {
public static String getIp(){
int totalWeight = 0;
for(Integer weight : ServerIps.WEIGHT_IPMAP.values()) {
totalWeight += weight;
}
int requestid = requestId.getrequestId();
int iid=requestid % totalWeight;
System.out.println(requestid);
for (String ip : ServerIps.WEIGHT_IPMAP.keySet()) {
int weight = ServerIps.WEIGHT_IPMAP.get(ip);
if (iid < weight) {
return ip;
}
iid = iid - weight;
}
return null;
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
System.out.println(getIp());
}
}
这只是简单的轮询,如果能够让这些请求交叉分布就更好了,平滑加权轮询的出现就是为了解决这个问题,这也是Nginx的负载均衡机制(简单思想)
》采用Map<ip,weight<ip,current’weight>>的数据结构,weight对象用来记载当前ip的实时权重值
》若初始的权重为【5,3,1】,每次循环初始化到weight对象中并封装到map集合中(即当前的currentweight+初始的权重值,作为当前的权重值)
》找出当前weight对象中maxCurrentweight,即当前权重最大的点
》进行减操作maxCurrentweight-totalweight,将结果赋给currentWeight,返回当前权重值的ip;
具体代码如下:
public class SmoothRoundRobin {
/*平滑加权轮询算法*/
public static HashMap<String,Weight> weightMap = new LinkedHashMap<>();
public static int totalweight =0;
/*计算weight的总和*/
public static Integer gettotalweight(){
for(Integer weight:ServerIps.WEIGHT_IPMAP.values()){
totalweight +=weight;
}
return totalweight;
}
public static void InitweightMap(){
for(String ip:ServerIps.WEIGHT_IPMAP.keySet()){
Integer weight = ServerIps.WEIGHT_IPMAP.get(ip);
weightMap.put(ip,new Weight(ip,weight,0));
}
gettotalweight();
}
public static String getServer(){
if(weightMap.isEmpty()){
InitweightMap();
}
/*为currentweight赋值,每次的重新计算当前权重*/
for(Weight w:weightMap.values()){
w.setCurrentweight(w.getCurrentweight()+w.getWeight());
}
System.out.println(weightMap);
/*找出weightMap中最大的currentweight*/
Weight maxCurrentweight = null;
for(Weight weight:weightMap.values()){
if(maxCurrentweight==null||weight.getCurrentweight()>maxCurrentweight.getCurrentweight()){
maxCurrentweight=weight;
}
}
/*对maxweight进行-sumweight处理*/
maxCurrentweight.setCurrentweight(maxCurrentweight.getCurrentweight()-totalweight);
return maxCurrentweight.getIp();
}
public static void main(String[] args) {
for(int i=0;i<20;i++){
System.out.println(getServer());
}
}
}
这样就会使输出结果更加均衡。
------------------------一致hash法
又称为hash环法,根据请求者的IP计算出其hashcode,去查询由服务器ip的hashcode所构成的一个环,这个环上面有很多虚节点,虚节点的多少可以有权重而定,
找出比当前iphash值大的第一个点作为返回。
注意:一定要保证hash环是一个有序的环(采用TreeMap实现)
具体代码如下:
public class ConsistenHash {
public static TreeMap<Integer,String> hashcirle=new TreeMap();
private static final int childrensize = 20;
//初始化hash环
static {
for (String ip:ServerIps.iplist){
for(int i=0;i<childrensize;i++){
int nodehash = geHash(ip+i);
hashcirle.put(nodehash,ip);
}
}
System.out.println(hashcirle);
}
/*根据请求的ip的hash值,找到比这个值大的一颗子树(同样有序的),取出子树中的最小节点,返回此节点的ip值,如没有这样一课子树,则返回整棵树最小节点的ip值
* 问题:若此时的最小节点出了故障,或者负载量太高,再分配给他则会造成等待,或者不成功,应该再加上一个值来反映当前结点的活跃状态,根据活跃状态判断是否可取
* 依次向下排取,但是这种方法的问题又出来了,可以将活跃节点用一个有序表来进行映射,这时候取对应的点*/
public static String getServer(String clientip){
int clientiphash = geHash(clientip);
SortedMap<Integer, String> integerStringSortedMap = hashcirle.tailMap(clientiphash);
if(integerStringSortedMap!=null){
return integerStringSortedMap.get(integerStringSortedMap.firstKey());
}else {
return hashcirle.get(hashcirle.firstKey());
}
}
public static int geHash(String str){
final int p=115487;
int hash=(int)857422411L;
for (int i=0;i<str.length();i++){
hash=(hash^str.charAt(i))*p;
hash+=hash<<13;
hash^=hash>>7;
hash+=hash<<3;
hash^=hash>>17;
hash+=hash<<5;
}
if (hash<0){
hash=Math.abs(hash);
}
return hash;
}
public static void main(String[] args) {
for(int i=0;i<10;i++){
String s = getServer("192.168.1."+i);
System.out.println(s);
}
}
}
-------------------最小活跃数
这个就是动态实现了,需要根据服务器此时的请求多少和其他影响因素得出一个活跃因子,然后根据这个值去判断由哪一个服务器来处理当前请求,可以结合上面的三种方法一起实现