负载均衡
- 在真正的反向代理场景中,必然涉及到的一个概念,就是负载均衡
- 所谓负载均衡,也就是将Nginx的请求发送给后端的多台应用程序服务器
- 通常的应用程序服务器,后面的每台服务器都是一个同等的角色,提供相同的功能
- 用户发送一个request 到我们的这个负载均衡器(Nginx)
- 负载均衡器会将这个请求发给后端的任何一台服务器
- 它会依据一定的负载均衡算法去后端的三台服务器中选择其中的一台
- 从而将这个请求发送给后端这个应用程序服务器
- 由应用程序服务器处理完之后,再次的返回给负载均衡器
- 由负载均衡器再把这个响应的结果给传递给我们的用户
- 在整个这样一个过程中,对于负载均衡器和后端的应用程序服务器来说
- 它这三台服务器提供的角色通常来说是一样的
- 比如说在某些场景下,后端的这个应用服务器可能都是一台mysql数据库服务器
- 在mysql数据库服务器中,通常我们用的最多的一种性能是读请求读,请求压力会很大
- 在一组两重的架构中,第一台服务器可能是用来写的,第二台和第三台都是用来读的
- 当然很多情景下,我们第一台服务器也可以用来读
- 这个时候所有的读请求,就都可以平均的分发到我们后端这些服务器
1 )场景举例1
- 现在,后端的一个应用程序服务器提供了一个电商站点,
- 这个时候我的用户可能需要去登录到自己的账户中,然后去买商品
- 用户会首先发起个请求到负载均行器,负载均行器再发到应用服务器
- 这个时候我们用户登录账户之后,账号信息保存在应用服务器一上
- 这个时候我们的用户就去浏览我们的网页,选商品,之后,加到购物车
- 这个时候,假如他一刷新页面,这会触发一个新的HTTP请求
- 这个请求也会到负载均容器,但被分配到第二台服务器上了,这样用户信息丢失了
- 后续操作可能会要重新登录,这种场景对用户来说是不能接受的
- 对于这样一种情形来说,涉及到后端的三台应用服务器如何去对session进行共享?
- 这是负载均衡器需要面临的第一个问题
2 )场景举例2
- 负载均衡器在分发请求的时候,三台服务器,如果所有的角色能力都一样,服务器硬件规格也一样
- 可能是第一次发给第一,第二次发给第二,第三次发给第三,第四次再发给第一,这样轮询
- 假如,服务器有些性能处理能力很强,有些性能处理很弱,如何合理的分发这样一些请求 ?
- 也就是说负载均衡器在选择后端的应用服务的时候,要依据一定的算法, 算法设定的优劣
- 对于负载均衡的效果起到了决定性的影响的,其实没有一个绝对的优劣之分
- 所有的这些负载均衡的算法,都各自有一些不同的适用场景,需要去结合业务来进行选择使用
3 )场景举例3
- 在反向代理的场景下,这些应用服务器上面,某些缓存信息是如何结合Nginx进行保存的?
Nginx 对上游服务负载均衡
1 )回顾 upstream 段
-
语法:upstream name { … }
-
默认值:无
-
上下文:http
-
示例
upstream {............ }
-
指令:
- server address [parameters];
- weight=number
- max_conns=nuimber
- fail_timeout=time
- max_fails=number
- backup
- down
- keepalive connections;
- keepalive_request number;
- keepalive_timeout time;
- queue;
- server address [parameters];
-
配置示例
upstream back_end {server 127.0.0.1:8080 weight=3 max_conns=1000 fail_timeout=10s max_fails = 2;keepalive 32;keepalive_requests 50;keepalive_timeout 30s;
}
2 ) 实际部署
2.1 应用服务器 (使用 nginx 模拟),比如当前ip地址为:192.168.184.20
server {listen 8020;location / {return 200 'Return Result For Server 8020\n';}
}server {listen 8021;location / {return 200 'Return Result For Server 8021\n';}
}server {listen 8022;location / {return 200 'Return Result For Server 8022\n';}
}
这里 模拟3台 应用服务器
2.2 nginx 服务器
upstream demo_server {server 192.168.184.20:8020 weight=3 max_conns=50 fail_timeout=10s max_fails=2;server 192.168.184.20:8021 weight=2;server 192.168.184.20:8022 weight=1;
}server {listen 80;server_name balance.baidu.com;location /balance/ {proxy_pass http://demo_server;}
}
哈希算法
- 上面的示例,演示的是加权轮询的方式,在分配请求的时候,会保持这个分配比例,而非严格按照顺序分配
- 现在,来看新的一个负载均衡算法 — 哈希算法
- 定义:哈希算法是将任意长度的二进制值映射为较短的固定长度的二进制值,这个小的二进制值我们称之为哈希值,散落明文到哈希值的映射是不可逆的
- 简单来说,哈希算法就是根据一个固定内容的值,经过哈希运算之后,可以得到一个唯一的值
- 哈希算法有很多种,但是它的思想都是一样的,总的来说,它是根据一个内容经过一定的哈希值换算之后得到一个固定长度的一个字符串
- 对一个文件内容不变的文件来说,它永远会得到一个相同的字符串值,这个算法可以解决文件传输前后校验是否一致的场景
- 比如说,现在有两个文件,左侧的上和下,是不一样的,别看多了一行,就是多了一个字节,计算出来的内容都不会一样
1 )哈希指令
- 语法:hash key [consistent];
- 这个key 通常是一个内容, 可以使用变量,特定内容的变量
- 可以根据请求头的内容来进行哈希运算,
- 如果头部的某些内容来源一样,哈希值一定一致
- 或者根据客户端的ip
- 默认值:无
- 上下文:upstream
2 )配置示例
2.1 这里使用Nginx模拟应用服务器
server {listen 10020;location / {return 200 'Return Result For Server 10020\n';}
}server {listen 10010;location / {return 200 'Return Result For Server 10010\n';}
}
2.2 应用服务器
upstream demo_server_1 {hash $request_uri;server 192.168.184.20:10020;server 192.168.184.20:10010;
}server {listen 80;server_name balance_hash.baidu.com;location / {proxy_pass http://demo_server_1;}
}
- 在上述两台服务器中,假如,请求在一台服务器上有缓存,在另一台是用不了的
- 某一些情形下,为了保证来自同一个客户端的请求,总是能够分配到指定的一台服务器上
- 可能需要根据这样一个哈希算法,把其永远分配到某一个服务器上,而不会再调度到另外一台服务器上
- 其实最初它的一个设计应用是在缓存中用的比较多,像CDN这样的场景
IP Hash 算法
- 哈希算法是可以指定各种不同的key来进行一个哈希运算,这适用于我们一些比较复杂的一个应用场景
- 我们有一些特殊需求,比如,根据某一些应用层的信息,HTTP请求投入的某一些具体信息
- 或者是请求行中的某一些具体信息来进行一个哈希运算
- 它就是根据IP进行哈希运算,因为从这个算法的名称上也能够看出来
1 ) ip_hash 指令
-
语法:ip_hash
-
默认值:无
-
上下文:upstream
-
示例配置
upstream demo_server_1 {ip_hash # 只加这一个配置就行server 192.168.184.20:10020;server 192.168.184.20:10010; }
-
其实, ip_hash 指令是为了解决Nginx和后端应用程序服务器的一个 session 保持的
-
它其实对出的一个应用场景,也是对于这种 session 保持的解决而生的
-
某一些客户端在请求完服务器之后,常会有cookie信息一些
-
对应到服务器端的时候,会有 session 信息, 对于不同的用户来说是不同的
-
对于固定的客户端来说,使用ip hash这样一种负载运用算法来进行 session 保持
-
无疑是一个简错的选择
最少连接数算法
- 不管是 hash 还是 ip_hash,还是加权轮循的负载均衡算法,其实都忽略了一个重要的事实
- 对于这些算法来说,没有将后端应用服务器当前的一个负荷状况给考虑在内
- 只是根据我们从用户发来的请求做一些负带均衡算法的挑选或者是加权轮型就更简单粗暴了
- 不会把任何因素考虑在内,而只是简单的将请求逐个分发
- 包括 hash 或者 ip_hash,他们只能根据某一些特定的属性来对后端进行分发的
- 但是这些属性也仅仅是来自于用户的请求,其实这样一些场景是不合理的
- 比如说,现在应用服务器一的处理能力很强,应用服务器二和三处理能力特别的弱
- 你看之前的这些算法就不合理了
- 都没有将后端服务器的一个当前运行状态就是作为服务器分发请求的一个策略
- 现在引入最少连接算法,它考虑上游服务器的一个情况所决定的一种算法
1 )最少连接算法
- 从上游服务器,挑选一台当前已建立连接数最少的分配请求
- Nginx 在收到用户请求之后,在如何去挑选后端应用服务器的时候
- 会去考虑当前这个应用服务器具体已经建立了多少个连接,已经正在处理多少用户请求
- 他会找最少的,将请求分发给这个
- 在某些情形下,总是挑选当前后端应用服务器正在处理连接数最少的那台进行分发请求
- 极端情形下退化为 rr 算法
- 当服务器分配数都一样的场景下
- 会退化到轮循算法,逐个分发
1.1 least_conn 指令
- 模块
ngx_http_upstream_least_conn_module
- 禁用通过
--without-http_upstream_least_conn_module
- 语法:least conn;
- 默认值:无
- 上下文:upstream
- 基于此算法的Nginx在挑选应用程序服务器的时候会找当前处理请求数最少的一个
- 存在多个 worker 子进程的时候,Nginx 是一个 worker 子进程的情形下,就有一个work子进程
- 每一次的请求它都会分配到不同的worker子进程中,不同的worker子进程,是怎么样知道每一个应用服务器,当前正在处理的请求有多少,包括它当前的一个响应状态是怎么样的
- 也就是说,我们的后端应用服务器的一个具体状态信息,是没办法在不同的worker子进程之间共享的
- 解决办法:在Nginx上去开辟出一块内存空间
- worker 子进程在处理这个用户请求的时候,都会将后端对应的应用程序服务器的一个当前状态
- 比如说它当前正在处理的一个用户连接数,访问失败的次数等服务器的一个共态信息给保存到共享内存中
- 这样实现了最少连接的算法
1.2 zone 指令
- 用于指定共享内存空间大小
- 语法:zone name [size];
- 默认值:无
- 上下文:upstream
- 示例
upstream demo_server_1 {zone test 10M;least_conn;server 192.168.184.20:10020;server 192.168.184.20:10010; }
- 在某一些场景中会有用的,但是它用的也并不是很多