一概念解析

对于新事物的理解,最好的方式就是从概念入手,先从Nginx的名称开始来分解这个神秘的引擎。
Nginx,是engine X的缩写,发音也是’engine x’,2004年由俄罗斯大神伊戈尔·赛索耶夫开发,提供了高性能而易用的HTTP反向代理功能。后期还加入了TCP的反向代理支持。
最初Nginx是为了解决早年的C10K问题而生的。什么是C10K呢?C代表Client客户、10K代表10000,即一台服务器同时保持1万链接。这在当时是一个非常棘手的问题。
通过Google搜索Nginx会得到以下解释:

Nginx是异步框架的网页服务器,也可以用作反向代理、负载平衡器和HTTP缓存。 从这句话中,我们可以得到下面几个关键:

  • 异步框架
  • 反向代理
  • 负载均衡
  • HTTP缓存

本专题文章将分别从这几个关键词来解读Nginx的强大之处。本文先介绍Nginx特性之反向代理及其配置实现。

二、反向代理

正向代理:正向代理:是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端才能使用正向代理。

反向代理:反向代理服务器位于用户与目标服务器之间,但是对于用户而言,反向代理服务器就相当于目标服务器,即用户直接访问反向代理服务器就可以获得目标服务器的资源。同时,用户不需要知道目标服务器的地址,也无须在用户端作任何设定。反向代理服务器通常可用来作为Web加速,即使用反向代理作为Web服务器的前置机来降低网络和服务器的负载,提高访问效率。

什么是反向代理

代理在生活中非常常见,房屋中介是代理、终端零售是代理、选举代表是代理。这些代理都可以帮助需求方减轻很多工作的复杂度,提升效率和体验。
网络里的代理服务是什么样子,我想各位读者也非常清楚,这里再简单回顾一下:假设我们想在公司上网看B站的视频,而规范的公司出于安全和办公效率的考虑,设置了网络策略,不允许访问视频网站,聪明的程序员不可能被这些事情所打败,只要购买一台云服务,搭建代理服务,把浏览器设置上代理,就可以轻松访问视频网站。这就是常见的代理。
Nginx - 图1
那么现在问题来了:“代理”大家都懂,这里为什么强调是反向代理呢?难道还有正向代理?答案是肯定的。
正向代理就是大家常见的代理,以请求端也就是客户端的角度为正向,用户发出请求经过的代理,称为“正向代理”。这时是用户主动选择使用代理。
反向代理:先看图再解释。
Nginx - 图2
主动权被反转,原来是客户端选择代理,现在是代理选择服务端节点。由于控制权的反转,这样的代理被称为“反向代理”。

反向代理的优点

1)保护服务安全

  • 隐藏服务节点的IP;
  • 将服务节点置于防火墙之后,避免直接攻击业务节点服务器。

2)服务节点更专注于业务,同时提升性能

  • 由于有反向代理的存在,可以让反向代理服务器去实现比如https、gzip压缩等与业务无关的功能;
  • 提供动静态分离,将静态文件发往静态服务器或本地文件系统,避免业务节点处理这些与业务无关的请求;
  • 提供缓存机制,将一些短时间内不会变化的动态内容,在反向代理服务器这层增加缓存,降低业务服务器的请求量;
  • 由于控制权在代理服务这边,完全可以根据服务节点的性能动态分配请求,做到服务节点性能最佳。

正是由于Ngxin引入了反向代理的特性,让请求和响应都要经过Nginx,因此给Nginx带来了非常多的可能。比如负载均衡、HTTP缓存等。
Nginx - 图3

三、反向代理的配置

Nginx中关于反向代理的配置相当简单。

3.1 配置一个单节点的反向代理

  1. # simple reverse-proxy
  2. server {
  3. listen 80;
  4. server_name big.server.com;
  5. access_log logs/big.server.access.log main;
  6. # pass requests for dynamic content to rails/turbogears/zope, et al
  7. location / {
  8. proxy_pass http://127.0.0.1:8080;
  9. }
  10. }

