从 Nginx 看负载均衡

FightingCoder

共 7842字,需浏览 16分钟

 · 2020-09-24

阅读本文大概需要 3 分钟。


众所周知,Nginx  是一个轻量级、高性能的 Web  服务器,它的用途广泛,上可作为各种服务的网关,下可直接作为静态网站的服务器,甚至还能被用来做代理服务器、负载均衡器、缓存服务器等。

在目前流行的云服务架构体系中,Nginx 经常会被用于作为网关、负载均衡器之类的用途。在这其中,负载均衡器这个用途可以说是最为广泛的了,毕竟但凡是个需要横向扩展的后端服务,就都需要有个负载均衡器来顶在前面分发请求,而 Nginx 作为一个轻量又高性能的工具,自然是最合适的选择之一。

负载均衡概念

负载均衡是一个基础服务,主要被用来在多个节点间分配负载,以做到最大化资源利用率和吞吐量、最小化响应时间的效果,可以使服务尽可能地做到高并发;同时,负载均衡还能通过提供冗余节点、自动切换的方式提高可靠性,使得服务能尽可能地做到高可用。

注意是从下往上数

负载均衡主要分为两种,一种是基于四层的、一种是基于七层的,这里的“层”指的是 OSI  模型中的“层”的概念。四层指的是 TCP/UDP 这些最原始的协议所在的传输层,而七层指的是 HTTP、SSH 之类的这些上层协议所在的应用层。

两者的主要区别在于:

四层负载均衡

因为通常只需要根据包的报文头中的信息直接分配(转发)负载给节点就行,所以性能会更高、资源占用更低。但由于无法对上层协议的内容进行具体的解析(或者说做了解析就不再是四层了),所以可控性相比于七层的负载均衡而言会低很多。

比如上面这张图是一个 HTTP 请求包的第四层(及以下)的部分,可以得到的信息有来源 IP  和端口、目标 IP 和端口等,而玩过 TCP 通信的朋友应该知道,这些信息都是建立连接后就能得到的。而在做四层负载均衡的时候,我们并不需要理会具体传输的内容就可以直接进行转发,所以也就少了读取具体内容这个从内核态转到用户态的步骤,性能自然也就会更高一些了。

七层负载均衡

可以做到根据客户端信息、目录结构等来分配负载的效果,配置起来也更贴近于应用。但由于需要处理具体的流量,所以七层负载均衡的性能相对低一些,并且由于需要对上层协议的内容进行解析,所以资源占用也相对会高一些。

比如上面这张图是一个 HTTP 请求包的第七层(及以下)的部分,我们可以直接看到它的 Method 是 GET、请求的路径是 /speedtest.js、User-Agent 是 Mac 下的 Chrome的、Cookie 是 balabala 等。


除了四层和七层的区别外,负载均衡还有硬件负载均衡和软件负载均衡两种做法:

硬件负载均衡

硬件负载均衡通常是在交换机上直接进行操作,而这些设备通常还会有着专用的加速芯片,可以使得包的分发效率变得极高,所以性能上相比于软件负载均衡而言,会要高很多。比如可以轻松地带起上万量级的节点,并且还能承受住每秒几百万的负载。(顺带一提,软路由和硬路由有时候性能差距极大也是因为这个原因)

但硬件负载均衡的缺点很明显:由于需要专用设备,所以成本也会高很多,一般根据功能和性能的不同,一个就得要十几万到上百万,相当昂贵。并且还会有部分硬件负载均衡器的配置项过于底层、只支持四层负载均衡,所以会有无法根据节点和应用的实际状况来分配负载的问题。

所以,硬件负载均衡更适合于有海量负载、海量节点需要管理的场景,依靠着专用的加速芯片可以轻松处理在这种场景下的负载均衡问题。

软件负载均衡

软件负载均衡是基于系统和应用的负载均衡,相比于硬件负载均衡而言,能够更好地根据系统和应用的情况来分配负载;同时,由于不需要购买专用设备,随便一个服务器就行,成本会低很多。

