2016/5/23

Nginx Reverse Proxy


Web Cache Server 介於 Client 跟 Web Server 之間,當使用者存取一個網址,Web Cache Server 會先向後端的 Web Server 取得資料,如果下一個 request 又存取了相同的網址,這時候就可直接由 Web Cache Server 回傳給 Client,用以減少 Web Server 的 Loading。


其他常見的開源代理伺服器有 Squid,Varnish,Nginx,HAProxy,Apache Traffic Server(ATS),其中 HAProxy 沒有 Cache 的功能,Squid 是比較老牌的 proxy server,但遇到 session 數量太高時,容易失效,已經比較少用了,然後 Varnish 取代了 Squid 的地位。


Varnish 主要功能就是 reverse proxy cache server,由於是使用記憶體作為cache,適合用來處理小文件如 css,js 及小圖片的cache。


nginx 主要功能是靜態網頁服務,若是為了提供訪問最頻繁資源的cache,可以使用 nginx-cache 模組,適合cache少量頁面資源,如果 Cache 內容過多容易造成性能瓶頸與負載過大。


所以目前看起來,有兩個伺服器可以嘗試,一個是 ngnix + cache,這個可以用在少量 cache 資源,加上可以處理靜態網頁檔案,另一個是 Apache Traffic Server (ATS),這是比較專用的 cache server,如果要建置專業的 CDN,應該要選擇使用 ATS。


Apache Traffic Server(ATS或TS)是一個高性能的、模塊化的 HTTP 代理和cache服務器。Traffic Server 最初是 Inktomi 公司的商業產品,該公司在 2003 年被 Yahoo 收購,之後 Traffic Server 一直在 Yahoo 內部使用長達 4 年,直到 2009 年 8 月 Yahoo 向 Apache 軟件基金會(ASF)貢獻了源代碼,並於 2010 年 4 月成為了 ASF 的頂級項目(Top-Level Project)。Apache Traffic Server 現在是一個開源項目,開發語言為C++。


軟件名稱 性能 功能 過濾規則配置
Squid 不能多核,disk cache有容量優勢,性能中等 多,支援ACL角色控制,也支援ICP cache 協定 支援外部規則文件讀取及熱加載,支援熱啟動
Varnish 多核支援,memory cache,性能強 夠用,不支援集群,支援後端存活檢查 不支援外部文件讀取,需要轉譯,支援熱啟動
Nginx 多核,支援代理插件,性能較強 多,通過plugin可以充當多種服務器 不支援外部文件讀取,需要轉譯,支援熱啟動
ATS 多核,disk/memory cache,性能強 夠用,支援plugin開發,也支援ICP協議 支援外部規則文件讀取及熱加載,支援熱啟動,但缺乏文檔
HAProxy 多核,無 cache,支援HTTP header語法操作,性能強 少,只專注HTTP頭部解析和轉發功能,支援ACL角色控制,支援後端存活檢查 支援外部規則文件讀取及熱加載,支援熱啟動,支援 sticky session 和長連接

nginx for CentOS 7


安裝 nginx


yum -y install nginx

# 立即啟動
systemctl start nginx

# 查看目前運作狀態
systemctl status nginx

# 查看 nginx 服務目前的啟動設定
systemctl list-unit-files | grep nginx

# 若是 disabled,可以改成開機自動啟動
systemctl enable nginx

調整 nginx 設定,先把 nginx service port 改為 6000


vi /etc/nginx/nginx.conf
listen       6000 default_server;
listen       [::]:6000 default_server;

systemctl restart nginx

安裝及設定 PHP-FPM


yum -y install php php-fpm php-mysql

# 查詢 PHP-FPM 的狀態
systemctl list-unit-files | grep php-fpm

# 改成開機自動啟動
systemctl enable php-fpm

# 立即啟動
systemctl start php-fpm

# 查看目前運作狀態
systemctl status php-fpm

修改 PHP-FPM 設定


chown -R nginx.nginx /var/lib/php/session

vi /etc/php-fpm.d/www.conf

user = nginx
group = nginx

listen.owner = nginx
listen.group = nginx
listen.mode = 0660

; listen = 127.0.0.1:9000  改成
listen = /var/run/php-fpm/php-fpm.sock