这里定义的规则是以big.server.com域名来请求Nginx的80端口,会将请求代理到127.0.0.1:8080上。

3.2 配置一组反向代理的服务节点。

1)配置一组反向代理并命名。

  1. upstream big_server_com {
  2. server 192.168.0.1:8000;
  3. server 192.168.0.1:8001;
  4. }

这里定义了upstream,这个upstream可以理解为上传流,之所以叫上传是因为:从服务器获取数据叫下载,向服务器发送数据就叫上传,这里是将数据请求发送到服务节点,所以叫上传。
给这组服务节点命名为big_server_com,其中包括两个节点,分别是:192.168.0.1:8000和192.168.0.1:8001。
2)配置规则:让满足的请求能够反向代理到这组服务节点中。

  1. server {
  2. listen 80;
  3. server_name big.server.com;
  4. access_log logs/big.server.access.log main;
  5. location / {
  6. proxy_pass http://big_server_com;
  7. }
  8. }

这里定义的规则是以big.server.com域名来请求Nginx的80端口,请求url是以/为后缀的所有请求,都会转发到前面定义的名字为big_server_com的服务节点组。

一、Nginx的负载均衡策略

负载均衡就是将请求“均衡”地分配到多台业务节点服务器上。这里的“均衡”是依据实际场景和业务需要而定的。
对于Nginx来说,请求到达Nginx,Nginx作为反向代理服务器,有绝对的决策权,可以按照规则将请求分配给它知道的节点中的一个,通过这种分配,使得所有节点需要处理的请求量处于相对平均的状态,从而实现负载均衡。
Nginx支持的负载均衡策略很多,比较重点的如下:

  • round robin(轮询)
  • random(随机)
  • weight(权重)
  • fair(按响应时长,三方插件)
  • url_hash(url的hash值)
  • ip_hash(ip的hash值)
  • least_conn(最少连接数)

这么多的策略,非常不利于记忆和选择,我们不妨将这些常见的策略归类,分而化之,方便挑选。

第一类 最佳实现

  • weight(权重)
  • random(随机)

最佳实践,其实就是最常见、最普通的默认配置,当然也是在一定程度上最好用的配置。不知道用什么方式的时候,就可以选择用这一类型。
轮询不用多说。这里的随机,其实在大量请求的情况下,按照概率的理论等同于轮询的方式。
轮询配置参考:

  1. #默认配置就是轮询策略
  2. upstream server_group {
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. }

随机配置参考:

  1. upstream server_group {
  2. random;
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. server backend3.example.com;
  6. server backend4.example.com;
  7. }

第二类 性能优先

  • weight(权重)
  • fair(按响应时长,三方插件)
  • least_conn(最少连接数)

让业务节点中性能更强的机器得到更多请求,这也是一个比较好的分配策略。
什么是性能更好的机器?这个问题也有很多的维度去考量。

  • 从经验或硬件上分为高权重、低权重的机器。
  • 按照节点请求的响应时长来决定是多分配请求,还是少分配请求。
  • 按照保持的连接数。一般来说保持的连接数越多说明处理的任务越多,也是最繁忙的,可以将请求分配给其他机器处理。

权重的配置参考:

  1. upstream server_group {
  2. server backend1.example.com weight=5;
  3. #默认为不配置权重为1
  4. server backend2.example.com;
  5. }

响应的时长(fair)配置参考:需要在Nginx编译时加入nginx-upstream-fair模块。

  1. upstream server_group{
  2. fair;
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. server backend3.example.com;
  6. server backend4.example.com;
  7. }

最少连接数(least_conn)配置参考:

  1. upstream server_group {
  2. least_conn;
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. }

第三类 保持稳定

  • ip_hash
  • url_hash

很多请求都是有状态的,上一次请求到哪个业务节点,这次还要请求到哪台机器。比如常见的session就是这样一种有状态的业务。
这里Nginx提供了按照客户端ip的hash来作为用户的标示分配、url的hash作为分配标示的规则。本质上还是要找到用户的请求中不变的要素,抽离出来,这样就可以进行分配了。
ip_hash配置参考:

  1. upstream server_group {
  2. ip_hash;
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. }

