一、负载均衡维度

从负载均衡设备的角度来看,分为硬件负载均衡和软件负载均衡

  • 硬件负载均衡 - 比如最常见的F5,还有array等,这些负载均衡是商业的负载均衡器,性能比较好,背后有专业的团队维护,可提供各种解决方案,但是价格昂贵。
  • 软件负载均衡** - **nginx、lvs、tengine(阿里对nginx进行的改造)。

从负载均衡的技术角度来看,分为服务端负载均衡和客户端负载均衡

  • 服务端负载均衡 - 当我们访问一个服务,请求会先到另外一台服务器,然后这台服务器,会把请求分发到提供这个服务的服务器,如果有多台服务器,就需要根据一定的算法去选择其中的一台。

负载均衡 - 图1

  • 客户端负载均衡 - 客户端服务负载均衡的概念貌似是有了服务治理才产生的,简单的来说,就是在一台服务器上维护着所有服务的ip,名称等信息,当我们在代码中访问一个服务,是通过一个组件访问的,这个组件会从那台服务器上渠道所有提供这个服务的服务器信息,然后通过一定的算法,选择一台服务器进行请求。

负载均衡 - 图2

二、负载均衡算法

2.1、随机

2.1.1、普通随机

  1. public class Demo {
  2. public static List<String> serverList = new ArrayList<>();
  3. static {
  4. serverList.add("192.168.198.1");
  5. serverList.add("192.168.198.2");
  6. serverList.add("192.168.198.3");
  7. }
  8. public static String selectServer(){
  9. Random random = new Random();
  10. int index = random.nextInt(serverList.size());
  11. return serverList.get(index);
  12. }
  13. public static void main(String[] args){
  14. for (int i = 0; i < 10; i++) {
  15. System.out.println(selectServer());
  16. }
  17. }
  18. }

2.2.2、加权随机

加权随机,虽然采用的还是随机算法,但是每台机械设置了权重,权重大的服务器应获得的概率大,权重小的获得概率小。通常有以下两种实现方式

实现方式一

  1. public static List<String> serverList = new ArrayList<>();
  2. //192.168.198.1权重是2
  3. //192.168.198.2权重是1
  4. //192.168.198.2权重是3
  5. static {
  6. //权重是2添加两次
  7. serverList.add("192.168.198.1");
  8. serverList.add("192.168.198.1");
  9. serverList.add("192.168.198.2");
  10. //权重是三添加三次
  11. serverList.add("192.168.198.3");
  12. serverList.add("192.168.198.3");
  13. serverList.add("192.168.198.3");
  14. //在初始化数据之后,可以进行一次shuffle
  15. }
  16. public static String selectServer(){
  17. Random random = new Random();
  18. int index = random.nextInt(serverList.size());
  19. return serverList.get(index);
  20. }
  21. public static void main(String[] args){
  22. for (int i = 0; i < 10; i++) {
  23. System.out.println(selectServer());
  24. }
  25. }

此种实现方式,如果权重很大,会占用内存,所以可以用以下方法优化

实现方式二

假如服务器A权重是2,服务器B权重是7,服务器C权重是1

负载均衡 - 图3

  1. public class Demo {
  2. public static List<String> serverList = new ArrayList<>();
  3. static int weight_A = 2;
  4. static int weight_B = 7;
  5. static int weight_C = 1;
  6. static {
  7. serverList.add("192.168.198.1");
  8. serverList.add("192.168.198.2");
  9. serverList.add("192.168.198.3");
  10. }
  11. public static String selectServer(){
  12. int totalWeight = weight_A + weight_B + weight_C;
  13. Random random = new Random();
  14. int index = random.nextInt(totalWeight);
  15. String ip = null;
  16. if(index <= weight_A){
  17. ip = serverList.get(0);
  18. }else if(weight_A < index && index <= (weight_A + weight_B)){
  19. ip = serverList.get(1);
  20. }else {
  21. ip = serverList.get(2);
  22. }
  23. return ip;
  24. }
  25. public static void main(String[] args){
  26. for (int i = 0; i < 10; i++) {
  27. System.out.println(selectServer());
  28. }
  29. }
  30. }

注意:这种算法有严重的缺陷,会导致某些服务空闲,某些服务压力过大,没有均衡分配请求。

2.2、轮询

2.2.1、完全轮询

  1. public class Demo {
  2. public static List<String> serverList = new ArrayList<>();
  3. static int index = 0;
  4. static {
  5. serverList.add("192.168.198.1");
  6. serverList.add("192.168.198.2");
  7. serverList.add("192.168.198.3");
  8. }
  9. public static String selectServer(){
  10. if(index == serverList.size()){
  11. index = 0;
  12. }
  13. return serverList.get(index++);
  14. }
  15. public static void main(String[] args){
  16. for (int i = 0; i < 10; i++) {
  17. System.out.println(selectServer());
  18. }
  19. }
  20. }

