一.负载均衡介绍
1.什么是负载均衡?
用户->负载均衡平衡器->多台服务器(一个集群下) 负载均衡就是负载均衡器中实现的算法或者硬件设施.同时解决大量并发访问服务器问题.
2.负载均衡的方式?
软件负载均衡:Nginx.LVS,HAProxy
硬件负载均衡:Arrays,F5
3.负载均衡算法
//正常分配
public static final List<String> LIST = Arrays.asList(
"192.168.0.1",
"192.168.0.2",
"192.168.0.3",
"192.168.0.4",
"192.168.0.5",
"192.168.0.6",
"192.168.0.7",
"192.168.0.8",
"192.168.0.9",
"192.168.0.10" //这一台性能高
);
//考虑权重
public static final Map<String,Integer> WEIGHT_MAP = new LinkedHashMap<>();
static {
WEIGHT_MAP.put("192.168.0.1",2);
WEIGHT_MAP.put("192.168.0.2",8);
WEIGHT_MAP.put("192.168.0.3",1);
WEIGHT_MAP.put("192.168.0.4",9);
WEIGHT_MAP.put("192.168.0.5",4);
WEIGHT_MAP.put("192.168.0.6",6);
}
3.1随机算法
1.正常随机
public static String getServer() {
Random random = new Random();
return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));s
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(getServer());s
}
}
2.ip + 权重系数
public static String getServer() {
List<String> ips = new ArrayList<>();
ClentTest.WEIGHT_MAP.keySet().stream().forEach(ip -> {
Integer weight = ClentTest.WEIGHT_MAP.get(ip);
for (int i = 0; i < weight; i++) {
ips.add(ip);
}
});
Random random = new Random();
return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
3.随机分配理解和改进
A服务器权重系数为5 也是服务器调用的次数
B 为 3
C 为 2
看作数组为[5,3,2],看作定点长 5+3+2 = 10 权重总和
0-----5—8–10
A B C 服务器所在位置
1.假设一个随机数offest = 7;随机0-5则取A,5-8则取B,8-10则取C,但是0-5的距离长所以占权重也多.
2.因为随机是7所以定点落在A-B上.
offest > 5; offest = offest - 5; offest = 2;
定点坐标改变为 0—3–5 :因为减去了5
offest < 3;return B; 减去的步骤是将A服务器排除,同时offest一定要比服务器数值小.
public static String getServer() {
//流计算整数和
int totalWeight = ClentTest.WEIGHT_MAP.values().stream().mapToInt(weight -> weight).sum();
//System.out.println(totalWeight);
int offest = new Random().nextInt(totalWeight);
for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
Integer weight = ClentTest.WEIGHT_MAP.get(ip);
if (offest < weight) {
return ip;
}
offest = offest - weight;
}
Random random = new Random();
return ClentTest.LIST.get(random.nextInt(ClentTest.LIST.size()));
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println(getServer());
}
}
3.2轮询算法
1.正常实现
private static Integer pos = 0; //轮询下标
public static String getServer() {
if (pos >= ClentTest.LIST.size()) {
pos = 0;
}
String ip = ClentTest.LIST.get(pos);
pos ++;
return ip;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println(getServer());
}
}
2.考虑权重去实现
假设offest = 1,2,3,4,5,…有规律的递增的轮询,也可以通过上述实现(递增的范围跟服务器的权重总和有关)
A服务器权重系数为5 也是服务器调用的次数
B 为 3
C 为 2
offest > 5; offest = offest - 5; offest = 2;
offest < 3;return B;
则出现: AAAAABBBCC
同时我们可以得出上述逻辑根据offest的结果不同则产生的影响也不同,在轮询随机中都可以采用这种算法
我们实现轮询可以根据requestID(主键递增的),如果ID到达1000,1001…
//requestID的生成
public static Integer num = 0;
public static Integer getAndIncrement() {
return num++;
}
1.我们采用%实现
public static String getServer() {
int totalWeight = 0;
for (Integer weight : ClentTest.WEIGHT_MAP.values()) {
totalWeight += weight;
}
int requestID = RequestID.getAndIncrement(); //0--10000
int offset = requestID % totalWeight;
for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
Integer weight = ClentTest.WEIGHT_MAP.get(ip);
if (offset < weight) {
return ip;
}
offset = offset - weight;
}
return null;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
System.out.println(getServer());
}
}
这样实现产生问题:如果
A服务器权重系数为5 也是服务器调用的次数
B 为 1
C 为 1
则产生AAAAABC,但是采用平衡加权轮询算法则可以产生AABACAA,可以使得某时刻A服务器的压力不是那么大
问题:那么轮询之后还是会使得A服务器上压力过大?
2.平衡加权轮询算法(Nginx中默认采用的)
同时他的结果也很平滑 AABACAA
class Weight {
private String ip;
private Integer weight;
private Integer currentWeight;
}
/**
* 平衡加权轮询算法实现
*/
private static Map<String,Weight> weightMap = new LinkedHashMap<>();
public static String getServer() {
if (weightMap.isEmpty()) {
for (String ip : ClentTest.WEIGHT_MAP.keySet()) {
Integer weight = ClentTest.WEIGHT_MAP.get(ip);
weightMap.put(ip,new Weight(ip,weight,0));
}
}
//1.currentWeight += weight
for (Weight weight : weightMap.values()) {
weight.setCurrentWeight(weight.getCurrentWeight() + weight.getWeight());
}
//2.max(currentWeight)
Weight maxCurrentWeight = null;
for (Weight weight : weightMap.values()) {
if (maxCurrentWeight == null || weight.getCurrentWeight() >
maxCurrentWeight.getCurrentWeight()) {
maxCurrentWeight = weight;
}
}
int totalWeight = ClentTest.WEIGHT_MAP.values().stream().mapToInt(weight -> weight).sum();
//4.max(currentWeight) -= sum(weight);
maxCurrentWeight.setCurrentWeight(maxCurrentWeight.getCurrentWeight() - totalWeight);
//5.return
return maxCurrentWeight.getIp();
3.3一致哈希算法
- 问题:服务器集群中,用户访问分配给第一台服务器有用户的session,但是再次请求中分配给第二台没有的session,重新登陆的不同步问题?
- 解决:redis ,tomact ,一致hash算法
一致哈希算法:要保证同一个用户接下来的请求的负载均衡通过客户端的ip,或请求路径与请求参数产生hash值指向同一个服务器.
因为客户端发起的请求情况是无穷尽的(客户但地址不同,请求参数不同),所以对于的hash值也是无穷大的,所以我们不可能把所有的hash值都进行映射到服务器端ip上,所以这里需要用到hash环.
每一个IP对应的hashCode是存在环上的 ,同时呈现出来顺时针的走势,如果hash值在ip1到ip2之间则如图指向的是ip2作为结果.
- 问题:但是如果ip4挂掉了,那么请求将放到ip1上面,因为是顺时针.产生了不公平?
- 解决:需要加入虚拟节点,如:(是将请求平均分配了下)
其中ip2-1,ip3-1就是虚拟节点,并不能处理节点,而是等同于对应的ip2和ip3服务器.
实际上,这只是处理这种不均衡性的一种思路,实际上就算哈希环本身是均衡的,你也可以增加更多的虚拟节点来使这个环更加平滑,比如:
这个彩虹环也是"公平的",并且只有ip 1,2,3,4使实际的服务器ip,其他的都是虚拟IP;
步骤:
1.> hashCode,虚拟节点
2.采用treemap实现有序
private static TreeMap<Integer,String> treeMap = new TreeMap<>();
private static final int VIRTUAL_NODES = 160; //虚拟节点
static {
for (String ip : ClentTest.LIST) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
int hash = getHash(ip + i);
treeMap.put(hash,ip);
}
}
}
public static String getServer(String client) {
int hash = getHash(client);
//找大于hash,treeMap的子树的第一个firstKey
SortedMap<Integer, String> subMap = treeMap.tailMap(hash);
Integer firstKey = null;
if (subMap == null) {
firstKey = treeMap.firstKey();
}else {
firstKey = subMap.firstKey();
}
return treeMap.get(firstKey);
}
//计算hash
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 < 12; i++) {
System.out.println(getServer("client" + i));
}
}