为了账号安全,请及时绑定邮箱和手机立即绑定

理解Nginx的反向代理与负载均衡

Nginx 简介

初次发布

Nginx (engine x) 是一个高性能的HTTP和反向代理服务,也是一个IMAP/POP3/SMTP服务。Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。

性能概览

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,Nginx的并发能力在同类型的网页服务器中表现较好。

与旧版本的Apache不同,Nginx不采用每客户机一线程的设计模型,而是充分使用异步逻辑(这一点与NodeJS采取了相同的做法,支持高并发,Nginx在官方测试的结果中,能够支持五万个平行连接,而在实际的运作中,是可以支持二万至四万个平行链接),削减了上下文调度开销,所以并发服务能力更强。整体采用模块化设计,有丰富的模块库和第三方模块库,配置灵活。

Nginx的反向代理

理解反向代理

要使用Nginx的反向代理的特性,首先就是要理解什么是反向代理,为此我们先从最简单的C/S架构说起,C/S架构,也即是Client-Server的架构。而最简单的C/S架构,也即是以单个节点作为后端Server的C/S架构。

C/S架构中的单点Server

在普通的开发当中,单点Server的服务方式非常常见,比如使用Django框架做后台服务的时候,就可以通过简单的run server命令运行起一个临时的服务,接着通过地址就可以访问到这个服务了。当然了,对于Django而言,run server命令只是提供开发周期的调试使用的便捷工具,更一般的情况是通过uWSGI把这个Django服务跑起来,以获取并比run server更好的服务性能。
此时部署整一个架构图如下:
图片描述
对于请求量非常少的服务,这样的部署不会有什么问题,但如果这个服务请求量上来的时候,这样部署的架构就很有问题了。

首先一个很客观的问题就是,如果单从服务器的物理特性来看,这个服务器就不能支持这么高的请求量。这种情况下,就迫使开发者去把服务迁移到一个CPU更强、内存更高,综合性能更好的服务器。通过更换服务器当然可以解决这个问题,在不考虑成本的条件下,总可以找到性能更好的服务器来替换原来的服务器以支撑服务,但这种理想的条件不在本文考虑范围之内。

第二个问题就是,如果服务Server单节点发生了故障,就必然会影响整个服务,因为对于众多的Client客户端都是连接的一个Server服务端,如果这个节点不可用了,势必会影响所有使用了客户端的用户。

为了解决这两个显而易见的问题,就必须提出一种可以横向拓展的部署架构,使得服务可以支撑的请求量可以随着服务的横向拓展而增加。因而就催生了如下的部署架构:

图片描述

基于代理的可横向拓展的C/S架构

在这个部署的架构当中,除了Server节点,还多出了一个叫“Proxy”的节点,那么这个节点是干嘛的呢?
“Proxy”的这个节点,它把他接收的所有的请求都转发到他后面的Server节点当中,然后等待Server节点处理请求,再从Server节点取回执行结果返回到Client。所以,“Proxy”的这个节点,他实际上不处理任何的请求。
我们先来看这样子的架构怎么解决了以上的问题,再来思考“Proxy”的这个角色。
首先是第一个问题,服务器性能不足的问题,这个架构如何解决性能不足的问题呢?在这个架构里面,假设Server节点S1性能到达瓶颈了,不能处理更多的请求了。如果采用上面的这个架构,那么我们可以添加Server节点S2,同时告诉“Proxy”节点,让他把部分原来转发到S1节点的请求转发到我这里来。这样子通过服务分流减少压力的方法就可以解决原来S1节点性能不足的问题了。
图片描述

接着是第二个问题,单点服务器挂掉了怎么办。还是考虑Server节点S1和S2,两者所能够提供的服务是一样的,假设某一个时候S1挂掉了,这时如果有“Proxy”节点的存在,并且“Proxy”节点能够察觉到S1挂掉了的话,那么让“Proxy”节点把原来要转发给S1节点的请求转发到S2进行处理就可以了,这样子通过服务冗余的方法就可以解决原来S1突然挂掉影响服务的问题。

图片描述
解决了这两个问题,接下来我们再从这两个问题里面“Proxy”的角色来理解什么叫做代理。
在第一个问题里面,“Proxy”节点通过服务分流的方法来减少S1的压力,对于原来应该被S1服务的,却由于被“Proxy”节点转发而被S2服务的Client而言,Client他并不知道实际上是由S1处理的还是S2处理的,Client拿到的是一个从“Proxy”节点返回的结果,换言之,Client并不知道自己的请求实际上是被哪个处理的,他只需要往“Proxy”节点发送请求就好了,剩下的工作就由“Proxy”节点去解决。
也就是,“Proxy”节点相当于一个中介,或者说是一个代理,代理Client去寻找实际的Server节点去完成服务。这样子的模式在现实生活也非常常见,在买房子的时候,通常由房产中介帮助你完成和卖者之间的手续,而不需要你亲自去处理这些事情,你只需要协调好自己与中介之间的手续就好了,这里的中介,就和我们的“Proxy”节点所做的工作非常类似。
同样的,在第二个问题里面,也是相似的理解方式,Client并不需要感知S1是否还能正常工作,Client只需要把请求发送给“Proxy”节点,由它去帮忙处理就可以了。
因此,这里面的“Proxy”节点,也就是我们的代理节点。

