随笔|六种常见负载均衡算法图解

122 阅读7分钟

负载均衡是指将客户端的请求分布到多台服务器上进行处理,从而有效地提高系统的性能、可用性和可扩展性。

常见的负载均衡算法有:Round-Robin法、Weighted Round-Robin法、Random法、Weighted Random法、Source IP Hash法、Least Connections法等。

下面将一一进行介绍。

Round-Robin 循环

如图所示:

第一个请求先发送给第一台服务器A; 第二个请求先发送给下一台服务器,也就是第二台服务器B; 第三个请求先发送给再后面的服务器,也就是第三台服务器C; 对于第四个请求,由于三台服务器都已经依次发送过一次请求,所以需要从头开始,先发送给第一台服务器A;

以此类推...

该算法的优点是实现简单,可靠性高,但是没有考虑服务器的实际负载情况,可能造成部分服务器负载过大,而其他服务器处于空闲状态。

下面是Round Robin方法的简单实现代码,大家可以大概了解一下:

public  class  RoundRobinDemo { 

    // 定义一个全局计数器,每次调用时都会递增。
    private  static AtomicInteger index = new AtomicInteger( -1 ); 
    // 定义服务器列表。
    private  static List<String> serverList = new ArrayList<>(); 

    public  static String roundRobin () { 
        // 获取服务器数量。
        int serverCount = serverList.size(); 
        // 确定当前请求应该转发到哪个服务器。
        int currentServerIndex = index.incrementAndGet() % serverCount; 
        //返回相应的服务器地址。         
        return serverList.get(currentServerIndex);
    } 
         
    public static void main ( String[] args ) { 
        serverList.add( "服务器A" ); 
        serverList.add( "服务器B" ); 
        serverList.add ( "服务器C" ); 
        System.out.println ( roundRobin () ); 
        System.out.println ( roundRobin () ); 
        System.out.println ( roundRobin ()   );         
        System.out.println ( roundRobin ()  );         
        System.println (roundRobin());     
        } 
    }

输出:

服务器 A
服务器B
服务器 C
服务器A

Weighted Round-Robin 加权轮询

加权轮询法是在轮询法基础上的改进,其思想是在服务器选择时根据服务器的处理能力或者负载情况赋予不同的权重,使得处理能力强或者负载较轻的服务器能够获得更多的请求。

如下图所示,服务A、B、C的权重分别为4、3、1,那么服务A会接收并处理更多的请求, 可以看出前三个请求被路由到了服务A,而第四个请求被路由到了服务B:

该算法的优点是可以根据服务器的实际负载情况进行请求分配,但是由于该算法只是按照权重值进行分配,没有考虑服务器的实际负载情况,因此仍然存在服务器负载不均衡的问题。

public  class  WeightRoundRobinDemo { 

    // 定义一个全局计数器,每次调用时都会递增。
    private  static  AtomicInteger atomicInteger = new  AtomicInteger ( 0 ); 
    // 定义服务器列表及其对应的权重值。
    private  static  Map < String , Integer > serverMap = new TreeMap  < >(); 
    // 记录所有服务器的总权重。
    private  static int totalWeight = 0 ; 

