跳转至

Nginx 反向代理与负载均衡


2013-05-13 by dongnan

开始之前

什么时候需要使用反向代理?

我这里有个例子,早期的WEB服务器使用的是HTTP/1.0协议,是不支持keep-alive功能的, 为了解决这个问题当时主流的解决方案是使用nginx做反向代理(支持HTTP/1.1当时优势之一),为客户端浏览器链接HTTP/1.1, 并为后端WEB服务器链接HTTP/1.0,经过nginx代理后就可以愉快的使用keep-alive功能了。

为什么HTTP/1.1协议中的keep-alive功能这么重要(HTTP/2笑而不语),这是因为HTTP/1.0开销很大而速度又很慢, HTTP/1.0为每一个请求/响应都打开一个TCP连接,打开一个TCP连接需要多次往返消息传递因此速度较慢。

HTTP/1.1引入了持久连接(俗称长连接)的概念: 底层的TCP连接可以通过Connection头部来被部分控制。 使得对于同一个服务器的请求可以继续在该连接上完成后续请求。 (一图胜千言)

环境描述

八卦完HTTP/1.0协议,让我们回到nginx反向代理上,由于时间太过久远找不到当时的nginx代理配置文件, 下面的示例是以一个python项目来讲解nginx如何配置反向代理。两个案例功能类似都是反向代理后端服务器,所以能够说明问题。

说明下项目环境,这个python项目是基于django框架开发的,django框架内置web服务可以直接运行项目, 不过django官方明确内置的web服务适用于开发环境,不建议直接用于生产环境, 更不用说nginx静态文件处理能力是"这些框架"内置web服务所望尘莫及的。所以这里增加了nginx用于反向代理django内置web服务,拓扑图如下。

配置文件

cat /etc/nginx/conf.d/default.conf

server{

    #... 其它配置项省略

    location ^~ /static {
        alias /django-demo/static/;
        expires 30d;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;

        proxy_pass http://unix:/var/run/django.socket;
        #proxy_pass http://upstream_server:8000;
    }

    #... 其它配置项省略

}

示例配置文件中,使用proxy_set_header指令设置Http头部,最后使用proxy_pass指令转发请求到后端服务器。

  • X-Forwarded-For,用来记录代理服务器的地址,每经过一个代理该字段会追加上一个记录。例如:6.6.6.6, 8.8.8.8
  • X-Real-IP,同样用来记录代理服务器的地址,但是和上面的不同它不把记录追加到尾部,而是直接替换掉。
  • Host,为后端服务器重新设定Host主机头,$host变量的值在请求包含"Host"请求头时为"Host"字段的值,在请求未携带"Host"请求头时为虚拟主机的主域名。
  • remote_addr,表示客户端地址,注意如果存在代理服务器,则是最后的代理服务器地址,而非真正的客户端地址。
  • proxy_pass,经过调整头部信息后,将请求转发到后端的服务器。

Nginx 指令

proxy_set_header 指令

语法: proxy_set_header field value
默认值: 
proxy_set_header Host $proxy_host;
proxy_set_header Connection close;
使用字段: http, server, location字段。
功能: 这个指令允许重新定义或者添加发往后端服务器的请求头。
value可以包含文本、变量或者它们的组合。

proxy_pass 指令

语法: proxy_pass URL
默认值: none
使用字段: location, location中的if字段。
功能: 这个指令设置被代理服务器的地址和被映射的URI。
地址可以使用主机名或IP加端口号的形式。
proxy_pass http://localhost:8000/uri/;
或者unix socket,路径在unix关键字的后面指定,位于两个冒号之间。
proxy_pass http://unix:/tmp/backend.socket:/uri/;

负载均衡模块

这个模块为后端的服务器提供简单的负载均衡: 轮询(round-robin)和连接IP(client IP)。

nginx部分配置文件:

upstream backend  {
  server backend1.example.com weight=5;
  server backend2.example.com:8080;
  server unix:/tmp/backend3;
}

server {
  location / {
    proxy_pass  http://backend;
  }
}

ip_hash 指令

语法: ip_hash
默认值: none
使用字段: upstream
这个指令将基于客户端连接的IP地址来分发请求。
哈希的关键字是客户端的C类网络地址,这个功能将保证这个客户端请求总是被转发到一台服务器上,
但是如果这台服务器不可用,那么请求将转发到另外的服务器上,这将保证某个客户端有很大概率总是连接到一台服务器。
无法将权重(weight)与ip_hash联合使用来分发连接。如果有某台服务器不可用,你必须标记其为“down”.

nginx部分配置文件:

upstream backend {
  ip_hash;
  server   backend1.example.com;
  server   backend2.example.com;
  server   backend3.example.com  down;
  server   backend4.example.com;
}

server 指令

语法: server name [parameters]
默认值: none
使用字段: upstream
指定后端服务器的名称和一些参数,可以使用域名,IP,端口,或者unix socket。如果指定为域名,则首先将其解析为IP。
weight = NUMBER - 设置服务器权重,默认为1。
max_fails = NUMBER - 在一定时间内(这个时间在fail_timeout参数中设置)检查这个服务器是否可用时产生的最多失败请求数,默认为1,将其设置为0可以关闭检查,这些错误在proxy_next_upstream或fastcgi_next_upstream(404错误不会使max_fails增加)中定义。
fail_timeout = TIME - 在这个时间内产生了max_fails所设置大小的失败尝试连接请求后这个服务器可能不可用,同样它指定了服务器不可用的时间(在下一次尝试连接请求发起之前),默认为10秒,fail_timeout与前端响应时间没有直接关系,不过可以使用proxy_connect_timeout和proxy_read_timeout来控制。
down - 标记服务器处于离线状态,通常和ip_hash一起使用。
backup - (0.6.7或更高)如果所有的非备份服务器都宕机或繁忙,则使用本服务器(无法和ip_hash指令搭配使用)。

nginx部分配置文件:

upstream  backend  {
  server   backend1.example.com    weight=5;
  server   127.0.0.1:8080          max_fails=3  fail_timeout=30s;
  server   unix:/tmp/backend3;
}

注意:如果你只使用一台上游服务器,nginx将设置一个内置变量为1,即max_failsfail_timeout参数不会被处理。建议使用多台上游服务器。

upstream 指令

语法: upstream name { … }
默认值: none
使用字段: http
这个字段设置一群服务器,可以将这个字段放在proxy_pass和fastcgi_pass指令中作为一个单独的实体,
它们可以是监听不同端口的服务器,并且也可以是同时监听TCP和Unix socket的服务器。
服务器可以指定不同的权重,默认为1。

nginx部分配置文件:

upstream backend {
  server backend1.example.com weight=5;
  server 127.0.0.1:8080       max_fails=3  fail_timeout=30s;
  server unix:/tmp/backend3;
}

请求将按照轮询的方式分发到后端服务器,但同时也会考虑权重。 在上面的例子中如果每次发生7个请求,5个请求将被发送到backend1.example.com,其他两台将分别得到一个请求。

如果有一台服务器不可用,那么请求将被转发到下一台服务器,直到所有的服务器检查都通过。 如果所有的服务器都无法通过检查,那么将返回给客户端最后一台工作的服务器产生的结果。

参考

回到页面顶部