systemctl restart php-fpm

修改 ulimit open file的限制


# 查閱file open 限制
ulimit -a

vi /etc/security/limits.conf
# 增加 4行
* soft nofile 65535
* hard nofile 65535
root hard nofile 65535
root soft nofile 65535

# 設定 file open數量
ulimit -n 65535

nginx proxy_cache


nginx 自 v0.7.48 開始支援了類似 Squid 的功能,他會把 url 當作 key,以 MD5 對 key 進行 hash,然後得到硬碟上對應的 hash 檔案目錄,然後將快取的內容存放到這個目錄中。目前沒有清除 cache 的功能,但可透過 ngxcachepurge 模組,清除指定 URL 的 cache。


建立 cache 目錄


mkdir -p /usr/share/nginx/cache
chown -R nginx.nginx /usr/share/nginx/cache

proxy_cache 相關的設定值


# 設定快取檔案的存放路徑 proxy_cache_path
proxy_cache_path path [levels=number] keys_zone=zone_name:zone_size [inactive=time] [max_size=size];

# path  快取檔案的存放路徑
# levels=number  快取空間有兩層 hash 目錄
# keys_zone=zone_name:zone_size  zone_name是為這個快取空間命名,zone_size是記憶體快取的空間大小
# inactive=time  快取存活的時間
# max_size=size  硬碟快取空間

# example
proxy_cache_path /usr/share/nginx/cache levels=1:2 keys_zone=STATIC:50m inactive=1d max_size=20g;

# ---------------
# 設定要使用哪一個快取空間名稱 proxy_cache
proxy_cache zone_name;
# zone_name 就是剛剛在 proxy_cache_path 設定的 zone_name

# example
proxy_cache STATIC;

# ---------------
# proxy_cache_methods 設定快取哪些 HTTP method,預設為 GET/HEAD
proxy_cache_methods [GET HEAD POST]

# ---------------
# proxy_cache_min_uses 設定快取的最小使用次數,預設為 1
proxy_cache_min_uses 1;

# ---------------
# proxy_cache_valid 對不同的 reponse status code 的 URL 設定不同的快取時間
proxy_cache_valid reply_code time;

# example
proxy_cache_valid 200 302 10m;
proxy_cache_valid 301 1h;
proxy_cache_valid 404 1m;
proxy_cache_valid any 1m;

# ---------------
# proxy_cache_key 設定快取的 key 值
proxy_cache_key "$host:$server_port$uri$is_args$args"

# ---------------
# 如果後端服務器經常發生503錯誤,服務不能保證可正確取得資料,為了避免訪問錯誤,可以在cache server上做調整。如果cache過期,但訪問後端服務器又不能返回正常的內容,則使用cache的內容。
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;

如果要驗證是不是從 Cache 取得的內容,可以加上這個 header


add_header      Nginx-Cache     "$upstream_cache_status from cache";

當我們用瀏覽器觀看 Header 時,如果沒有 hit,會看到這樣的 header


Nginx-Cache:MISS from cache

如果有 hit,會看到這樣的 header


Nginx-Cache:HIT from cache

範例


完整的範例


http {
    proxy_cache_path /usr/share/nginx/cache levels=1:2 keys_zone=STATIC:50m inactive=1d max_size=20g;
    server {
        location / {
            proxy_pass             http://127.0.0.1:8080;
            proxy_set_header       Host $host;
            proxy_cache            STATIC;
            proxy_cache_valid      200 302 10m;
            proxy_cache_valid      301 1h;
            proxy_cache_valid      404 1m;
            proxy_cache_valid      any 1m;
            proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;
        }
    }
}

紀錄最後修改好的設定檔