2.2.2、加权轮询

  1. public class Demo {
  2. public static List<String> serverList = new ArrayList<>();
  3. static int index = 0;
  4. static int weight_a = 2;
  5. static int weight_b = 7;
  6. static int weight_c = 1;
  7. static int weight_index = 1;
  8. static {
  9. serverList.add("192.168.198.1");
  10. serverList.add("192.168.198.2");
  11. serverList.add("192.168.198.3");
  12. }
  13. public static String selectServer(){
  14. if(index == serverList.size()){
  15. index = 0;
  16. }
  17. String ip = null;
  18. if(weight_index <= weight_a){
  19. ip = serverList.get(0);
  20. }
  21. if(weight_index > weight_a && weight_index <= weight_a + weight_b){
  22. ip = serverList.get(1);
  23. }
  24. if(weight_index > weight_a + weight_b && weight_index <= weight_a + weight_b + weight_c){
  25. ip = serverList.get(2);
  26. }
  27. weight_index++;
  28. return ip;
  29. }
  30. public static void main(String[] args){
  31. for (int i = 0; i < 10; i++) {
  32. System.out.println(selectServer());
  33. }
  34. }
  35. }

2.2.3、平滑加权轮询

定义:A服务器的权重是5,B服务器的权重是1,C服务器的权重是1,请求会落在权重最大的机械上

固定权重为:5 :1:1,总权重 7 非固定权重:根据每次请求按照一定规则变动 第一次请求:固定权重5:1:1,则请求到A服务器,请求之后,非固定权重为 5-7 :1:1,及 -2:1:1 第二次请求:固定权重为3:2:2, 则请求到A服务器,请求之后,非固定权重为 3-7 :1:1,及 -4:1:1 第二次请求:固定权重为1:3:3, 则请求到A服务器,请求之后,非固定权重为 3-7 :1:1,及 -4:1:1 以此类推

负载均衡 - 图4

  1. public class Demo {
  2. public static List<String> serverList = new ArrayList<>();
  3. static int index = 0;
  4. static int weight_a = 5;
  5. static int weight_b = 1;
  6. static int weight_c = 1;
  7. static int weight_total = weight_a + weight_b + weight_c;
  8. static List<Integer> fixWeight = new ArrayList<>();
  9. static List<Integer> noFixWeight = new ArrayList<>();
  10. static {
  11. serverList.add("192.168.198.1");
  12. serverList.add("192.168.198.2");
  13. serverList.add("192.168.198.3");
  14. fixWeight.add(weight_a);
  15. fixWeight.add(weight_b);
  16. fixWeight.add(weight_c);
  17. noFixWeight.add(weight_a);
  18. noFixWeight.add(weight_b);
  19. noFixWeight.add(weight_c);
  20. }
  21. static int getMaxWeightIndex(List<Integer> weight ){
  22. int index = 0;
  23. int value = 0;
  24. for (int i = 0; i < weight.size(); i++) {
  25. if(weight.get(i) > value){
  26. value = weight.get(i);
  27. index = i;
  28. }
  29. }
  30. return index;
  31. }
  32. public static String selectServer(){
  33. int index = getMaxWeightIndex(fixWeight);
  34. int maxWeight = fixWeight.get(index);
  35. int currentWeight = maxWeight - weight_total;
  36. fixWeight.set(index, currentWeight);
  37. for (int i = 0; i < fixWeight.size(); i++) {
  38. fixWeight.set(i, noFixWeight.get(i) + fixWeight.get(i));
  39. }
  40. //System.out.println("固定权重:" + fixWeight.toString());
  41. return serverList.get(index);
  42. }
  43. public static void main(String[] args){
  44. Map<String, Integer> map = new HashMap<>();
  45. for (int i = 0; i < 70; i++) {
  46. String ip = selectServer();
  47. System.out.println(ip);
  48. if(map.get(ip) == null){
  49. map.put(ip, 1);
  50. }else {
  51. map.put(ip, map.get(ip) + 1);
  52. }
  53. }
  54. System.out.println(map.toString());
  55. }
  56. }
注意:平滑加权轮询,巧妙的利用了巧妙算法,既有轮询的效果,又避免了某台服务器压力突然升高

2.3、哈希

就是根据某个值生成一个哈希值,然后对应到某台服务器上去,如果用用户哈希的话,可以解决session共享问题,因为某个用户哈希后永远都在那台机械上(前提:不发生扩容,缩容的情况)

常用算法:哈希环(可用treemap实现),ABC为真实的节点,其他为虚拟节点。

负载均衡 - 图5

  1. private static String go(String client) {
  2. int nodeCount = 20;
  3. TreeMap<Integer, String> treeMap = new TreeMap();
  4. for (String s : new Servers().list) {
  5. for (int i = 0; i < nodeCount; i++)
  6. treeMap.put((s + "--服务器---" + i).hashCode(), s);
  7. }
  8. int clientHash = client.hashCode();
  9. SortedMap<Integer, String> subMap = treeMap.tailMap(clientHash);
  10. Integer firstHash;
  11. if (subMap.size() > 0) {
  12. firstHash = subMap.firstKey();
  13. } else {
  14. firstHash = treeMap.firstKey();
  15. }
  16. String s = treeMap.get(firstHash);
  17. return s;
  18. }

2.4、最小压力

最小压力算法是指:选择一台当前最悠闲的服务器,这种算法是比较科学的,但是该如何判断服务器最悠闲呢。