从Nginx看负载均衡

Posted NightTeam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Nginx看负载均衡相关的知识,希望对你有一定的参考价值。

"NightTeam",一个值得加星标从Nginx看负载均衡的公众号。

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

在目前流行的云服务架构体系中,Nginx经常会被用于作为网关、负载均衡器之类的用途。在这其中,负载均衡器这个用途可以说是最为广泛的了,毕竟但凡是个需要横向扩展的后端服务,就都需要有个负载均衡器来顶在前面分发请求,而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

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来做,而是自己在代码中去实现,所以了解一下基本的思路和原理还是很有必要的。

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


夜幕团队成立于 2019 年,团队包括崔庆才(静觅)、周子淇(Loco)、陈祥安(CXA)、唐轶飞(大鱼|BruceDone)、冯威(妄为)、蔡晋(悦来客栈的老板)、戴煌金(咸鱼)、张冶青(MarvinZ)、韦世东(Asyncins|奎因)和文安哲(sml2h3)。

涉猎的编程语言包括但不限于 Python、Rust、C++、Go,领域涵盖爬虫、深度学习、服务研发、逆向工程、软件安全等。团队非正亦非邪,只做认为对的事情,请大家小心。

以上是关于从Nginx看负载均衡的主要内容,如果未能解决你的问题,请参考以下文章

集群和负载均衡的区别 nginx

nginx如何做到TCP的负载均衡

nginx如何做到TCP的负载均衡

高可用架构用Nginx实现负载均衡

从0开始,在Linux中配置Nginx反向代理负载均衡session共享动静分离

Nginx+Tomcat负载均衡集群