正向代理与反向代理

理解了什么是“代理”,离我们理解什么是“反向代理”就只差“反向”两个字了,与“反向”相对的,就是“正向”,本质上来讲,代理都是存在于Client和Server之间,但是由于性质不同,所以也分为这两种。在这一个部分,我们将会学习,什么是反向代理,什么是正向代理。
直观的以两个例子去理解什么是正向代理和反向代理。假设由A、B和C三人,他们之间存在借钱的关系。
对于正向代理,理解起来就是:

  1. A需要钱,A知道C由很多钱,想向C借钱
  2. 但是A和C有矛盾,于是A想到通过B去借C借钱
  3. B向C借到钱了,C不知道是A的存在
  4. 这样B就帮助A借到了Z的钱
    在这个过程,B就充当了代理借钱的角色,并且是代替A去借钱的,这样就是正向代理。

图片描述

接着是反向代理:

  1. A需要钱,C有很多钱,A不知道C很多钱
  2. A找B借钱
  3. B知道C有很多钱
  4. B向C借钱,并把借到的钱给A,而没有使用自己的钱借给A
  5. A拿到钱以后,以为钱是B的,A不知道C的存在
    在这个过程当中,B也充当了代理借钱的角色,不过这次不是代替A去借的,而是用C的钱借给A的,换言之即是代替C将钱借给了A,这就是反向代理。
    图片描述
    在例子里面,把A换成Client,把C换成Server,就是计算机的例子了。
两者区别
  1. 服务对象不同
    • 正向代理,代理的是客户端,也就是例子中的A,服务端不知道实际发起请求的客户端
    • 反向代理,代理的是服务端,也就是例子中的C,客户端不知道实际提供服务的服务端

正向代理
图片描述

反向代理

图片描述

  1. 安全模型不同
    • 正向代理允许客户端通过它访问任意网站并且隐藏客户端自身,因此必须采取安全措施以确保仅为授权的客户端提供服务
    • 反向代理都对外都是透明的,访问者并不知道自己访问的是代理,访问者不知道服务节点的存在,认为处理请求的就是代理节点

总而言之,正向代理是从客户端的角度出发,服务于局域网用户,以访问非特定的服务,其中最典型的例子就是翻墙;反向代理正好与此相反,从服务端的角度出发,服务于所有用户,隐藏实际的服务节点,服务节点的架构对用户透明,以代理节点统一对外服务。

Nginx配置反向代理

(文末附完整nginx.conf配置)
为了演示Server节点, 我们使用Django快速启动一个项目,可以快速验证反向代理的配置是否ok。

django-admin startproject server1

接着我们启动一个内网地址,端口为8080。

python manage.py runserver 127.0.0.1:8080

接着配置Nginx,在Nginx的配置里面,主要是upstream块,和server块。

upstream httpserver{
    server 127.0.0.1:8080;
}

然后在server块配置转发到upstream。

location / {
    proxy_pass http://httpserver;
}

如此,便完成反向代理的配置,接着通过命令nginx -s reload重新加载配置文件即可。若存在多个Server节点,在upstream域添加多个Server的地址即可。
我们另起一个django项目,服务启动在8081端口。
接着更新upstream的配置。

upstream httpserver{
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;
}

就可以完成两个节点的反向代理。

Nginx代理中的负载均衡

提到Nginx的反向代理,不得不提的就是Nginx的负载均衡,Nginx支持丰富的负载均衡算法,以提高使用Nginx部署的服务的综合性能。
在介绍负载均衡之前,我们先来看为什么需要负载均衡。

为什么需要负载均衡

需要负载均衡的一个最简单的诉求就是由Proxy代理的多个Server节点的性能并不完全相等的,也即是不同Server节点,他们处理请求的速度和容量都不相同,这就要求Proxy在转发的时候,能够因Server而异,给不同Server转发适合其处理性能的请求量,以防Server节点低负载浪费资源,或者过高负载引起宕机。
其次就是如果没有负载均衡算法,我们就无法避免很多请求被调度到一个Server节点从而把这个Server节点压垮的情况发生,因为我们没有一种机制保证不同Server节点所处理的请求量数均匀的或者是合理的。

负载均衡的算法

