`

几种简单的负载均衡算法及其Java代码实现

 
阅读更多

什么是负载均衡

负载均衡,英文 名称为Load Balance,指由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无须其他服务器的辅助。通过某种 负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求。负载均衡能够平均分配客户请求到服 务器阵列,借此提供快速获取重要数据,解决大量并发访问服务问题,这种集群技术可以用最少的投资获得接近于大型主机的性能。

负载均衡分为软件负载均衡和硬件负载均衡,前者的代表是阿里章文嵩博士研发的LVS,后者则是均衡服务器比如F5,当然这只是提一下,不是重点。

本文讲述的是"将外部发送来的请求均匀分配到对称结构中的某一台服务器上"的各种算法,并以Java代码演示每种算法的具体实现,OK,下面进入正题,在进入正题前,先写一个类来模拟Ip列表:

 

 

public class IpMap
{
    // 待路由的Ip列表,Key代表Ip,Value代表该Ip的权重
    public static HashMap<String, Integer> serverWeightMap = 
            new HashMap<String, Integer>();
    
    static
    {
        serverWeightMap.put("192.168.1.100", 1);
        serverWeightMap.put("192.168.1.101", 1);
        // 权重为4
        serverWeightMap.put("192.168.1.102", 4);
        serverWeightMap.put("192.168.1.103", 1);
        serverWeightMap.put("192.168.1.104", 1);
        // 权重为3
        serverWeightMap.put("192.168.1.105", 3);
        serverWeightMap.put("192.168.1.106", 1);
        // 权重为2
        serverWeightMap.put("192.168.1.107", 2);
        serverWeightMap.put("192.168.1.108", 1);
        serverWeightMap.put("192.168.1.109", 1);
        serverWeightMap.put("192.168.1.110", 1);
    }
}

 

 

轮询(Round Robin)法

轮询法即Round Robin法,其代码实现大致如下:

public class RoundRobin
{
    private static Integer pos = 0;
    
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap = 
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);
        
        // 取得Ip地址List
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);
        
        String server = null;
        synchronized (pos)
        {
            if (pos > keySet.size())
                pos = 0;
            server = keyList.get(pos);
            pos ++;
        }
        
        return server;
    }
}

 

由于serverWeightMap中的地址列表是动态的,随时可能有机器上线、 下线或者宕机,因此为了避免可能出现的并发问题,方法内部要新建局部变量serverMap,现将serverMap中的内容复制到线程本地,以避免被多 个线程修改。这样可能会引入新的问题,复制以后serverWeightMap的修改无法反映给serverMap,也就是说这一轮选择服务器的过程中, 新增服务器或者下线服务器,负载均衡算法将无法获知。新增无所谓,如果有服务器下线或者宕机,那么可能会访问到不存在的地址。因此,服务调用端需要有相应的容错处理,比如重新发起一次server选择并调用

对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,需要在操作时对其加锁,使得同一时刻只能有一个线程可以修改pos的值否则当pos变量被并发修改,则无法保证服务器选择的顺序性,甚至有可能导致keyList数组越界。

轮询法的优点在于:试图做到请求转移的绝对均衡

轮询法的缺点在于:为了做到请求转移的绝对均衡,必须付出相当大的代价,因为为了保证pos变量修改的互斥性,需要引入重量级的悲观锁synchronized,这将会导致该段轮询代码的并发吞吐量发生明显的下降

 

随机(Random)法

通过系统随机函数,根据后端服务器列表的大小值来随机选择其中一台进行访问。由概率统计理论可以得知,随着调用量的增大,其实际效果越来越接近于平均分配流量到每一台后端服务器,也就是轮询的效果。

随机法的代码实现大致如下

 

public class Random
{
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap = 
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);
        
        // 取得Ip地址List
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);
        
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(keyList.size());
        
        return keyList.get(randomPos);
    }
}

 

整体代码思路和轮询法一致,先重建serverMap,再获取到server列表。在选取server的时候,通过Random的nextInt方法取0~keyList.size()区间的一个随机值,从而从服务器列表中随机获取到一台服务器地址进行返回。基于概率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果

 

 

 

源地址哈希(Hash)法

 

源地址哈希的思想是获取客户端访问的IP地址值,通过哈希函数计算得到一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的序号。源地址哈希算法的代码实现大致如下:

 

 

 

 

public class Hash
{
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap = 
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);
        
        // 取得Ip地址List
        Set<String> keySet = serverMap.keySet();
        ArrayList<String> keyList = new ArrayList<String>();
        keyList.addAll(keySet);
        
        // 在Web应用中可通过HttpServlet的getRemoteIp方法获取
        String remoteIp = "127.0.0.1";
        int hashCode = remoteIp.hashCode();
        int serverListSize = keyList.size();
        int serverPos = hashCode % serverListSize;
        
        return keyList.get(serverPos);
    }
}

 

 

 

 

前两部分和轮询法、随机法一样就不说了,差别在于路由选择部分。通过客户端的ip也就是remoteIp,取得它的Hash值,对服务器列表的大小取模,结果便是选用的服务器在服务器列表中的索引值。

源地址哈希法的优点在于:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变更。根据此特性可以在服务消费者与服务提供者之间建立有状态的session会话

源地址哈希算法的缺点在于:除非集群中服务器的非常稳定,基本不会上下线,否则一旦有服务器上线、下线,那么通过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的概率非常低,如果是session则取不到session,如果是缓存则可能引发"雪崩"如果这么解释不适合明白,可以看我之前的一篇文章MemCache超详细解读,一致性Hash算法部分。

 

 

加权轮询(Weight Round Robin)法

不同的服务器可能机器配置和当前系统的负载并不相同,因此它们的抗压能力也不尽相 同,给配置高、负载低的机器配置更高的权重,让其处理更多的请求,而低配置、高负载的机器,则给其分配较低的权重,降低其系统负载。加权轮询法可以很好地 处理这一问题,并将请求顺序按照权重分配到后端。加权轮询法的代码实现大致如下:

 

 

public class WeightRoundRobin
{
    private static Integer pos;
    
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap = 
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);
        
        // 取得Ip地址List
        Set<String> keySet = serverMap.keySet();
        Iterator<String> iterator = keySet.iterator();
        
        List<String> serverList = new ArrayList<String>();
        while (iterator.hasNext())
        {
            String server = iterator.next();
            int weight = serverMap.get(server);
            for (int i = 0; i < weight; i++)
                serverList.add(server);
        }
        
        String server = null;
        synchronized (pos)
        {
            if (pos > keySet.size())
                pos = 0;
            server = serverList.get(pos);
            pos ++;
        }
        
        return server;
    }
}

 

 

与轮询法类似,只是在获取服务器地址之前增加了一段权重计算的代码,根据权重的大小,将地址重复地增加到服务器地址列表中,权重越大,该服务器每轮所获得的请求数量越多。

 

 

加权随机(Weight Random)法

与加权轮询法类似,加权随机法也是根据后端服务器不同的配置和负载情况来配置不同的权重。不同的是,它是按照权重来随机选择服务器的,而不是顺序。加权随机法的代码实现如下:

 

 

 

public class WeightRandom
{
    public static String getServer()
    {
        // 重建一个Map,避免服务器的上下线导致的并发问题
        Map<String, Integer> serverMap = 
                new HashMap<String, Integer>();
        serverMap.putAll(IpMap.serverWeightMap);
        
        // 取得Ip地址List
        Set<String> keySet = serverMap.keySet();
        Iterator<String> iterator = keySet.iterator();
        
        List<String> serverList = new ArrayList<String>();
        while (iterator.hasNext())
        {
            String server = iterator.next();
            int weight = serverMap.get(server);
            for (int i = 0; i < weight; i++)
                serverList.add(server);
        }
        
        java.util.Random random = new java.util.Random();
        int randomPos = random.nextInt(serverList.size());
        
        return serverList.get(randomPos);
    }
}

 

 

 

这段代码相当于是随机法和加权轮询法的结合,比较好理解,就不解释了。

 

 

 

最小连接数(Least Connections)法

 

前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。

 

 

 

 

 

 

上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。

最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有 快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载 合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    基于Java的网格计算框架及其实现

    ### 基于Java的网格计算框架及其实现 #### 一、引言 随着信息技术的飞速发展,网格计算作为一种新型的分布式计算模式,在处理大规模科学计算与工程计算问题时展现出巨大的潜力。本文旨在探讨一种基于Java语言的...

    Java面试中常见的面试问题及其答案

    Java中有哪几种访问修饰符?** Java中有四种访问修饰符,分别为: - **private**:表示私有成员,只能在当前类中访问。 - **default**(无修饰符):表示默认权限,即在同一个包内的类可以访问。 - **protected**...

    基于Java代码实现游戏服务器生成全局唯一ID的方法汇总

    以下是几种常见的Java实现全局唯一ID的方法及其优劣势: 1. **UUID**: - **优点**:UUID是通过特定算法生成的128位长的唯一标识,可以在本地生成,无需网络通信,生成速度快,全局唯一性得到保障。 - **缺点**:...

    DAG任务调度算法&优化现实_Java_下载.zip

    这可能涉及到负载均衡策略,如最小负载优先或者基于亲和性的策略,以最大化资源利用率和降低任务完成时间。 4. **并发控制**:在并行执行任务时,需要处理并发控制问题,防止数据竞争和死锁。Java的`synchronized`...

    java程序员需要掌握的知识点

    - **负载均衡与集群**:熟悉Nginx、HAProxy等负载均衡工具的配置与使用,了解集群部署的基本原理。 #### 7. 学习与持续发展 技术不断进步和发展,作为Java程序员,需要不断学习新知识以保持竞争力。这包括: - **...

    rpc-server.zip

    常见的负载均衡算法有轮询、随机、最少连接数等。\n\n7. **异常处理和重试机制**:在RPC调用中,可能会遇到网络问题、服务异常等情况,因此需要设计合理的异常处理和重试机制,保证服务的稳定性和可靠性。\n\n8. **...

    稍微有点难度的10道java面试题,你会几道?

    - **分层系统**:允许客户端和服务端之间存在中间层,实现负载均衡等功能。 ### 4. Hessian 的作用及其传输单位 Hessian 是一种轻量级的远程过程调用(RPC)协议,主要用于实现分布式应用间的通信。它采用二进制...

    Java面试资料大全

    13. **分布式系统**:CAP原理、微服务架构、消息队列(MQ)、负载均衡等。 14. **并发编程**:线程安全、并发工具类(Semaphore、CountDownLatch、CyclicBarrier等)的使用。 15. **性能调优**:代码优化技巧、...

    Java并发编程与高并发解决方案(高清视频教程).rar

    4. 负载均衡:利用Nginx或Apache等负载均衡器分配请求,避免单点压力过大。 5. 分布式ID生成:例如Twitter的Snowflake算法或美团的Leaf服务,解决全局唯一ID的问题。 6. 限流和熔断:Hystrix库提供了限流、降级和...

    java面试 初中高级适用 最详细全面 私人珍藏

    7. **分布式与并发**:分布式系统原理,CAP理论,分布式一致性算法(如Paxos、Raft),分布式锁,分布式缓存(如Redis、Memcached),负载均衡,消息队列(如RabbitMQ、Kafka)。 8. **性能优化**:代码性能调优,...

    Java后端技术面试基础汇总

    - **一致性Hash及其原理**:用于分布式系统中的负载均衡。 - **排序算法**:快速排序、堆排序等。 - **网络/IO基础:** - **BIO、NIO、AIO**:阻塞I/O、非阻塞I/O、异步I/O。 - **长连接和短连接**:长连接建立...

    互联网高频Java后端面试题20道(适合1~3年)V1.0.59.docx

    答案:Java 提供了几种同步机制,包括 synchronized 关键字、Lock 接口(如 ReentrantLock)、Semaphore 和 CountDownLatch。synchronized 关键字简单易用,但可能导致整个方法或代码块锁定,性能稍低。Lock 接口...

    2021年JAVA核心知识点整理.rar

    4. **服务治理**:熔断、降级、负载均衡、健康检查等。 **Netty与RPC** Netty是高性能的异步事件驱动的网络应用框架,而RPC(远程过程调用)用于服务间的通信: 1. **Netty**:了解它的NIO模型,以及Channel、...

    2023最新版Java学习路线图-第6阶段大厂面试专题

    - **垃圾回收机制**:探讨JVM的几种垃圾回收算法,如标记-清除算法、复制算法等,并了解不同GC策略的选择依据。 ##### 08-并发编程篇 - **线程安全**:分析线程安全问题产生的原因及解决办法,掌握synchronized、...

    互联网高频Java后端面试题20道(适合1~3年)V1.0.78.docx

    答案:Spring Cloud 是一套微服务解决方案,提供服务注册与发现、配置中心、熔断器、路由、负载均衡等功能,适用于大型复杂微服务系统。而 Dubbo 是一个高性能、轻量级的 RPC 框架,侧重于服务治理,适用于中型项目...

    jAVA的设计模式 java学习资料

    在J2EE中,如负载均衡策略、缓存策略等,都可以通过策略模式实现。 学习和实践这些设计模式能帮助开发者更好地理解和设计复杂的J2EE系统。通过"j2ee-design-model-master"这个资源,你可以深入学习每个模式的实际...

    java面试要点

    微服务与SOA的区别,服务的拆分,数据库管理,链式调用异常的应对策略,前后端分离的实现,分布式事务和集群与负载均衡的算法与实现都是需要深入了解的内容。 安全问题是面试中的另一个重要方面。安全要素与STRIDE...

    java超有用的面试题目

    - **负载均衡算法**:轮询、随机、最少连接等。 - **Zookeeper**:用于分布式协调服务,实现配置管理、命名服务等功能。 - **数据拆分**:垂直拆分针对功能模块,水平拆分针对数据量。 - **分布式锁**:通过...

    1000道 互联网Java工程师面试题 485页_PDF密码解除.pdf

    MyBatis实现一对多有几种方式,怎么操作的?** - **嵌套查询方式:**通过一个独立的SQL查询来获取关联对象集合。 - **嵌套结果方式:**在同一个查询中同时获取主对象及其关联对象集合。 **22. Mybatis是否支持延迟...

Global site tag (gtag.js) - Google Analytics