---
order: 10
---
# 反向代理和负载平衡

反向代理服务器位于用户与目标服务器之间，对于用户而言，反向代理服务器就相当于目标服务器，用户直接访问反向代理服务器就可以获得目标服务器的资源。

![反向代理拓扑](./images/proxy-2020-03-06-13-08-00.png)

## 反向代理的作用和优点 {#why-use-reverse-proxy}

反向代理服务器通常可用来作为Web加速，使用反向代理作为Web服务器的前置机来降低网络和服务器的负载，提高访问效率。同时，用户不需要知道目标服务器的地址，也无须在用户端作任何设置。

1. **提高了内部服务器的安全**。作为应用层防火墙，隐藏了内部服务器的地址和端口，只开放80以及443端口，为网站提供对基于Web的攻击行为（例如DoS/DDoS）的防护，更容易排查恶意软件，并统一提供加密和SSL加速。
2. **加快了对内部服务器的访问速度**。减少真实服务器与客户端的直接交互，对于静态内容及短时间内有大量存取请求的动态内容提供快取服务，对一些内容进行压缩，以节约频宽或为网路频宽不佳的网路提供服务。
3. **节约了有限的IP资源**。公网分配的IP地址数目是有限的，如果每个服务器有分配-个公网地址，那是不可能的，通过反向代理技术很好的解决了IP地址不足的问题。
4. **负载平衡**。对多个节点的真实服务器集群实现负载均衡策略。

对于生产环境，推荐配置反向代理，反向代理与真实服务器最好使用不同的机器部署。常用反向代理软件版本有nginx、apache httpd、haproxy以及基于以上的变种等，本文主要介绍nginx反向代理设置。

## 基于nginx的反向代理 {#nginx}

### 安装启动 {#nginx-install}

