首先给大家介绍下什么是负载均衡(来自百科)
负载均衡 建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和 服务器的带宽、增加 吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
负载均衡,英文名称为Load Balance,其意思就是分摊到多个操作单元上进行执行,例如Web 服务器、 FTP服务器、 企业关键应用服务器和其它关键任务服务器等,从而共同完成工作任务。
本文讲述的是"将外部发送来的请求均匀分配到对称结构中的某一台服务器上"的各种算法,并以Java代码演示每种算法的具体实现,OK,下面进入正题,在进入正题前,先写一个类来模拟Ip列表:
- import java.util.HashMap;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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);
- }
- }
import java.util.HashMap; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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)法
轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开始,直到N(内部服务器个数),然后重新开始循环。算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
其代码实现大致如下:
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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;
- }
- }
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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)法
通过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由概率统计理论可以得知,随着客户端调用服务端的次数增多,
其实际效果越来越接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
随机法的代码实现大致如下:
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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);
- }
- }
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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地址,通过哈希函数计算得到的一个数值,用该数值对服务器列表的大小进行取模运算,得到的结果便是客服端要访问服务器的序号。采用源地址哈希法进行负载均衡,同一IP地址的客户端,当后端服务器列表不变时,它每次都会映射到同一台后端服务器进行访问。
源地址哈希算法的代码实现大致如下:
- import java.util.ArrayList;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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);
- }
- }
import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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)法
不同的后端服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,降低其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权轮询法的代码实现大致如下:
- import java.util.*;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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;
- }
- }
import java.util.*; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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)法
与加权轮询法一样,加权随机法也根据后端机器的配置,系统的负载分配不同的权重。不同的是,它是按照权重随机请求后端服务器,而非顺序。
- import java.util.*;
- /**
- * @author ashang.peng@aliyun.com
- * @date 二月 07, 2017
- */
- 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);
- }
- }
import java.util.*; /** * @author ashang.peng@aliyun.com * @date 二月 07, 2017 */ 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)法
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的连接情况,动态地选取其中当前
积压连接数最少的一台服务器来处理当前的请求,尽可能地提高后端服务的利用效率,将负责合理地分流到每一台服务器。
前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,当然这么做是没错的,可以为后端的多台服务器平均分配工作量,最大程度地提高服务器的利用率,但是实际情况是否真的如此?实际情况中,请求次数的均衡真的能代表负载的均衡吗?这是一个值得思考的问题。
上面的问题,再换一个角度来说就是:以后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小连接数法便属于此类。
最小连接数算法比较灵活和智能,由于后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理地分流到每一台机器。由于最小连接数设计服务器连接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。
http://www.iteye.com/news/32119
相关推荐
Ribbon 是 Netflix 提供的客户端负载均衡器,它作为一个客户端的负载均衡中间件,实现了基于 Java 的客户端负载均衡算法。Ribbon 默认集成了 Eureka,可以从 Eureka Server 获取服务列表并进行负载均衡。Ribbon 提供...
Ribbon 内置了多种负载均衡算法,如轮询、随机等。 2. **Ribbon 的组件** - `IRule`: 负载均衡规则接口,定义了如何选择服务器的方法。 - `ServerList`: 服务器列表,用于存储可用的服务实例。 - `...
### 基于Java的网格计算框架及其实现 #### 一、引言 随着信息技术的飞速发展,网格计算作为一种新型的分布式计算模式,在处理大规模科学计算与工程计算问题时展现出巨大的潜力。本文旨在探讨一种基于Java语言的...
常见的负载均衡算法有轮询、随机、最少连接数等。 **分布式缓存** 分布式缓存如Redis、Memcached,用于存储热数据,提高数据访问速度,减轻数据库压力。它们通常支持多种数据结构,如字符串、哈希、列表、集合等,...
- **负载均衡和集群技术**:如Nginx、HAProxy等,分发请求到不同的服务器节点,提高系统的处理能力。 #### 五、拓展知识 **1. Java中的多线程实现方式有哪些?** Java中实现多线程的方式主要有两种: - **实现...
- **同步机制**:Java提供了多种同步机制,包括synchronized关键字、Lock接口及其实现类ReentrantLock、Condition等。 - **线程池**:线程池是管理一组线程的有效方式,有助于减少线程创建和销毁的成本。常见的...
1. **国密算法实现**:Java代码会实现SM2非对称加密算法,用于生成和验证数字签名;SM3哈希算法,用于计算交易信息的摘要;以及可能的SM4对称加密算法,用于保护敏感数据的安全传输。 2. **签名生成**:服务器接收...
常见的负载均衡算法有轮询、随机、最少连接数等。\n\n7. **异常处理和重试机制**:在RPC调用中,可能会遇到网络问题、服务异常等情况,因此需要设计合理的异常处理和重试机制,保证服务的稳定性和可靠性。\n\n8. **...
源码中的`LoadBalance`接口定义了选择服务提供者的方法,实际的负载均衡算法则在对应的实现类中实现,如`RandomLoadBalance`。 五、容错与重试机制 Dubbox提供了Failover、Failfast、Failsafe、Fallback等多种失败...
封装通过访问修饰符实现数据保护,继承使得代码复用,多态提供灵活的接口。 - **构造器**:用于初始化类的实例,与类名相同且无返回值。 - **抽象类与接口**:抽象类可以有非抽象方法,而接口仅包含抽象方法。一个...
- **负载均衡**:分发请求到多个服务器,减轻单个服务器的压力。 - **缓存策略**:利用缓存技术减少数据库的负担。 - **数据库分片**:将数据分散到多个物理数据库中,提高查询效率。 ##### 2.6 DataSift创建了...
- **分层系统**:允许客户端和服务端之间存在中间层,实现负载均衡等功能。 ### 4. Hessian 的作用及其传输单位 Hessian 是一种轻量级的远程过程调用(RPC)协议,主要用于实现分布式应用间的通信。它采用二进制...
- **一致性Hash及其原理**:用于分布式系统中的负载均衡。 - **排序算法**:快速排序、堆排序等。 - **网络/IO基础:** - **BIO、NIO、AIO**:阻塞I/O、非阻塞I/O、异步I/O。 - **长连接和短连接**:长连接建立...
- 学习如何通过缓存、负载均衡等手段提高系统响应速度。 - 掌握单元测试、集成测试等方法,确保应用质量。 12. **实战案例分析**: - 提供多个实际项目案例,涵盖电子商务、博客系统等多种类型的应用。 - 通过...
- **高可用集群**:学习如何搭建Redis集群,实现高可用和负载均衡。 ##### 01-准备篇 - **环境搭建**:指导学习者完成开发环境的配置,包括IDE选择、版本控制工具等。 - **学习资源推荐**:分享高质量的学习资料和...
- 支持智能路由策略和负载均衡算法; - 动态配置服务和透明化服务调用; - 服务降级、限流等功能。 #### 二、视频教程内容概览 - **基础知识**:包括Dubbo的基本概念介绍、安装部署指导等。 - **核心功能详解**...
- 负载均衡:Nginx、HAProxy等负载均衡技术的原理和配置。 8. **其他** - 架构设计:微服务架构、SOA、MVC等架构模式的理解。 - 容器技术:Docker容器化部署,Kubernetes集群管理。 - 代码质量:遵循编码规范,...
答案:Spring Cloud 是一套微服务解决方案,提供服务注册与发现、配置中心、熔断器、路由、负载均衡等功能,适用于大型复杂微服务系统。而 Dubbo 是一个高性能、轻量级的 RPC 框架,侧重于服务治理,适用于中型项目...
#### 八、Nginx实现负载均衡配置 **8.1 Nginx简介** - **功能**: 反向代理服务器、负载均衡、HTTP缓存等功能。 - **优势**: 性能高、稳定性好、易于配置。 **8.2 负载均衡配置** - **轮询**: 按顺序分配请求。 -...