Nginx技术总结之四——集群和负载均衡的算法与实现
六. 集群和负载均衡的算法与实现
6.1 负载均衡器
负载均衡器可以是专用设备,也可以是在通用服务器上运行的应用程序。 分散请求到拥有相同内容或提供相同服务的服务器。 专用设备一般只有以太网接口,可以说是多层交换机的一种。 负载均衡器一般会被分配虚拟IP地址,所有来自客户端的请求都是针对虚拟IP地址完成的。负载均衡器通过负载均衡算法将来自客户端的请求转发到服务器的实际IP地址上。
6.2 负载均衡算法
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);
}};
6.2.1 随机算法
Random随机,按权重设置随机概率。在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
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);
}
WeightRandom
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);
}
6.2.2 轮询及加权轮询
轮询及加权轮询 (Round Robin) 算法是一种平滑权重轮询算法。普通的权重轮询方法有时会发生某个节点突然被连续频繁选中,流量暴增的情况,而平滑权重轮询的 RoundRobin 算法的优点是将连续频繁的节点分配更加均匀。通常的 RoundRobin 算法比较简单,总共 N 个服务节点,传来一个 key 值,计算:
int n = hash(key) % N;
以 Dubbo 的 RoundRobin 负载均衡算法为例,它参考了 Nginx 的 RoundRobin 算法,有三个核心参数:
- int weight:对 Invoker 设定的权重;
- AtomicLong current:考虑到并发场景下的某个 Invoker 会被同时选中,表示该节点被所有线程选中的权重总和;
- long lastUpdate:最后一次更新时间;
每次请求进行负载均衡时,算法操作如下:
- 遍历所有可调用 Invoker 列表:
- 根据
weight
值更新当前current
值:current = current + weight;
- 累加每个 Invoker 的权重
weight
到总权重totalWeight
;
- 根据
- 遍历结束后,选取最大的
current
值作为本次要选的节点,同时对其更新,减去totalWeight
。
比如有三个 Invoker 节点 A, B, C,它们权重分别为 [A, B, C]=[4, 2, 1],初始 current 值都为 0,所以平滑轮询过程如下表所示:
请求次数 |
选中前 Invoker 的 current 值 |
被选中节点 |
选中后 Invoker 的 current 值 |
---|---|---|---|
1 |
[4,2,1] |
A |
[-3,2,1] |
2 |
[1,4,2] |
B |
[1,-3,2] |
3 |
[5,-1,3] |
A |
[-2,-1,3] |
4 |
[2,1,4] |
C |
[2,1,-3] |
5 |
[6,3,-2] |
A |
[-1,3,-2] |
6 |
[3,5,-1] |
B |
[3,-2,-1] |
7 |
[7,0,0] |
A |
[0,0,0] |
对上述过程进行解释,以第一次到第二次请求过程为例,经过了如下几次计算:
- 首先初始值为 [A, B, C] = [0, 0, 0],对每个节点分别加上权重 [4, 2, 1],即第一次请求时的
current
值分别为 [A, B, C] = [4, 2, 1]; - 第一次请求中,最大的
current
节点为 A=4,所以第一次请求选中 A 节点; - 第一次请求选中后,更新三个节点的
current
值:对此次被选中的 A 节点减去总权重值 7,此时current
值分别为 [A, B, C] = [-3, 2, 1]; - 第二次请求来临,再对每个节点分别加上权重 [4, 2, 1],第二次请求时的
current
值分别为 [A, B, C] = [1, 4, 2]; - 此后重复步骤 2, 3, 4;
由上表结果,每 7 次循环中,A 被选中了 4 次,B 被选中了 2 次,C 被选中了 1 次,满足 4:2:1 的权重分配,同时 A 节点也没有被连续分配。
6.2.3 最小连接及加权最小连接
- 最少连接 (Least Connections) 在多个服务器中,与处理连接数(会话数)最少的服务器进行通信的算法。即使在每台服务器处理能力各不相同,每笔业务处理量也不相同的情况下,也能够在一定程度上降低服务器的负载。
- 加权最少连接 (Weighted Least Connection) 为最少连接算法中的每台服务器附加权重的算法,该算法事先为每台服务器分配处理连接的数量,并将客户端请求转至连接数最少的服务器上。
6.2.4 哈希算法
普通哈希
public void hash(){
List<String> keyList = new ArrayList<String>(serverMap.keySet());
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,相同参数的请求总是发到同一提供者。当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提供者,不会引起剧烈变动。
6.2.5 IP地址散列
通过管理发送方IP和目的地IP地址的散列,将来自同一发送方的分组(或发送至同一目的地的分组)统一转发到相同服务器的算法。当客户端有一系列业务需要处理而必须和一个服务器反复通信时,该算法能够以流(会话)为单位,保证来自相同客户端的通信能够一直在同一服务器中进行处理。
6.2.6 URL散列
通过管理客户端请求URL信息的散列,将发送至相同URL的请求转发至同一服务器的算法。
6.3 负载均衡算法的手段
负载均衡算法的手段 (DNS->数据链路层->IP层->Http层)
6.3.1 DNS域名解析负载均衡(延迟)
利用DNS处理域名解析请求的同时进行负载均衡是另一种常用的方案。在DNS服务器中配置多个A记录,如:www.mysite.com IN A 114.100.80.1、www.mysite.com IN A 114.100.80.2、www.mysite.com IN A 114.100.80.3.
每次域名解析请求都会根据负载均衡算法计算一个不同的IP地址返回,这样A记录中配置的多个服务器就构成一个集群,并可以实现负载均衡。
DNS域名解析负载均衡的优点是将负载均衡工作交给DNS,省略掉了网络管理的麻烦,缺点就是DNS可能缓存A记录,不受网站控制。
事实上,大型网站总是部分使用DNS域名解析,作为第一级负载均衡手段,然后再在内部做第二级负载均衡。
6.3.2 数据链路层负载均衡(LVS)
数据链路层负载均衡是指在通信协议的数据链路层修改mac地址进行负载均衡。
这种数据传输方式又称作三角传输模式,负载均衡数据分发过程中不修改IP地址,只修改目的的mac地址,通过配置真实物理服务器集群所有机器虚拟IP和负载均衡服务器IP地址一样,从而达到负载均衡,这种负载均衡方式又称为直接路由方式(DR).
在上图中,用户请求到达负载均衡服务器后,负载均衡服务器将请求数据的目的mac地址修改为真是WEB服务器的mac地址,并不修改数据包目标IP地址,因此数据可以正常到达目标WEB服务器,该服务器在处理完数据后可以经过网管服务器而不是负载均衡服务器直接到达用户浏览器。
使用三角传输模式的链路层负载均衡是目前大型网站所使用的最广的一种负载均衡手段。在linux平台上最好的链路层负载均衡开源产品是LVS(linux virtual server)。
6.3.3 IP负载均衡(SNAT)
IP负载均衡:即在网络层通过修改请求目标地址进行负载均衡。
用户请求数据包到达负载均衡服务器后,负载均衡服务器在操作系统内核进行获取网络数据包,根据负载均衡算法计算得到一台真实的WEB服务器地址,然后将数据包的IP地址修改为真实的WEB服务器地址,不需要通过用户进程处理。真实的WEB服务器处理完毕后,相应数据包回到负载均衡服务器,负载均衡服务器再将数据包源地址修改为自身的IP地址发送给用户浏览器。
这里的关键在于真实WEB服务器相应数据包如何返回给负载均衡服务器,一种是负载均衡服务器在修改目的IP地址的同时修改源地址,将数据包源地址改为自身的IP,即源地址转换(SNAT),另一种方案是将负载均衡服务器同时作为真实物理服务器的网关服务器,这样所有的数据都会到达负载均衡服务器。
IP负载均衡在内核进程完成数据分发,较反向代理均衡有更好的处理性能。但由于所有请求响应的数据包都需要经过负载均衡服务器,因此负载均衡的网卡带宽成为系统的瓶颈。
6.3.4 HTTP重定向负载均衡(少见)
HTTP重定向服务器是一台普通的应用服务器,其唯一的功能就是根据用户的HTTP请求计算一台真实的服务器地址,并将真实的服务器地址写入HTTP重定向响应中(响应状态吗302)返回给浏览器,然后浏览器再自动请求真实的服务器。
这种负载均衡方案的优点是比较简单,缺点是浏览器需要每次请求两次服务器才能拿完成一次访问,性能较差;使用HTTP302响应码重定向,可能是搜索引擎判断为SEO作弊,降低搜索排名。重定向服务器自身的处理能力有可能成为瓶颈。因此这种方案在实际使用中并不见多。
6.3.5 反向代理负载均衡(nginx)
传统代理服务器位于浏览器一端,代理浏览器将HTTP请求发送到互联网上。而反向代理服务器则位于网站机房一侧,代理网站web服务器接收http请求。
反向代理的作用是保护网站安全,所有互联网的请求都必须经过代理服务器,相当于在web服务器和可能的网络攻击之间建立了一个屏障。
除此之外,代理服务器也可以配置缓存加速web请求。当用户第一次访问静态内容的时候,静态内存就被缓存在反向代理服务器上,这样当其他用户访问该静态内容时,就可以直接从反向代理服务器返回,加速web请求响应速度,减轻web服务器负载压力。
另外,反向代理服务器也可以实现负载均衡的功能。
由于反向代理服务器转发请求在HTTP协议层面,因此也叫应用层负载均衡。优点是部署简单,缺点是可能成功系统的瓶颈。
- 无意禁止使用:英伟达官方回应GeForce软件条款更改
- Ext JS 6 新特性和工具
- 为你的WordPress 博客文章页面增加多彩排版条
- java: web应用中不经意的内存泄露
- java: web应用中不经意的内存泄露
- velocity模板引擎学习(4)-在standalone的java application中使用velocity及velocity-tools
- mac上开启ftp
- Web前端开发的四个阶段
- 使用Visual Studio 2015 开发ASP.NET MVC 5 项目部署到Mono/Jexus
- 关于把本地应用封装成windows app发布审核通不过的问题
- java并发编程学习:用 Semaphore (信号量)控制并发资源
- java并发编程学习: ThreadLocal使用及原理
- 使用Autofac IOC组织多项目应用程序
- 国内首个“人工智能与变革管理研究院”成立
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- LeetCode51|寻找旋转排序数组中的最小值
- Yapi 可视化接口平台安装实践
- LeetCode50|搜索旋转排序数组II
- LeetCode49|搜索旋转排序数组
- 第13天:NLP补充——RNN算法
- Android自定义跑马灯效果(适合任意布局)
- Handler Looper.prepareMainLooper();源码分析
- Caused by: java.lang.IllegalStateException: System services not available to Activities before onCre
- Actuator与服务监控
- Typecho 文章置顶插件:Sticky
- SpringBoot源码学习(一)
- Typecho Markdown编辑器粘贴剪贴板图片插件:PasteImage
- SpringBoot源码学习(二)
- 【React+Typescript+Antd】Echarts滑动卡顿问题解决
- 13个超实用的JavaScript数组操作技巧