负载均衡实现多种,硬件到软件,商业的、开源的。常见的负载均衡方式:
需要HTTP重定向服务器。
- 浏览器先去HTTP重定向服务器请求,获取到实际被定向到的服务器;浏览器再请求被实际定向到的服务器。
简单易实现。
- 浏览器需要两次请求才能完成一次完整的访问,性能以及体验太差。
- 依赖于重定向服务器自身处理能力,集群伸缩性规模有限。
- 使用HTTP 302进行重定向可能使搜索引擎判断为SEO作弊,降低搜索排名。
需要DNS服务器。
- 浏览器请求某个域名,DNS服务器返回web服务器集群中的某个ip地址。
- 浏览器再去请求DNS返回的服务器地址。
该方案要要求在DNS服务器中配置多个A记录,例如:
www.example.com IN A | 10.192.168.66 |
---|---|
www.example.com IN A | 10.192.168.77 |
www.example.com IN A | 10.192.168.88 |
该方案将负载均衡的工作交给了DNS,节省了网站管理维护负载均衡服务器的麻烦。
- 目前的DNS是多级解析的,每一级别都可能缓存了A记录,当某台业务服务器下线后,即使修改了DNS的A记录,要使其生效还需要一定时间。这期间,可能导致用户会访问已下线的服务器造成访问失败。
- DNS负载均衡的控制权在域名服务商那里,网站无法对其做出更多的改善和管理。
温馨提示
事实上,可能是部分使用DNS域名解析,利用域名解析作为第一级的负载均衡手段,域名解析得到的是同样提供负载均衡的内部服务器,这组服务器再进行负载均衡,请求分发到真是的web应用服务器。
反向代理服务器需要配置双网卡和内外部两套IP地址。即反向代理服务器需要有外部IP、内部IP。
部署简单,负载均衡功能和反向代理服务器功能集成在一块。
反向代理服务器是所有请求和相应的中转站,性能可能成为瓶颈。
优点是在内核进程中完成了数据分发,而反向代理负载均衡是在应用程序中分发数据,IP负载均衡处理性能更优。
所有请求、响应都经由负载均衡服务器,导致集群最大响应数据吞吐量不得不受制于负载均衡服务器网卡带宽。
- 数据链路层负载均衡也叫三角传输模式。
- 负载均衡数据分发过程中不修改IP地址,而是修改MAC地址。
- 由于实际处理请求的真实物理IP地址和数据请求目的IP地址一致,所以不需要通过负载均衡服务器进行地址转换,可以将响应数据包直接返回给用户浏览器,避免负载均衡服务器网卡宽带成为瓶颈。
这种负载均衡方式也叫作直接路由方式(DR)。
使用三角传输模式的链路层负载均衡是目前大型网站使用最广泛的一种负载均衡手段。
在Linux平台上最好的链路层负载均衡开源产品是LVS(Linux Virutal Server)。
如何从web服务器列表中计算得到一台目标web服务器地址,就是负载均衡算法。
表示一个web服务器ip、权重的字典映射。
private Map<String,Integer> serverMap = new HashMap<String,Integer>(){{
put("192.168.1.100",1);
put("192.168.1.101",1);
put("192.168.1.102",4);
put("192.168.1.103",1);
put("192.168.1.104",1);
put("192.168.1.105",3);
put("192.168.1.106",1);
put("192.168.1.107",2);
put("192.168.1.108",1);
put("192.168.1.109",1);
put("192.168.1.110",1);
}};
这是最简单的调度算法,调度器将收到的请求循环分配到服务器集群中的每台机器,这种算法平等地对待每一台服务器,而不管服务器上实际的负载状况和连接状态,适合所有服务器有相同或者相近性能的情况.
轮询记录一个字段,记录下一次的字典位置。
private Integer pos = 0;
public void roundRobin(){
List<String> keyList = new ArrayList<String>(serverMap.keySet());
String server = null;
synchronized (pos){
if(pos > keyList.size()){
pos = 0;
}
server = keyList.get(pos);
pos++;
}
System.out.println(server);
}
加权轮询,旨在轮询的基础上,增加权重的影响。 用一个列表获取web服务器的字典映射,权重大的在列表中出现的次数就多。
public void weightRoundRobin(){
// 所有的服务器字典集合
Set<String> keySet = serverMap.keySet();
// 权重影响的可选列表(权重大的出现的次数较多)
List<String> servers = new ArrayList<String>();
for(Iterator<String> it = keySet.iterator();it.hasNext();){
String server = it.next();
int weithgt = serverMap.get(server);
for(int i=0;i<weithgt;i++){
servers.add(server);
}
}
String server = null;
synchronized (pos){
// 如果位置大于服务器个数,则选择第一个服务器
if(pos > keySet.size()){
pos = 0;
}
// 其他则每一次选择,都自增位置,从【权重影响的可选列表】中获取索引位置上的服务器
server = servers.get(pos);
pos++;
}
System.out.println(server);
}
维护一个服务列表,随机调取
public void random(){
List<String> keyList = new ArrayList<String>(serverMap.keySet());
Random random = new Random();
int idx = random.nextInt(keyList.size());
String server = keyList.get(idx);
System.out.println(server);
}
维护一个列表, 但是会读取web服务器的权重,权重大的出现次数较多,再随机调取。
public void weightRandom(){
Set<String> keySet = serverMap.keySet();
List<String> servers = new ArrayList<String>();
for(Iterator<String> it = keySet.iterator();it.hasNext();){
String server = it.next();
int weithgt = serverMap.get(server);
for(int i=0;i<weithgt;i++){
servers.add(server);
}
}
String server = null;
Random random = new Random();
int idx = random.nextInt(servers.size());
server = servers.get(idx);
System.out.println(server);
}
选择与当前连接数最少的服务器通信。
为最少连接算法中的每台服务器附加权重的算法,该算法事先为每台服务器分配处理连接的数量,并将客户端请求转至连接数最少的服务器上。
可以将请求源地址的ip;将其哈希值与web服务器字典集合映射做取模运算,获取合适的web服务器发送请求。
public void hash(){
List<String> keyList = new ArrayList<String>(serverMap.keySet());
// 可能是源地址-请求地址的ip;将其哈希值与web服务器字典集合映射做取模运算,获取合适的web服务器发送请求。
String remoteIp = "192.168.2.215";
int hashCode = remoteIp.hashCode();
int idx = hashCode % keyList.size();
String server = keyList.get(Math.abs(idx));
System.out.println(server);
}
- 一致性Hash,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
实现可以参考dubbo的一致性哈希算法:
org.apache.dubbo.rpc.cluster.loadbalance.ConsistentHashLoadBalance