首先给你们介绍下什么是负载均衡(来自百科)java
负载均衡 创建在现有网络结构之上,它提供了一种廉价有效透明的方法扩展 网络设备和 服务器的带宽、增长 吞吐量、增强网络数据处理能力、提升网络的灵活性和可用性。算法
负载均衡,英文名称为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); } }
Java缓存
轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从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 keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); String server = null; synchronized (pos) { // if (pos > keySet.size()) 謝謝@WO還在這兒的提醒, // 應該為**-1否則確實會出現IndexOutOfBoundsException if (pos > keySet.size()-1) pos = 0; server = keyList.get(pos); pos ++; } return server; } }
Javasession
因为serverWeightMap中的地址列表是动态的,随时可能有机器上线、下线或者宕机,所以为了不可能出现的并发问题,方法内部要新建局部变量serverMap,现将serverMap中的内容复制到线程本地,以免被多个线程修改。这样可能会引入新的问题,复制之后serverWeightMap的修改没法反映给serverMap,也就是说这一轮选择服务器的过程当中,新增服务器或者下线服务器,负载均衡算法将没法获知。新增无所谓,若是有服务器下线或者宕机,那么可能会访问到不存在的地址。所以,服务调用端须要有相应的容错处理,好比从新发起一次server选择并调用。并发
对于当前轮询的位置变量pos,为了保证服务器选择的顺序性,须要在操做时对其加锁,使得同一时刻只能有一个线程能够修改pos的值,不然当pos变量被并发修改,则没法保证服务器选择的顺序性,甚至有可能致使keyList数组越界。负载均衡
轮询法的优势在于:试图作到请求转移的绝对均衡。
轮询法的缺点在于:为了作到请求转移的绝对均衡,必须付出至关大的代价,由于为了保证pos变量修改的互斥性,须要引入重量级的悲观锁synchronized,这将会致使该段轮询代码的并发吞吐量发生明显的降低。
经过系统的随机算法,根据后端服务器的列表大小值来随机选取其中的一台服务器进行访问。由几率统计理论能够得知,随着客户端调用服务端的次数增多,
其实际效果愈来愈接近于平均分配调用量到后端的每一台服务器,也就是轮询的结果。
随机法的代码实现大体以下:
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 keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); keyList.addAll(keySet); java.util.Random random = new java.util.Random(); int randomPos = random.nextInt(keyList.size()); return keyList.get(randomPos); } }
Java
总体代码思路和轮询法一致,先重建serverMap,再获取到server列表。在选取server的时候,经过Random的nextInt方法取0~keyList.size()区间的一个随机值,从而从服务器列表中随机获取到一台服务器地址进行返回。基于几率统计的理论,吞吐量越大,随机算法的效果越接近于轮询算法的效果。
源地址哈希的思想是根据获取客户端的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 keySet = serverMap.keySet(); ArrayList keyList = new ArrayList(); 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); } }
Java
前两部分和轮询法、随机法同样就不说了,差异在于路由选择部分。经过客户端的ip也就是remoteIp,取得它的Hash值,对服务器列表的大小取模,结果即是选用的服务器在服务器列表中的索引值。
源地址哈希法的优势在于:保证了相同客户端IP地址将会被哈希到同一台后端服务器,直到后端服务器列表变动。根据此特性能够在服务消费者与服务提供者之间创建有状态的session会话。
源地址哈希算法的缺点在于:除非集群中服务器的很是稳定,基本不会上下线,不然一旦有服务器上线、下线,那么经过源地址哈希算法路由到的服务器是服务器上线、下线前路由到的服务器的几率很是低,若是是session则取不到session,若是是缓存则可能引起"雪崩"。若是这么解释不适合明白,能够看我以前的一篇文章MemCache超详细解读,一致性Hash算法部分。
不一样的后端服务器可能机器的配置和当前系统的负载并不相同,所以它们的抗压能力也不相同。给配置高、负载低的机器配置更高的权重,让其处理更多的请;而配置低、负载高的机器,给其分配较低的权重,下降其系统负载,加权轮询能很好地处理这一问题,并将请求顺序且按照权重分配到后端。加权轮询法的代码实现大体以下:
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 keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); 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()) 感謝網友“紫寒”的指正,確實须要訂正為“serverList”應爲此時serverList 大小和keySet大小是有可能不一致的。有可能就達不到權重的效果 if (pos > serverList.size()-1) pos = 0; server = serverList.get(pos); pos ++; } return server; } }
Java
与轮询法相似,只是在获取服务器地址以前增长了一段权重计算的代码,根据权重的大小,将地址重复地增长到服务器地址列表中,权重越大,该服务器每轮所得到的请求数量越多。
与加权轮询法同样,加权随机法也根据后端机器的配置,系统的负载分配不一样的权重。不一样的是,它是按照权重随机请求后端服务器,而非顺序。
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 keySet = serverMap.keySet(); Iterator iterator = keySet.iterator(); List serverList = new ArrayList(); 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); } }
Java
这段代码至关因而随机法和加权轮询法的结合,比较好理解,就不解释了。
最小链接数算法比较灵活和智能,因为后端服务器的配置不尽相同,对于请求的处理有快有慢,它是根据后端服务器当前的链接状况,动态地选取其中当前
积压链接数最少的一台服务器来处理当前的请求,尽量地提升后端服务的利用效率,将负责合理地分流到每一台服务器。
前面几种方法费尽心思来实现服务消费者请求次数分配的均衡,固然这么作是没错的,能够为后端的多台服务器平均分配工做量,最大程度地提升服务器的利用率,可是实际状况是否真的如此?实际状况中,请求次数的均衡真的能表明负载的均衡吗?这是一个值得思考的问题。
上面的问题,再换一个角度来讲就是:之后端服务器的视角来观察系统的负载,而非请求发起方来观察。最小链接数法便属于此类。
最小链接数算法比较灵活和智能,因为后端服务器的配置不尽相同,对于请求的处理有快有慢,它正是根据后端服务器当前的链接状况,动态地选取其中当前积压链接数最少的一台服务器来处理当前请求,尽量地提升后端服务器的利用效率,将负载合理地分流到每一台机器。因为最小链接数设计服务器链接数的汇总和感知,设计与实现较为繁琐,此处就不说它的实现了。
附了一个说明“NGINX的实现缘由,你们能够看看":