url_hash配置参考:

  1. upstream server_group{
  2. hash $request_uri consistent;
  3. server backend1.example.com;
  4. server backend2.example.com;
  5. server backend3.example.com;
  6. server backend4.example.com;
  7. }

二、Nginx支持一致性哈希进行分配

Nginx支持一致性hash进行分配,也就是配置中consistent。
什么是一致性hash?为什么要引入这个机制?在生产环境下,业务节点经常会出现增加或减少的情况,就算这种增加或减少都是被动的,也可能会对hash分配产生影响。如何能够做到尽量减少影响呢?这时一致性hash被发明出来。
一致性hash解决两个问题:

  • 分配特别不均匀;
  • 节点变动除了对分配到这个节点上的请求有影响,还会导致其他节点上的请求重新分配。

1)如何解决分配不均的问题
将原来的每一个节点复制出N个虚拟节点,并且给这些虚拟节点都起个名字。
Nginx - 图4
比如原来有5个节点,分配的时候经常会不均匀,现在每个节点都虚拟出N个节点,就是5*N个节点,会极大降低分配不均匀的情况。下面就要说说如何分配的问题了。
2)如何解决节点变动的问题
一致性哈希的基本思想:

  • 定义一个[0,(2^32)-1]的数值空间。相当于取长度从 0到2^32-1 的线段。
  • 节点映射到线段上。将每个节点,包括虚拟节点,都通过hash算法得到数值,然后映射到这个取值区间上。

如下图。
Nginx - 图5

  • 计算数据的Hash值。把请求中的关键字符串通过hash算法得到一个数值,在线段中找到一个位置,如果算出来的数值大于2^32-1则认为是0。按照这个规则,其实是把这个线段首尾相连形成一个环,所以也叫hash环。
  • 数据节点在线段上找归属的节点。沿着这个线段向右找到离得最近的节点,并把该节点作为这个数据的归属节点。

Nginx - 图6
下面再来看节点的变化对一致性Hash的影响。

  • 节点减少:比如NodeA突然故障了,原来分配到其他节点上的数据不会发生变化,只有分配到NodeA上的数据会重新找离它们最近的点,从而减少了hash重新分配的数量。这也是一致性hash最大的优势。
  • 节点增加:比如现在请求量抖增,需要增加节点降低负载。当新加入节点NodeE时,NodeE及它的一群虚拟节点会根据hash值分配在hash环上。这时,大部分数据再根据一致性哈希规则找其归属的Node节点都不会发生变化,只有那些值计算出来发现离NodeE更近的数据发生了变化,但数量毕竟是有限的,减少了因为节点增加造成的影响。

    三、故障节点摘除与恢复

    先看看经典配置,再详细解释。
    1. upstream server_group {
    2. server backend1.example.com ;
    3. server backend2.example.com max_fails=3 fail_timeout=30s;
    4. server backup1.example.com backup;
    5. }

    max_fails=number

    这个参数决定了多少次请求后端失败后会暂停这个业务节点,不再给它发新的请求,默认值是1。此参数需要配合fail_timeout一起用。
    题外话:如何定义失败,有很多种类型,这里因为主要处理HTTP代理,所以更关注proxy_next_upstream。
    proxy_next_upstream:主要定义了当服务节点出现状况时,会将请求发给其他节点,也就是定义了怎么算作业务节点失败。

    fail_timeout=time

    决定了当Nginx认定这个节点不可用时,暂停多久。不配置默认就是10s。
    把上面两个参数联合起来考虑就是:当Nginx发现发送到这个节点上的请求失败了3次的时候,就会把这个节点摘除,摘除时间是30s,30s后才会再次发送请求到这个节点上。

    backup

    类似于switch语句中的default,当主要节点都挂了的时候,会把请求打到这个backup节点。这是最后一个救兵了。