对服务器性能的考究有很多变量需要去关注:包括CPU的核数和频率、内存的容量速度、网络的质量、内核最大文件描述符、服务器的其他服务影响等等,因此在负载均衡算法上,也是非常丰富的,以下是几种常见的负载均衡算法。

随机算法

随机,顾名思义,就是随机概率的调度,每一个请求通过随机算法调度到不同的服务器,若调用量越大,不同服务器处理的请求数就越平均。
这种算法看是公平的,但是没有考虑到不同节点性能的差异,同时对随机算法也有一定的要求,要求随机算法在大量的随机事件后能够得到一个均匀的随机结果。

轮询算法

轮询算法也非常容易理解,请求到来的时候,按顺序把每个请求转发到多个Server节点当中,保证每个服务节点所处理的请求量都是均匀的。
虽然这种算法很公平,但是没有考虑到不同节点性能的差异。

最少连接算法

在多个服务器当中,优先将请求调度到处理连接数少的服务器上,即使每台服务器的处理能力各不相同,也能够在一定程度上降低服务器的负载。
这种算法能够考虑不同节点性能之间的差异,处理连接数在一定程度上能够反映其性能的差异,不过并非绝对,在一些条件下,其他原因也可限制处理连接数,比如网络性能、带宽压力等等。

权重轮询算法

这种算法要求在转发节点配置Server的时候,添加Server节点的权重信息,以表示不同Server节点的处理性能,在转发节点转发请求的时候,将按照权重的大小来分配转发的流量。
这种算法可以合理的转发请求,开发人员可以按照Server节点的性能参数、部署的服务来修改Server节点的权重,使得每一个Server节点都能够得到最合理的使用。
当然,除了开发人员手动配置权重以外,更为智能的一种方式是由代理的Server节点主动上报自己的能力,转发节点在接收到相关信息的时候再权衡不同Server节点的能力,按照权重进行转发。这样就不用人工介入,提高效率。不过这种方式对转发节点和Server节点都有一定的要求,需要开发者去开发相关的逻辑。

使用Nginx配置负载均衡

理解不同的负载均衡算法以后,接下来就是对Nginx配置负载均衡了。Nginx支持普通轮询算法和权重轮询算法的负载均衡配置。

轮询算法的负载均衡

其实基于普通轮询算法的负载均衡,在前面我们已经实现了,只不过当时重点不在负载均衡,所以没有提及。

upstream httpserver{
    server 127.0.0.1:8080; 
    server 127.0.0.1:8081;
}

这就是轮询算法的负载均衡的配置,在upstream配置块的多个server节点默认都有权重weight=1,也即是每个服务节点权重一样了。

权重轮询算法的负载均衡

在普通轮询算法的基础上,我们可以进行权重轮询算法的配置,配置如下:

upstream httpserver{
    server 127.0.0.1:8080 weight=5;   # 权重为5
    server 127.0.0.1:8081 weight=2;   # 权重为2
    server 127.0.0.1:8082; # 默认权重为1
}

如此添加weight关键字即可,接着你就可以在日志中看到,请求大致会以5:2:1的比例落到三个server中,这就是权重轮询算法的负载均衡。

总结

Nginx是一个高性能的部署服务端。支持HTTP部署、代理和反向代理、负载均衡、邮件服务等丰富的功能,是非常出色的一款软件,本文介绍了反向代理和负载均衡的概念,并介绍了使用Nginx配置反向代理和负载均衡的过程,希望看完这篇文章,大家对Nginx和相关概念有进一步的理解。
最后打个小广告,实战课程《Django 2.0+微信小程序打造个人助手》,除了使用Django和微信小程序这两款热门技术去开发个人助手以外,针对Django的服务高可用部署等知识点也有详细的介绍,感兴趣的同学可以参加学习,我们一起来探讨哦。


附:最简单Nginx的完整配置

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
        worker_connections 768;
}

http {

        ##
        # Basic Settings
        ##

        sendfile on;
        tcp_nopush on;
        tcp_nodelay on;
        keepalive_timeout 65;
        types_hash_max_size 2048;

        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        ##
        # SSL Settings
        ##

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
        ssl_prefer_server_ciphers on;

        ##
        # Logging Settings
        ##

        access_log /var/log/nginx/access.log;
        error_log /var/log/nginx/error.log;

        ##
        # Gzip Settings
        ##

        gzip on;
        gzip_disable "msie6";

        include /etc/nginx/conf.d/*.conf;
        include /etc/nginx/sites-enabled/*;

        upstream httpserver {
                server 127.0.0.1:8080 weight=5;
                server 127.0.0.1:8081 weight=2;
        }

        server {
                listen 80;
                server_name .dongcia.com;
                charset utf-8;
                location / {
                       proxy_pass http://httpserver;
                }
        }
}

···················
欢迎关注课程:

点击查看更多内容
85人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
7278
获赞与收藏
1767

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消