《大型分布式网站架构设计与实践》
对于负载较高的服务来说,往往对应着由多台服务器组成的集群。在请求到来时,为了将请求均衡的分配到后端服务器,负载均衡程序将从服务对应的地址列表中,通过响应的负载均衡算法和规则(rount_robin、random、weight_random),选取一台服务器进行访问,这个过程称为服务的负载均衡。
当服务的规模较小时,可以采用硬编码的方式将服务地址和配置写在代码中,通过编码的方式来解决服务的路由和负载均衡问题,也可以通过传统的硬件负载均衡设备,如F5,或者采用LVC或Nginx等软件解决方案,通过相关的配置,来解决服务的路由和负载均衡问题。
当服务越来越多,规模越来越大,对应的机器数量也越来越庞大,单靠人工来管理和维护服务及地址的配置信息,变得困难。并且,依赖单一的硬件负载均衡设备或者使用LVS、Nginx等软件方案进行路由和负载均衡调度,单点故障的问题也开始凸显出来。
因此,需要一个能够动态注册和获取服务信息的地方,来统一管理服务名称和其对应的服务器列表信息,称之为服务配置中心。服务提供者在启动时,将其提供的服务名称、服务器地址注册到服务配置中心。服务消费者通过服务配置中心来获得需要调用的服务,通过相应的负载均衡算法,选择其中一台服务器开始调用。这种无中心化的结构解决了之前负载均衡设备导致的单点故障。
基于ZooKeeper的持久和非持久节点,我们能够近乎实时的感知后端服务器的状态(上线、下线、宕机)。通过zab协议,使得服务配置信息能够保持一致。而Zookeeper本身的容错特性和leader选举机制,能保障我们方便的进行扩容。通过ZooKeeper来实现服务动态注册、机器上线与下线的动态感知,扩容方便、容错性好,且无中心化结构能够解决之前使用负载均衡设备带来的单点故障问题,只有但配置信息更新才会去ZooKeeper上获取最新的服务地址列表,其他时候可采用本地缓存。
负载均衡算法包括轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法。
将请求按顺序轮流的分配到后端服务器。
package loadbalancing;
import java.util.*;
/**
* Created by yangenneng on 2017-02-17.
* 功能说明:轮询法
*/
public class RoundRobinDemo {
static Map serverWeightMap=new HashMap(); // 服务器地址 权重
static {
// 服务器地址 权重
serverWeightMap.put("10.10.1.100",1);
serverWeightMap.put("10.10.1.101",1);
serverWeightMap.put("10.10.1.102",1);
serverWeightMap.put("10.10.1.103",4);
serverWeightMap.put("10.10.1.104",1);
serverWeightMap.put("10.10.1.105",1);
serverWeightMap.put("10.10.1.106",3);
serverWeightMap.put("10.10.1.107",1);
serverWeightMap.put("10.10.1.108",2);
serverWeightMap.put("10.10.1.109",1);
serverWeightMap.put("10.10.1.110",1);
}
public static void main(String[] args) {
//重新创建一个Map,避免出现由于服务器上线和下线导致的并发问题
Map serverMap=new HashMap();
serverMap.putAll(serverWeightMap);
//取得IP地址的list
Set keySet=serverMap.keySet();
ArrayList keyList=new ArrayList();
keyList.addAll(keySet);
String server=null;
int pos=0;
for (int i = 0; i < 20; i++) {
if(pos>=keySet.size())pos=0;
server=keyList.get(pos);
System.out.println("第"+(i+1)+"次请求的服务器为:"+server);
pos++;
}
}
}
package loadbalancing;
import java.util.*;
/**
* Created by yangenneng on 2017-02-17.
* 功能说明:随机法
*/
public class RoundRobinDemo {
static Map serverWeightMap=new HashMap(); // 服务器地址 权重
static {
// 服务器地址 权重
serverWeightMap.put("10.10.1.100",1);
serverWeightMap.put("10.10.1.101",1);
serverWeightMap.put("10.10.1.102",1);
serverWeightMap.put("10.10.1.103",4);
serverWeightMap.put("10.10.1.104",1);
serverWeightMap.put("10.10.1.105",1);
serverWeightMap.put("10.10.1.106",3);
serverWeightMap.put("10.10.1.107",1);
serverWeightMap.put("10.10.1.108",2);
serverWeightMap.put("10.10.1.109",1);
serverWeightMap.put("10.10.1.110",1);
}
public static void main(String[] args) {
//重新创建一个Map,避免出现由于服务器上线和下线导致的并发问题
Map serverMap=new HashMap();
serverMap.putAll(serverWeightMap);
//取得IP地址的list
Set keySet=serverMap.keySet();
ArrayList keyList=new ArrayList();
keyList.addAll(keySet);
String server=null;
int pos=0;
for (int i = 0; i < 20; i++) {
Random random=new Random();
pos=random.nextInt(keySet.size());
server=keyList.get(pos);
System.out.println("第"+(i+1)+"次请求的服务器为:"+server);
pos++;
}
}
}
源地址哈希是获取客户端访问的IP地址,通过哈希函数计算得到一个数值,数值对服务器列表的大小进行取模运算,得到的结果便是要访问的服务器的序号。
package loadbalancing;
import java.util.*;
/**
* Created by yangenneng on 2017-02-17.
* 功能说明:源地址哈希法
*/
public class RoundRobinDemo {
static Map serverWeightMap=new HashMap(); // 服务器地址 权重
static {
// 服务器地址 权重
serverWeightMap.put("10.10.1.100",1);
serverWeightMap.put("10.10.1.101",1);
serverWeightMap.put("10.10.1.102",1);
serverWeightMap.put("10.10.1.103",4);
serverWeightMap.put("10.10.1.104",1);
serverWeightMap.put("10.10.1.105",1);
serverWeightMap.put("10.10.1.106",3);
serverWeightMap.put("10.10.1.107",1);
serverWeightMap.put("10.10.1.108",2);
serverWeightMap.put("10.10.1.109",1);
serverWeightMap.put("10.10.1.110",1);
}
public static void main(String[] args) {
getServer("10.10.1.100",1);
getServer("10.10.1.101",1);
getServer("10.10.1.102",1);
getServer("10.10.1.102",2);
getServer("10.10.1.101",2);
getServer("10.10.1.100",2);
}
private static void getServer(String ip,int count){
//重新创建一个Map,避免出现由于服务器上线和下线导致的并发问题
Map serverMap=new HashMap();
serverMap.putAll(serverWeightMap);
//取得IP地址的list
Set keySet=serverMap.keySet();
ArrayList keyList=new ArrayList();
keyList.addAll(keySet);
String server=null;
int hashCode=ip.hashCode();
int serverListSize=keyList.size();
int pos=hashCode%serverListSize;
System.out.println(ip+"第"+count+"次请求的服务器地址为:"+keyList.get(pos));
}
}
package loadbalancing;
import java.util.*;
/**
* Created by yangenneng on 2017-02-17.
* 功能说明:加权轮询法法
*/
public class RoundRobinDemo {
static Map serverWeightMap=new HashMap(); // 服务器地址 权重
static {
// 服务器地址 权重
serverWeightMap.put("10.10.1.100",1);
serverWeightMap.put("10.10.1.101",1);
serverWeightMap.put("10.10.1.102",1);
serverWeightMap.put("10.10.1.103",4);
serverWeightMap.put("10.10.1.104",1);
serverWeightMap.put("10.10.1.105",1);
serverWeightMap.put("10.10.1.106",3);
serverWeightMap.put("10.10.1.107",1);
serverWeightMap.put("10.10.1.108",2);
serverWeightMap.put("10.10.1.109",1);
serverWeightMap.put("10.10.1.110",1);
}
public static void main(String[] args) {
getServer();
}
private static void getServer(){
//重新创建一个Map,避免出现由于服务器上线和下线导致的并发问题
Map serverMap=new HashMap();
serverMap.putAll(serverWeightMap);
//取得IP地址的list
Set keySet=serverMap.keySet();
Iterator it=keySet.iterator();
List serverList=new ArrayList();
while (it.hasNext()){
String server=it.next();
Integer weight= (Integer) serverMap.get(server);//权重
for (int i = 0; i < weight; i++) {
serverList.add(server);
}
}
String server=null;
int pos=0;
for (int i = 0; i < 20; i++) {
if(pos>=serverList.size()) pos=0;
server=serverList.get(pos);
pos++;
System.out.println("第"+(i+1)+"次申请的服务器为:"+server);
}
}
}
package loadbalancing;
import java.util.*;
/**
* Created by yangenneng on 2017-02-17.
* 功能说明:随机法
*/
public class RoundRobinDemo {
static Map serverWeightMap=new HashMap(); // 服务器地址 权重
static {
// 服务器地址 权重
serverWeightMap.put("10.10.1.100",1);
serverWeightMap.put("10.10.1.101",1);
serverWeightMap.put("10.10.1.102",1);
serverWeightMap.put("10.10.1.103",4);
serverWeightMap.put("10.10.1.104",1);
serverWeightMap.put("10.10.1.105",1);
serverWeightMap.put("10.10.1.106",3);
serverWeightMap.put("10.10.1.107",1);
serverWeightMap.put("10.10.1.108",2);
serverWeightMap.put("10.10.1.109",1);
serverWeightMap.put("10.10.1.110",1);
}
public static void main(String[] args) {
getServer();
}
private static void getServer(){
//重新创建一个Map,避免出现由于服务器上线和下线导致的并发问题
Map serverMap=new HashMap();
serverMap.putAll(serverWeightMap);
//取得IP地址的list
Set keySet=serverMap.keySet();
Iterator it=keySet.iterator();
List serverList=new ArrayList();
while (it.hasNext()){
String server=it.next();
Integer weight= (Integer) serverMap.get(server);//权重
for (int i = 0; i < weight; i++) {
serverList.add(server);
}
}
String server=null;
for (int i = 0; i < 20; i++) {
Random random=new Random();
server=serverList.get(random.nextInt(serverList.size()));//产生随机数
System.out.println("第"+(i+1)+"次申请的服务器为:"+server);
}
}
}
根据后端服务器当前的连接情况,动态地选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能地提高后端服务器的利用效率,将负载合理的分流到每一台机器。