推荐使用nginx最新稳定版的源码进行编译安装，nginx安装，更多编译参数可参考: [编译参数](http://nginx.org/en/docs/configure.html)。

1. 从官方下载最新稳定版本，下载地址: `http://nginx.org/en/download.html`
2. 安装依赖环境: `yum -y install gcc gcc-c++ glibc openssl-devel zlib-devel`
3. 解压后进入目录编译，参数: `./configure --prefix=/usr/local/nginx --with-http_ssl_module && make && make install`
4. 程序添加到环境变量: `ln -sf /usr/local/nginx/sbin/nginx /usr/bin/`
5. 配置文件路径为`/usr/local/nginx/conf/nginx.conf`，默认备份为`nginx.conf.default`，所以无需备份可直接修改
6. 常用的操作（启动，停止，重启，检测配置文件语法）: `nginx`, `nginx -s stop`, `nginx -s reload`, `nginx -t`

### 基本配置 {#nginx-config}

更多功能可以参考官方手册：[nginx反向代理](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/)

```nginx
worker_processes  8;                #nginx工作进程数,可设置与CPU核心数一致
events {
    use epoll;                      #使用epoll io多路复用模型
    worker_connections  51200;      #单个进程可以处理的并发数。总并发数 = worker_porcesses * worker_connections
}
http {
    server_tokens off;              #关闭显示nginx的版本，防止针对版本攻击
    keepalive_timeout 75s;          #设置keepalive最大超时时间，75s为nginx最大值
    proxy_buffering on;             #开启代理缓冲区，默认开启
    proxy_buffer_size 32k;          #响应头缓冲区大小
    proxy_buffers 4 128k;           #网页内容缓冲区个数为4，单个大小为128k
    proxy_busy_buffers_size 256k;   #缓冲向客户端传输的数据
    proxy_set_header Connection ""; #设置Connection为空串，以禁止传递头部到后端
    proxy_http_version 1.1;         #开启对http1.1支持   
    proxy_redirect off;
    #设置请求头中的值，把客户端请求的Host转发给真实服务器
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    #设置请求头中的值，把用户请求的server端口转发至后端webserver
    proxy_set_header X-Forwarded-Port $server_port; 
    #避免https重定向到http，通过设置X-Forwarded-Proto头，传递真实协议到web服务器，避免客户端-->代理服务器-->web服务器之间协议不同。
    proxy_set_header X-Forwarded-Proto $scheme;
    client_max_body_size           300m;    #最大客户端发送的body大小
    client_body_buffer_size        512k;    #读取客户端请求体的缓存大小，默认16K，避免太小存入临时文件
    #-------------------文件压缩(性能)---------------------
    #代理默认对已经压缩过的文件不会再处理
    gzip on;                                #开启gzip传输压缩
    gzip_min_length 1k;                     #小于1k不压缩
    gzip_comp_level 2;                      #压缩级别1-9
    #常用的压缩类型
    gzip_types text/plain application/json;  
    #-----------------------------------------------------
    include /usr/local/nginx/conf.d/*.conf;
}  
```

## 安全和性能调优{#performance-secure}

### 超时配置（安全）{#timeout}

nginx的超时参数默认值都比较大，正常使用没有问题。但过大的超时时间也会带来安全隐患，这里给出推荐配置。建议参数增加到http段对全局生效。

```nginx
#代理读后端响应数据时，连续两次读操作的超时时间
proxy_read_timeout 10s; 
#代理发送后端数据时，连续两次写操作的超时时间
proxy_send_timeout 10s; 
#代理连接到后端服务器的超时时间
proxy_connect_timeout 2s;
#客户端发送request_body时两次数据包的超时时间
client_body_timeout 5s;
#客户端发送完整header的超时时间
client_header_timeout 3s;
#代理发送给客户端响应时，连续两次写操作的超时时间
send_timeout 30s;
```

### 自定义错误页面（安全）{#error\_page}

通常502、503、504等错误提示会暴露代理版本或服务器内部信息，且内容对用户也不够友好，那么可以自定义错误页面来解决这些问题

```nginx
#在server段下增加一个错误页面的location，推荐内容为系统繁忙，请稍后访问
#在server段下增加一个跳转的location，通过此处跳转到错误页面
#在server段下增加错误代码的响应uri
location /INFO/info.html {
    root html;
    index index.html;
}
location @info {
    rewrite ^/.*$ /INFO/info.html break;
}
error_page 502 @info;
error_page 503 @info;
error_page 504 @info;
```

### 过滤请求（安全）{#filter-req}

```nginx
#在server段下增加if判断，对指定的请求uri、客户端地址等参数进行过滤，匹配或者不匹配规则时执行动作，来达到安全控制
if ( $request_uri !~ "^/PROJECT($|/.*)" ) {
	rewrite ^ /PROJECT/ permanent;
}
if ( $remote_addr ~ "1.1.1.1|2.2.2.2" ) {
   rewrite ^ /PROJECT/ permanent;
}
```

### 运维状态（维护）{#maintance}

以下示例根据过滤请求实现ip白名单的运维。当代码生效的时候，对于ip是1.1.1.1和2.2.2.2或者uri是test\_zsc开头的项目则正常访问，其他客户端ip或者项目禁止访问

```nginx
set $maintance 0;
if ( $request_uri != "/maintance.html" ) {
   set $maintance "1";
}
if ( $remote_addr ~ "1.1.1.1|2.2.2.2" ) {
   set $maintance "0";
}
if ( $request_uri !~ "^/test_zsc($|/.*)" ) {
  set $maintance "0";
}
if ( $maintance = "1" ) {
   rewrite ^/.* /maintance.html break;
}
location /maintance.html {
    root html;
    index index.html;
    #设置超时时间，使短时间的维护后只刷新浏览器而无需清理浏览器缓存也能跳转到正常页面
    expires 1m;
}
```

### HTTP/2（性能）{#http2}

HTTP2.0除了新的二进制、header压缩、服务端推送外，还有多路复用的特性，使得多个请求可以在一个tcp连接上并行执行，减少阻塞带来的延迟

增加模块配置参数

```sh
--with-http_v2_module
```

```nginx
#虽然http2.0没有强制要求使用https，但目前主流的chrome、firefox等浏览器只支持https环境使用http2.0
server {
    listen 443 ssl http2 default_server;
 
    ssl_certificate     server.crt;
    ssl_certificate_key server.key;
    ...
}
```

### 本地多IP（性能）{#multi-ip}

当nginx作为代理服务器时，会作为客户端请求后端服务器，但在高并发环境下因为tcp挥手超时时间较长，可能会发生本地端口不足的情况，可在本机配置多ip来解决此问题

```nginx
#在http段中增加如下配置
split_clients "$remote_addr" $split_ip {
    50%  1.1.1.1;
    50%  1.1.1.2;
}
#在server段下增加如下配置
proxy_bind $split_ip;
```

### 缓存配置（性能）{#cache}

配置代理服务器缓存可以使请求动静分离，让后端的服务器只负责处理动态内容，减小后端服务器压力、节省内网交互带宽，在高并发下时有明显性能提升

```nginx
#在httpd段下增加如下配置
proxy_cache_path /usr/local/nginx/temp/cache_temp levels=1:2 keys_zone=cache_one:500m inactive=1d max_size=30g;
#在server段下增加如下配置
#缓存的文件类型，可以根据需要调整
location ~ ^/PROJECT/(.*)(gif|jpg|png|css|js|flv|ico|swf|woff|svg|ico)$ {  
    #当有set-cookie请求头时，仍然缓存
    proxy_ignore_headers Set-Cookie;
    #当有set-cookie请求头时，不会传给客户端
    proxy_hide_header Set-Cookie;
    #作为缓存key的参数。精确到args可以保证相同uri不同参数的请求是正确的
    proxy_cache_key $scheme$proxy_host$uri$is_args$args;
    proxy_pass    http://lb;
    proxy_cache cache_one;
    proxy_cache_valid 200 302 7d;
    proxy_cache_valid 301 1d;
    proxy_cache_valid any 1m;
    expires 30d;  
}
```

### 限流（安全/性能）{#limit-req}

合理的限流策略，可以保证环境在高并发下平稳运行，保障后端各个服务器安全以及其他用户体验

```nginx
#在http段下增加以下配置，以uri作为限速规则，速率为每秒1000个请求
limit_req_zone $uri zone=limit:30m rate=1000r/s;
#在server段对指定的uri限速，burst表示最大峰值并发为1000。当客户端请求此uri时，1秒最多请求1000次，后续每秒恢复1000个请求次数
location ~ ^/PROJECT/public/login/login_jkm\.action.* {
	limit_req zone=limit burst=1000 nodelay;
	proxy_pass http://lb;
}
```

### 操作系统调优（性能）{#sys-performance}

Linux内核调优

```sh
#在sysctl.conf中增加如下配置

#本地程序可使用的端口从1024开始到65000，默认从32768开始
net.ipv4.ip_local_port_range = 1024 65000
#TCP保持在FIN-WAIT-2状态的时间，及时关闭连接释放端口，默认60
net.ipv4.tcp_fin_timeout = 30
#开启SYN Cookies。当出现SYN等待队列溢出时，启用cookies来处理，可防范少量SYN攻击
net.ipv4.tcp_syncookies = 1
#本地对外的syn连接最大重试次数，可调整为1-2，默认为5
net.ipv4.tcp_syn_retries = 1
#当keepalive打开的情况下，TCP发送keepalive消息的频率，及时关闭没有数据的链接，默认2小时
net.ipv4.tcp_keepalive_time = 1200
#系统尽可能优先使用内存，而不是交换分区
vm.swappiness = 1
#系统所有进程可打开的文件数之和
fs.file-max = 6815744
#系统同时支持最大的异步io请求数
fs.aio-max-nr = 1048576
```

## Tengine的会话粘滞{#tengine}

Nginx开源版的负载均衡策略支持轮训、源地址哈希等，对有状态的http请求和nat客户端容易出现负载不平衡或会话不保持的情况，所以针对后端有多台web组成集群的场景，推荐使用Tengine的会话粘滞模块。

Tengine是淘宝基于Nginx开发的高性能Web服务器，配置完全兼容Nginx，并针对大访问量的需求，添加了很多功能和特性，可以查看[官方文档](http://tengine.taobao.org/documentation_cn.html)了解更多功能。

### 安装Tengine{#tengine-download}

点击[这里](http://tengine.taobao.org/download_cn.html)进入官网下载，并参考[安装启动Nginx](#nginx-install)进行安装。

### 使用session\_sticky模块{#session-sticky}

增加模块配置参数

```sh
--add-module=modules/ngx_http_upstream_session_sticky_module/
```

```nginx
#在http段中增加后端web节点，maxidle设置session cookie最大超时时间，fallback为on表示当前机器挂了后自动重试其他机器
upstream lb {
    server 1.1.1.1:8080;
    server 1.1.1.2:8080;
    session_sticky fallback=on maxidle=1800;
}
```

### 主动健康检查{#health-check}

主动健康检查可以定时检查每个后端web服务器，如果指定请求的状态码不符合预期，那么会主动标记服务器宕机，后续的正常请求不会发送至此机器，直到下次请求时响应预期的状态码

增加模块配置参数

```sh
--add-module=modules/ngx_http_upstream_check_module/
```

```nginx
#在upstream中增加如下配置
#健康监测请求的超时时间为4500ms，如果连续5次请求状态不是2xx或者3xx则认为节点宕机，如果宕机后连续5次响应符合预期则认为节点恢复正常
check interval=2000 rise=5 fall=5 timeout=4500 type=http;
check_http_send "GET /PROJECT/api/sys/health-info HTTP/1.0\r\nConnection: keep-alive\r\n\r\n";
check_keepalive_requests 100;
check_http_expect_alive http_2xx http_3xx;
```