软件负载均衡的缺点也很明显:虽然方便,但会受限于服务器的性能。如果服务器性能低下,那自然难以承受更多的负载,也就无法有效地将负载分配给各个节点。

所以,软件负载均衡更适合于负载量还达不到海量级,并且也没有那么多的节点需要管理的场景;或是一些即使舍弃掉部分性能也必须要保证可控性、扩展性的场景(比如加入了反爬虫功能的网关)。

Nginx 负载均衡简介

那么 Nginx 的负载均衡,是什么样的呢?

Nginx 现在的负载均衡既支持四层(ngx_stream_core_module模块)、又支持七层(ngx_http_upstream_module模块),但由于 LVS 在四层负载均衡方面做得知名度实在是太高了,所以 Nginx 的四层负载均衡用的人不怎么多,网上也很少会有说用  Nginx 来做四层负载均衡的。

Nginx 负载均衡方面的代码设计非常模块化,这方面在配置文件上也有所体现。不管是四层还是七层,都是用的同一套 upstream 配置,只是使用这个 upstream 的地方会有差别而已,所以四层和七层的差别其实对于我们的配置来说没有影响,反正写起来基本都是通用的。下面将以七层负载均衡为例讲解。

不过有一个点需要注意,在使用 Nginx 作为七层负载均衡器时,如果 Nginx 上有设置 proxy_cache,那么如果被访问的资源已经被缓存过,Nginx 就不会再将请求转发给节点,而是直接返回缓存资源。也就是说在 Nginx 有配置缓存的情况下是有可能不会触发调度的。

Nginx 负载均衡常见的调度算法

轮询(默认的模式)

轮询算法在被用于服务器的负载均衡时的表现为:按照客户端的请求顺序,逐一将请求分配到不同的后端节点服务器上。

配置时只需要在 upstream 配置中添加 server 即可,在没有指定 weight和其他调度算法的情况下,就是普通的轮询。