user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    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;

    include /etc/nginx/conf.d/*.conf;

    proxy_cache_path /usr/share/nginx/cache levels=1:2 keys_zone=STATIC:50m inactive=1d max_size=20g;

    server {
        listen       6000 default_server;
        listen       [::]:6000 default_server;
        server_name  _;
    server_tokens off;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
            proxy_pass             http://127.0.0.1:8080;
            proxy_set_header       Host $host;
            proxy_set_header       X-Real-IP $remote_addr;
            proxy_set_header       X-Forwarded-For $proxy_add_x_forwarded_for;
        }

    location ~ .*\.(gif|jpg|png|bmp|swf|js|css|lzv|pdf|html|jsp)$ {
        proxy_cache            STATIC;
        proxy_cache_valid      200 302 10m;
        proxy_cache_valid      301 1h;
        proxy_cache_valid      404 1m;
        proxy_cache_valid      any 1m;
        #proxy_cache_use_stale  error timeout invalid_header updating
        #                       http_500 http_502 http_503 http_504;
        proxy_cache_key     $host$uri$is_args$args;
        proxy_set_header       Host $host;
        proxy_set_header       X-Real-IP $remote_addr;
        #proxy_set_header       X-Real-IP $Forwarded-For;
        proxy_set_header       X-Forwarded-For $proxy_add_x_forwarded_for;
        add_header      Nginx-Cache     "$upstream_cache_status from cache";
        proxy_pass             http://127.0.0.1:8080;
    }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }
}

清除 cache


如果要清除 cache,可直接刪除 proxycachepath 目錄下所有檔案


vi clear_nginx_cache.sh

#!/bin/bash
find /usr/share/nginx/cache -type f -exec rm -f {} \;

chmod 755 clear_nginx_cache.sh

每天定時刪除超過兩天的 cache file


vi /etc/cron.daily/delete_nginx_cache.sh

#!/bin/bash
find /usr/share/nginx/cache -type f -mtime +2 -print0 | xargs -0 -r rm >/dev/null 2>&1

chmod 755 /etc/cron.daily/delete_nginx_cache.sh

處理 Nginx 產生的大量 TCP_WAIT


由於 Nginx Proxy 會產生大量 TCP_WAIT,我們需要調整 linux TCP 的參數,並調整 nginx 的設定。


# 用 netstat 查看 TCP_WAIT 數量
netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'

# 用 ss 查看 TCP_WAIT 數量
ss -ant | awk 'NR>1 {++s[$1]} END {for(k in s) print k,s[k]}'

查看 /proc/sys/net/ipv4/iplocalport_range 設定檔,可得知 Linux 動態分配 Local Port 的 Range。


cat /proc/sys/net/ipv4/ip_local_port_range
32768   61000

透過修改 /etc/sysctl.conf 可調整 local service ports


vim /etc/sysctl.conf
net.ipv4.ip_local_reserved_ports = 32768-61000

sysctl -p

修改 /etc/sysctl.conf TCP 參數


vim /etc/sysctl.conf
net.ipv4.ip_local_port_range = 1024 65535
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
net.core.netdev_max_backlog = 30000
net.ipv4.tcp_no_metrics_save = 1
net.core.somaxconn = 65535
net.ipv4.tcp_syncookies = 0
net.ipv4.tcp_max_orphans = 262144
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2

# sysctl.conf 重新載入設定
sysctl -p

修改 /etc/security/limits.conf


vim /etc/security/limits.conf
* soft nofile 655360
* hard nofile 655360

ulimit -n 655360

修改 /etc/nginx/nginx.conf 參數


vim /etc/nginx/nginx.conf

worker_rlimit_nofile 102400;
events {
    worker_connections 1024; # 可以加到 4096
    #worker_connections 768;
    use epoll;
    # multi_accept on;
}

upstream webserver {
   server 127.0.0.1:8000 weight=10;
   keepalive       16;
}

http {
    #keepalive_timeout 65;
    keepalive_timeout 1;
}

# 後面 proxy_pass 的地方要改成這樣
proxy_pass             http://webserver;

記一次TIME_WAIT網絡故障


Nginx做前端Proxy時TIME_WAIT過多的問題


Nginx Connection 不夠用 的參數調整


TCP 狀態 TIME_WAIT 分析


References


varnish / squid / nginx cache 有什麼不同?


自建CDN防禦DDoS(2):架構設計、成本與部署細節


RHEL / CentOS 7 安裝 Nginx, MySQL, PHP (LEMP)


在 CentOS 7 安裝 Nginx、PHP-FPM、MariaDB


Nginx proxy 反向代理 轉發Tomcat


Reverse Proxy with Caching example