负载均衡算法
加权随机算法
算法解释
例:有四台服务器ip,weight如下,权重越大代表机器性能越好,四台机器被访问的比率应该是权重比率
ip | weight |
---|---|
192.168.0.1 | 3 |
192.168.0.2 | 7 |
192.168.0.3 | 4 |
192.168.0.4 | 6 |
如何用随机方法控制机器的访问比率
方法一:新建一个ipList,里面按照权重添加机器ip,例如添加192.168.0.1三次,这样ipList就有20个ip了,就可以用随机算法了,但是缺点是浪费空间
方法二:坐标轴法,例如我把他们的权重当作长度画在坐标轴上如下图:
我们可以随机生成一个20内的数,落在哪个区间就用哪个ip,比如生成13,落在第三个区间用192.168.0.3
code
public class WeightRandom {
public static Server getServer(){
int totalWeight = 0;
for(Server server:WeightServer.servers){
totalWeight +=server.getWeight();
}
int pos = new Random().nextInt(totalWeight);
for(Server server:WeightServer.servers){
if(pos<server.getWeight()){
return server;
}
pos-=server.getWeight();
}
return null;
}
public static void main(String[] args) {
for(int i=0;i<1000;i++){
Server server = getServer();
server.setCallNum(server.getCallNum()+1);
}
for(Server server:WeightServer.servers){
System.out.println(server);
}
}
}
加权轮询算法
算法解释
例:有三台服务器ip,weight如下,权重越大代表机器性能越好,加权轮询的结果应是192.168.0.1,192.168.0.1,192.168.0.2,192.168.0.2,192.168.0.2,192.168.0.3
ip | weight |
---|---|
192.168.0.1 | 2 |
192.168.0.2 | 3 |
192.168.0.3 | 1 |
这个很简单,弄个自增变量对totalWeight取模即可
code
public class WeightRoundRobin {
private static AtomicInteger ai = new AtomicInteger(0);
public static Server getServer(){
int totalWeight = 0;
for(Server server:WeightServer.servers){
totalWeight +=server.getWeight();
}
int pos = ai.getAndIncrement() % totalWeight;
for(Server server:WeightServer.servers){
if(pos<server.getWeight()){
return server;
}
pos-=server.getWeight();
}
return null;
}
public static void main(String[] args) {
for(int i=0;i<30;i++){
Server server = getServer();
server.setCallNum(server.getCallNum()+1);
System.out.println(server);
}
System.out.println("-------------------------------------------------------");
for(Server server:WeightServer.servers){
System.out.println(server);
}
}
}
平滑加权轮询算法
算法解释
该算法是对加权随机算法的优化,也看到了加权随机算法都是先访问一台机器(权重次数),然后在访问若干次另一台机器,这样的负载均衡其实是很不均衡的,我们希望他不要老是访问一台机器
例如有机器A:1,B:2,C:3,冒号后数字代表权重
算法如下:
初始化两个权重数组
固定权重weight:A:1,B:2,C:3
动态权重curWeight:A:0,B:0,C:0
计算totalWeight=6
curWeight[i]+=weight[i] | (max curWeight) | 返回ip | (max curWeight)-totalWeight |
---|---|---|---|
curWeight1,2,3 | 3 | C | 1,2,-3 |
curWeight:2,4,0 | 4 | B | 2,-2,0 |
curWeight:3,0,3 | 3 | A | -3,0,3 |
curWeight:-2,2,6 | 6 | C | -2,2,0 |
code
public class SmoothWeightRoundRobin {
private static int[] curWeight = new int[WeightServer.servers.size()];
public static Server getServer(){
int totalWeight = 0;
for(Server server:WeightServer.servers){
totalWeight +=server.getWeight();
}
for(int i=0;i<curWeight.length;i++){
curWeight[i]+=WeightServer.servers.get(i).getWeight();
}
int maxWeightI = 0;
int maxWeight = curWeight[0];
for(int i=1;i<curWeight.length;i++){
if(curWeight[i]>maxWeight){
maxWeightI = i;
maxWeight = curWeight[i];
}
}
curWeight[maxWeightI]-=totalWeight;
return WeightServer.servers.get(maxWeightI);
}
public static void main(String[] args) {
for(int i=0;i<30;i++){
Server server = getServer();
server.setCallNum(server.getCallNum()+1);
System.out.println(server);
}
System.out.println("-------------------------------------------------------");
for(Server server:WeightServer.servers){
System.out.println(server);
}
}
}
一致性hash算法
算法解释
hash环原理
hash环上只有数字,数字从00:00的位置从零开始顺时针自增1,加到23:59:59(大约),加到最后位置就是int的最大数值。假设有三台服务器的ip地址经过hash算法映射到int的值如图,假设此时有一个客户端ip来访问机器集群,映射的clientHash在ip1和ip2之间,他就选择ip2作为访问地址,如果clientHash在ip2和ip3之间,就选ip3作为访问地址,即选择比clientHash大的第一个ip地址,如果clientHash落在ip3和ip1之间呢?找不到比clientHash大的ip,就选择所有ip中最小的那个,这样就是一个完整的hash环。
hash环如何保证映射的稳定性?
作为分布式集群,我们希望同一个用户尽可能的的访问同一台机器,即要确保clientHash到服务集群映射的稳定性,这一点hash环也很好的保证了,由于客户端ip不常变化,经过hash的数值也都不变,所以只要集群机器不变,映射性就不会改变,试想如下情形:
ip2挂了
这种情况只会影响到之前落在ip1和ip2之间的客户端,他们会被委托给ip3,这也只不过影响了1/3
新增一台ip4在ip2和ip3之间
这种情况只会把ip2和ip4之间的客户端委托给新机器ip4,影响更小了
综上所述无论新增还是减少机器,对于映射稳定性都是影响不大的,不像普通的clientHash%集群机器数这种算法,新增减少机器影响是巨大的
hash环也不总是均衡的
如果机器分布成这样,就机器不均衡了,大量请求会落在ip1,而且对于只有几台机器来说映射成这样是极有可能的,解决办法是加机器,但是机器很贵,几十台也不一定可以映射均衡,这就衍生出虚拟机器的概念
这样就让集群重新分布均衡
code
public class ConsistentHash {
//服务器列表
private static List<String> servers = new ArrayList();
//虚拟节点
private static TreeMap<Integer,String> VNode = new TreeMap();
//为每一个真实节点加的虚拟节点个数
private static int V_NODE = 160;
static{
servers.add("192.168.0.1");
servers.add("192.168.0.2");
servers.add("192.168.0.3");
servers.add("192.168.0.4");
//为每一个节点添加虚拟节点
for(String ip:servers){
for(int i=0;i<V_NODE;i++){
VNode.put(getHash(ip+"VN"+i),ip);
}
}
}
private static String getServer(String clientIp){
int hash = getHash(clientIp);
//拿到比hash大的第一个节点
Integer nodeIndex = VNode.tailMap(hash).firstKey();
//节点不存在
if(null==nodeIndex){
//拿所有节点第一个
nodeIndex = VNode.firstKey();
}
return VNode.get(nodeIndex);
}
private static int getHash(String str) {
final int p = 16777619;
int hash = (int) 2166136261L;
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++){
System.out.println(getServer("client" + i));
}
}
}
公共代码
public class Server {
private String ip;
private int weight;
private int callNum;//被调用次数
private int callOK;
private int callFail;
private double longestResponseTime;
private double shortestResponseTime;
private double averageResponseTime;
public Server(){}
public Server(String ip){
this.ip = ip;
}
public Server(String ip,int weight){
this.ip = ip;
this.weight = weight;
}
//get set toString 省略
public class WeightServer {
public static List<Server> servers = new ArrayList();
static {
servers.add(new Server("192.168.0.1",3));
servers.add(new Server("192.168.0.2",7));
servers.add(new Server("192.168.0.3",6));
servers.add(new Server("192.168.0.4",4));
servers.add(new Server("192.168.0.5",10));
}
}