    public  static  void  main ( String [] args ) { 
        serverMap.put ( "服务器 A" , 4 );         
        serverMap.put ( "服务器B" , 3 );         
        serverMap.put ( "服务器 C" , 1 ); 
        // 计算所有服务器的总权重。
        for ( Map . Entry < String , Integer > entry : serverMap.entrySet ( )) {             
            totalWeight += entry.getValue ( ) ;         
       
         } 
       // 模拟四个请求。
            System . out . println ( weightRoundRobin ()); 
            System . out.println ( weightRoundRobin ( )); 
            System.out.println ( weightRoundRobin ( )); 
            System.out.println ( weightRoundRobin ( ));     
     } 
     public static String weightRoundRobin ( ) { //获取服务器数量。         int serverCount = serverMap.size ( ) ; //如果没有可用的服务器,则返回null。 if ( serverCount == 0 ) { return null ; }         //为了避免多线程环境下并发操作导致的错误,请在方法内部进行加锁操作。         
         synchronized (serverMap) {
            // 确定当前请求应该转发到哪个服务器。
             int currentServerIndex = atomicInteger.incrementAndGet ( ) % totalWeight; 

            // 遍历服务器列表,根据服务器权重值选择对应地址。
            for ( Map . Entry < String , Integer > entry : serverMap.entrySet ()) { 
                String serverAddress = entry.getKey (); 
                Integer weight = entry.getValue ( );                 
                currentServerIndex -= weight; 
                if ( currentServerIndex < 0 ) { 
                    return serverAddress;                 
                }             
            }        
          } 
          `return null ;     
       }
    }
                

输出:

服务器 A
服务器A
服务器A
服务器B

Random 随机

Random 方式是一种将请求随机分配到后端服务器的负载均衡算法,该算法实现简单,但分配效果不可控,且难以保证后端服务器的负载均衡,因此通常在测试或者压测等临时场景下使用 Random 方式作为负载均衡算法。

如下图所示: 第一个请求随机分配给服务器A; 第二、第四个请求随机分配给服务器C; 第三个请求随机分配给服务器B;

实现随机化方法的代码如下:

// 定义服务器列表。
private  static List<String> serverList = new ArrayList<>(); 

public  static String random () { 
    // 获取服务器数量。
    int serverCount = serverList.size(); 

    // 如果没有可用的服务器,则返回 null。
    if (serverCount == 0 ) { 
        return  null ; 
    } 

    // 生成随机数。
    int randomIndex = new Random().nextInt(serverCount); 

    // 返回对应的服务器地址。
    return serverList.get ( randomIndex ); 
}

Weighted Random 加权随机

加权随机方法是在随机方法基础上的一种改进,其思想是在服务器选择时根据服务器的处理能力或者负载情况赋予不同的权重,使得处理能力强或者负载较轻的服务器获得更多的请求。

加权随机方法的实现代码如下:

// 定义服务器列表和它们对应的权重值。
private  static  Map < String , Integer > serverMap = new  ConcurrentHashMap <>(); 

public  static  String  weightRandom ( ) { 
    // 获取服务器数量。
     int serverCount = serverMap.size ( ); 

    // 如果没有可用的服务器,则返回 null。
    if (serverCount == 0 ) { 
        return  null ; 
    } 

    // 为了避免多线程环境下并发操作导致的错误,可以在方法内部进行加锁操作。
     synchronized (serverMap) { 
        // 计算所有服务器的总权重。
         int totalWeight = 0 ; 
        for ( Map . Entry < String , Integer > entry : serverMap.entrySet ()) { 
            totalWeight += entry.getValue ( ); 
        } 

        // 生成随机数。
         int randomWeight = new  Random (). nextInt (totalWeight); 

        // 遍历服务器列表,根据服务器权重值选择对应地址。
        for ( Map . Entry < String , Integer > entry : serverMap. entrySet ()) { 
            String serverAddress = entry. getKey (); 
            Integer weight = entry. getValue (); 
            randomWeight -= weight; 
            if (randomWeight < 0 ) { 
                return serverAddress; 
            } 
        } 
    } 

    // 默认返回 null。
    return  null ; 
}

Source IP Hash(Hash)

Source IP Hash 法是一种基于请求源 IP 地址的负载均衡算法,其思想是通过哈希函数对每个请求的源 IP 地址计算一个值,然后根据这个值和可用服务器总数取模的结果,决定将请求转发到哪个服务器。

换句话说,Source IP Hash算法使用客户端IP地址作为哈希键。负载均衡器将哈希值映射到其中一个可用的服务器,然后将请求发送到该服务器进行处理。如果客户端IP地址发生变化(例如,在重启后重新分配了新的IP地址),则会将其分配给另一台服务器。

如下图所示: 来自ClientA的所有请求都分配给ServerA; 来自ClientA的所有请求都分配给ServerC;

这种算法的优点是可以避免某些客户端被重定向到不同的服务器,来自同一个 IP 地址的请求总会被分配到同一个服务器,因此可以在一定程度上提高缓存命中率等性能指标。但是它也存在一些缺点,比如如果有很多请求来自同一个 IP 地址,可能会导致某台服务器负载过大。

另外,由于服务器数量的变化,哈希值映射也会发生变化,这可能会导致缓存失效,需要重新分配所有请求。

Source IP Hash方法的实现代码示例如下

// 定义服务器列表。
private  static List<String> serverList = new ArrayList<>(); 

public  static String hash ( String clientIP ) { 
    // 获取服务器数量。
    int serverCount = serverList.size(); 

    // 如果没有可用的服务器,则返回 null。
    if (serverCount == 0 ) { 
        return  null ; 
    } 

    // 计算客户端 IP 地址的哈希码。
    int hashCode = clientIP.hashCode(); 

    // 根据哈希码确定应该将请求转发到哪个服务器。
    int serverIndex = hashCode % serverCount; 

    // 返回对应的服务器地址。
    return serverList. get (serverIndex); 
}

最少连接法(Least Connections)

最少连接法是一种动态调整的负载均衡算法,其思想是将请求尽可能地分配给当前空闲连接数最少的后端服务器,以达到负载均衡的效果。在实现过程中通常需要定期检测各服务器的连接数,并进行动态调整。

如下图所示: 由于服务C的连接数当前最小,所以所有的请求都会分配给它。

最少连接方法的实现代码示例如下:

// 定义服务器列表。
 private static  List < String > serverList = new ArrayList<>(); 

// 记录每个服务器的连接数。
 private static  Map < String , Integer> connectionsMap = new ConcurrentHashMap<>(); 

public static  String leastConnections() { 
    // 获取服务器数量。
    int serverCount = serverList.size(); 

    // 如果没有可用的服务器,则返回 null。
    if (serverCount == 0 ) { 
        return  null ; 
    } 

    // 默认选择第一台服务器。String 
    selectedServerAddress = serverList.get ( 0 ); 
    // 获取第一台服务器的连接数。
    int minConnections = connectionsMap.getOrDefault(selectedServerAddress, 0 ); 
    // 遍历服务器列表,找出连接数最少的服务器。
    for ( int i = 1 ; i < serverCount; i++) { 
        String serverAddress = serverList.get ( i); 
        int connections = connectionsMap.getOrDefault(serverAddress, 0 ); 
        if (connections < minConnections) {             
            selectedServerAddress = serverAddress;
            minConnections = connections;
                     
         }
     } 
     // 返回连接数最少的服务器地址。
     return selectedServerAddress; 
   }

需要注意的是,如果某个服务器宕机或者网络链路中断,负载均衡器将需要重新计算服务器的连接数,这会延长响应时间,影响性能。

公众号《javascript高级程序设计》

如果文章对你有帮助的话,请点赞👏并关注,谢谢!╰(°▽°)╯