【前言】
负载均衡技术对于中大型网站的性能提高有着很大的优势,最近在学习《大型网站技术的架构》,其中对于负载均衡技术有一些介绍,将学习的经验总结一下,分享下。多交流。
【协议层】http重定向协议实现负载均衡
原理:根据用户的http请求计算出一个真实的web服务器地址,并将该web服务器地址写入http重定向响应中返回给浏览器,由浏览器重新进行访问。
如图:
优点:比较简单
缺点:浏览器需要零次请求服务器才能完成一次访问,性能较差。
http重定向服务器自身的处理能力可能成为瓶颈。
使用http302响应重定向,有可能使搜索引擎判断为SEO作弊,降低搜索排名。
【协议层】dns域名解析负载均衡
原理:在DNS服务器上配置多个域名对应IP的记录。例如一个域名www.baidu.com对应一组web服务器IP地址,域名解析时经过DNS服务器的算法将一个域名请求分配到合适的真实服务器上。
如图:
优点:将负载均衡的工作交给了DNS,省却了网站管理维护负载均衡服务器的麻烦,同事许多DNS还支持基于地理位置的域名解析,将域名解析成距离用户地理最近的一个服务器地址,加快访问速度吗,改善性能。
缺点:目前的DNS解析是多级解析,每一级DNS都可能化缓存记录A,当摸一服务器下线后,该服务器对应的DNS记录A可能仍然存在,导致分配到该服务器的用户访问失败。
DNS负载均衡的控制权在域名服务商手里,网站可能无法做出过多的改善和管理。
不能够按服务器的处理能力来分配负载。DNS负载均衡采用的是简单的轮询算法,不能区分服务器之间的差异,不能反映服务器当前运行状态,所以其的负载均衡效果并不是太好。
可能会造成额外的网络问题。为了使本DNS服务器和其他DNS服务器及时交互,保证DNS数据及时更新,使地址能随机分配,一般都要将DNS的刷新时间设置的较小,但太小将会使DNS流量大增造成额外的网络问题。
- 1
- 2
- 3
- 4
- 5
【协议层】反向代理负载均衡
原理:反向代理处于web服务器这边,反向代理服务器提供负载均衡的功能,同时管理一组web服务器,它根据负载均衡算法将请求的浏览器访问转发到不同的web服务器处理,处理结果经过反向服务器返回给浏览器。
如图:
例如:浏览器访问请求的地址是反向代理服务器的地址114.100.80.10,反向代理服务器收到请求,经过负载均衡算法后得到一个真实物理地址10.0.03,并将请求结果发给真实无服务,真实服务器处理完后通过反向代理服务器返回给请求用户。
优点:部署简单,处于http协议层面。
缺点:使用了反向代理服务器后,web 服务器地址不能直接暴露在外,因此web服务器不需要使用外部IP地址,而反向代理服务作为沟通桥梁就需要配置双网卡、外部内部两套IP地址。
- 1
- 2
- 3
【网络层】IP负载均衡
原理:在网络层通过修改目标地址进行负载均衡。
如图:
用户访问请求到达负载均衡服务器,负载均衡服务器在操作系统内核进程获取网络数据包,根据算法得到一台真实服务器地址,然后将用户请求的目标地址修改成该真实服务器地址,数据处理完后返回给负载均衡服务器,负载均衡服务器收到响应后将自身的地址修改成原用户访问地址后再讲数据返回回去。类似于反向服务器负载均衡。
优点:在响应请求时速度较反向服务器负载均衡要快。
缺点:当请求数据较大(大型视频或文件)时,速度较慢。
- 1
- 2
- 3
- 4
【链路层】数据链路层负载均衡
原理:在数据链路层修改Mac地址进行负载均衡。
如图:
负载均衡服务器的IP和它所管理的web 服务群的虚拟IP一致;
负载均衡数据分发过程中不修改访问地址的IP地址,而是修改Mac地址;
通过这两点达到不修改数据包的原地址和目标地址就可以进行正常的访问。
优点:不需要负载均衡服务器进行地址的转换。
数据响应时不需要经过负载均衡服务器。
缺点:负载均衡服务器的网卡带宽要求较高。
目前连路程负载均衡是特别常见的一种手段,典型的产品有LVS(Linux Virtual Server)。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
算法实现
常见的负载均衡算法包括轮询法、随机法、源地址哈希法、加权轮询法、加权随机法、最小连接法等。具体实现如下:
import java.util.*;
public class LoadBalancing {
static Map<String,Integer> serverWeightMap = new HashMap<String,Integer>();
static Integer pos = 0;
static{
serverWeightMap.put("192.168.1.100",1);
serverWeightMap.put("192.168.1.101",1);
serverWeightMap.put("192.168.1.102",4);
serverWeightMap.put("192.168.1.103",1);
serverWeightMap.put("192.168.1.104",1);
serverWeightMap.put("192.168.1.105",3);
serverWeightMap.put("192.168.1.106",1);
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",3);
}
//轮询
public static String testRoundRobin(){
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String,Integer> serverMap = new HashMap<String, Integer>();
serverMap.putAll(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 = new Integer(0);
}
server = keyList.get(pos);
pos++;
}
return server;
}
//随机
public static String testRandom(){
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String,Integer> serverMap = new HashMap<String,Integer>();
serverMap.putAll(serverWeightMap);
//取得ip地址list
Set<String> keySet = serverMap.keySet();
ArrayList<String> keyList = new ArrayList<String>();
keyList.addAll(keySet);
Random random = new Random();
int randomPos = random.nextInt(keyList.size());
String server = keyList.get(randomPos);
return server;
}
//源地址哈希(hash)法
public static String testConsumerHash(String remoteIp){
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String,Integer> serverMap = new HashMap<String,Integer>();
serverMap.putAll(serverWeightMap);
//取得ip地址list
Set<String> keySet = serverMap.keySet();
ArrayList<String> keyList = new ArrayList<String>();
keyList.addAll(keySet);
int hashCode = remoteIp.hashCode();
int serverListSize = keyList.size();
int serverPos = hashCode % serverListSize;
return keyList.get(serverPos);
}
//加权轮询
public static String testWeightRoundRobin(){
//重新创建一个map,避免由于服务器上线和下线导致的并发问题
Map<String,Integer> serverMap = new HashMap<String,Integer>();
serverMap.putAll(serverWeightMap);
//取得ip地址list
Set<String> keySet = serverMap.keySet();
Iterator<String> it = keySet.iterator();
List<String> serverList = new ArrayList<String>();
while(it.hasNext()){
String server = it.next();
Integer weight = serverMap.get(server);
for(int i=0;i<weight;i++){
serverList.add(server);
}
}
String server = null;
synchronized (pos){
if(pos >= serverList.size()){
pos = new Integer(0);
}
server = serverList.get(pos);
pos++;
}
return server;
}
//加权随机
public static String testWeightRandom(){
//重新创建一个map,避免出现由于服务器上线和下线导致的并发问题
Map<String,Integer> serverMap = new HashMap<String,Integer>();
serverMap.putAll(serverWeightMap);
//取得ip地址list
Set<String> keySet = serverMap.keySet();
ArrayList<String> serverList = new ArrayList<String>();
Iterator<String> iterator = keySet.iterator();
while(iterator.hasNext()){
String server = iterator.next();
Integer weight = serverMap.get(server);
for(int i=0;i<weight;i++){
serverList.add(server);
}
}
Random random = new Random();
int randomPos = random.nextInt(serverList.size());
String server = serverList.get(randomPos);
return server;
}
}