upstream balabala {    server 192.168.31.33;    server 192.168.31.237;}...location / {    proxy_pass http://balabala;}

在碰到后端服务器无响应的情况时,Nginx会自动剔除这台服务器,换到其他服务器上。

加权轮询

加权轮询算法在被用于服务器的负载均衡时的表现为:按照权重的大小来进行分配,权重值越大的节点,被分配到的请求就越多。例如现在有两台服务器,一台的权重是5、另一台的权重是1,那么权重为5的服务器被分配到的概率就会更高,被分配到的请求也就越多。

加权轮询通常被用于节点服务器性能不均匀的情况。比如可以给性能高的服务器设置高权重,使其负载更多请求,从而避免性能低的服务器被大量的请求压垮。

配置时只需要在轮询配置的基础上添加weight参数即可。

upstream balabala {    server 192.168.31.33 weight=1;    server 192.168.31.237 weight=1;}...location / {    proxy_pass http://balabala;}

ip_hash

ip_hash是一个静态调度算法,也是一种非一致性Hash算法。它会把每个请求按照客户端IP的Hash结果来分配给节点,使得同一个客户端IP的请求能够始终分配到同一个节点上(无论请求的是哪个URL)。然而由于是非一致性Hash算法,所以一旦节点数量发生变化,所有的分配映射关系就都会发生改变,在节点配置不稳定的情况下会无法达到预期的效果。

由于ip_hash算法可以使同一个客户端IP的请求始终被分配到同一个节点上,所以它可以有效地解决动态页面的session共享问题,也就是做到保持会话的效果。但是它有时也会导致请求分配不均,无法保证所有节点都能分配到一样的请求量。

由于IPV4资源紧缺,目前国内IPV4的IP通常都是会经过NAT的,所以很容易出现多个客户端同时使用同一个IP的情况。而在使用ip_hash的时候,这些使用着同一个IP的客户端就都会被分配到同一个节点上,从而导致出现请求分配不均的情况。

注:LVS中的-p参数、Keepalived 配置里的per-sistence_timeout 50参数,以及 Nginx 中的 ip_hash参数,其功能都可以用来解决动态页面的session共享问题。

配置时只需要在 upstream 配置中加一行 ip_hash;即可。注意不能与 backup 配置共同使用,因为 ip_hash 只能访问同一台服务器,而 backup 只有在所有参与负载均衡的服务器出现故障时,才会被访问。而如果所有参与负载均衡的服务器出现故障了的时候,ip_hash 就不能用了。

upstream balabala {        ip_hash;        server 192.168.31.33 weight=1;        server 192.168.31.237 weight=1;}...location / {        proxy_pass http://balabala;}

hash(一般是称之为 uri_hash)

hash 是一个静态调度算法,也是一种非一致性Hash算法。它会把每个请求按照客户端的IP、请求的URI的Hash结果(参数可选,主流使用URI)来分配给节点,使得同一个URI的请求能够始终分配到同一个节点上(无论来自于哪个客户端)。然而由于是非一致性Hash算法,所以一旦节点数量发生变化,所有的分配映射关系就都会发生改变,在节点配置不稳定的情况下会无法达到预期的效果。

由于hash算法可以使同一个URI的请求始终被分配到同一个节点上,所以它非常适合在节点服务器为缓存服务器的情况下使用,能够大大地提高缓存命中率。

Nginx在1.7.2版本之前并不支持hash算法,如果需要在旧版本中使用这种算法,就需要安装第三方的hash模块。

配置时只需要在upstream配置中加一行hash $request_uri;和一行hash_method crc32;即可,两个的参数均可被替换为其他内容,如有需要请参阅Nginx官方文档。

upstream balabala {        hash $request_uri;        hash_method crc32;        server 192.168.31.33;        server 192.168.31.237;}...location / {        proxy_pass http://balabala;}

fair

fair 是一个动态调度算法,它会根据节点服务器的响应时间来分配请求,响应时间短的会被优先分配。

Nginx 本身并不支持 fair 算法,如果需要使用这种算法,就需要安装第三方的 upstream_fair模块。

配置时只需要在 upstream 配置中加一行 fair;即可。

upstream balabala {        fair;        server 192.168.31.33;        server 192.168.31.237;}...location / {        proxy_pass http://balabala;}

least_conn

least_conn 是一个动态调度算法,它会根据节点服务器的连接数情况来分配请求,连接数少的会被优先分配。

least_conn 可以被用于有大量长连接的场景(比如游戏服务器),它可以帮助节点服务器均分连接,使负载尽可能地保持相同。

least_conn  算法很简单,它会先遍历一遍所有的节点,比较它们的连接数,然后选取值最小的那一个节点。如果有多个节点的连接数值都是最小的,那么就对它们采用加权轮询算法。

配置时只需要在 upstream 配置中加一行 least_conn;即可,可以结合 weight权重值使用。

upstream balabala {        least_conn;        server 192.168.31.33;        server 192.168.31.237;}...location / {        proxy_pass http://balabala;}

consistent_hash

consistent_hash 是一个基于一致性 Hash 算法产生的静态调度算法,它是 ip_hash 和 hash 的升级版。

consistent_hash 同样会把每个请求按照客户端的 IP、请求的 URI 的 Hash 结果(参数可选)来分配给节点,使得同一个 Hash 值的请求能够始终分配到同一个节点上。它的独特之处在于,一致性 Hash 算法为它提供了故障迁移和一致性保持的效果。也就是说,即使在使用过程中某一个节点宕机了,其他 Hash 值和对应的节点也还是不会受影响,仍然可以保持原来的映射关系;而这一个宕机的节点所对应的那些 Hash 值,会被映射到环上的下一个节点上,达到故障迁移的效果。

配置时和hash类似,只需要在 upstream 配置中加一行consistent_hash $request_uri;即可。同样,这个参数也是可以替换为其他内容的。

upstream balabala {        consistent_hash $request_uri;        server 192.168.31.33;        server 192.168.31.237;}...location / {        proxy_pass http://balabala;}

常用的相关参数/配置项说明

keepalive

为每个 worker 进程保留的长连接数量,可以节约端口开销,并减少连接管理的资源消耗。

配置时只需要在 upstream 配置中加一行keepalive 连接数;即可。

upstream balabala {        fair;        keepalive 123;        server 192.168.31.33;        server 192.168.31.237;}...location / {        proxy_pass http://balabala;}

proxy_next_upstream

proxy_next_upstream 被用于定义故障转移策略,当 server 返回指定的http status code、请求超时等错误时,就会自动将请求转发到 upstream 中的另一台服务器,实现故障转移。

配置时只需要在 location 配置中添加一行proxy_next_upstream 具体的http status code或错误类型;即可。可以指定多个需要转移的选项,只需要用空格分隔开就行。

可用的配置项例如(更多配置项请参阅Nginx官方文档):

http_500http_502errortimeoutinvalid_header

upstream balabala {        fair;        keepalive 123;        server 192.168.31.33 max_fails=3 fail_timeout=10s;        server 192.168.31.237 max_fails=3 fail_timeout=10s;}...location / {        proxy_pass http://balabala;        proxy_next_upstream http_500 http_502 error timeout invalid_header;}

server

server参数后面跟着的地址可为多种不同格式,如:

unix:/PATH/TO/SOME_SOCK_FILEIP[:PORT]HOSTNAME[:PORT]

server配置项

down

down表示当前server暂时不参与负载均衡,也就是将server标记为不可用状态。

down可以配合ip_hash来使用,实现灰度发布的效果。

配置时只需要在server后面指定down参数即可。

backup

backup表示当前server是预留的备份机器

只有其他所有的非backup机器出现故障或者忙的时候,才会请求backup机器

backup可以被用来做到sorry server的效果,也就是在后端服务器挂了的时候给个“对不起,网站暂时无法访问”之类的页面,用来提示用户。相比于将特定http status code的页面配置为类似效果的做法,这种做法的优势在于不会出现很长时间的超时等待,而是立马返回错误提示,用户体验会更好一些。

配置时只需要在server后面指定backup参数即可。

max_conns

Nginx在1.11.5版本后新增的参数,指连接某后端服务器时的最大并发活动连接数。

配置时只需要在server后面指定max_conns 连接数;即可。

max_fails

允许请求失败的次数,默认为1。当超出最大次数时,该server会被标记为不可用。

fail_timeout

在经历了max_fails次失败后,暂停服务的时间,默认为10s。某个server连接失败次数超过max_fails次后,nginx就会在接下来的fail_timeout的时间内不再分发请求给这个server。

配置时只需要在server后面指定max_fails参数即可。值为数值+单位,如10s等同于10秒。fail_timeout一般会结合max_fails使用。

upstream balabala {        server 192.168.31.33 max_fails=3 fail_timeout=10s;        server 192.168.31.237 max_fails=3 fail_timeout=10s;}

总结

负载均衡在提升性能、负载量、可用率等情况下都会有用到,且对于后端整个大方向(包括爬虫在内)的各种场景均有着用武之地。在使用负载均衡时,需要根据实际的业务情况来选择对应的调度方式和调度算法,以达到最大化收益的效果。

另外,像写爬虫的时候,有些场景我们也可以使用到负载均衡,但我们并不一定是要用Nginx来做,而是自己在代码中去实现,所以了解一下基本的思路和原理还是很有必要的。

文档链接、配置样例等资源

由于存在更新的可能性,所以我决定将这些东西放到自动回复中,你可以发送消息【Nginx负载均衡】到公众号【NightTeam】获取。


推荐阅读

1

我这几年踩过的十个坑,每一条都是血泪教训

2

Alfred 有多强悍,我写了个一键上传图片的 workflow 来告诉你

3

手把手教学,如何解决 Git 冲突?

4

中国最懒城市,这里的人不想赚钱,只想躺平




崔庆才

静觅博客博主,《Python3网络爬虫开发实战》作者

隐形字

个人公众号:进击的Coder

长按识别二维码关注





好文和朋友一起看~
浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报