本文目的
通過使用 2019年主流的軟件技術,配置高可靠高性能的Nginx。線上運行的8核8G的單臺虛擬機穩定支持 200Mb/s 持續流量沒有出現服務間斷、CPU飆高等情況。
本文不涉及到重新編譯內核、不依賴特定硬件設備、不依賴特定網絡架構、不需要使用物理機。
適用范圍
本文不求標新立異,使用的是通用而有效的優化方法,適用于主流的操作系統:
- CentOS 6.7+ (已經測試和驗證)
- CentOS 7.0+ (已經測試和驗證)
- Ubuntu 14.04 (支持,未測試)
- Ubuntu 16.04 (已經測試和驗證)
- Ubuntu 18.04 (已經測試和驗證)
- Debian 8, 9, 10 (支持,未測試)
Nginx + Keepalived 一句話 介紹
常見的組合,常用來實現高可靠的4層和7層的代理服務器。
Nginx 版本推薦
Tengine 3.x
基于 nginx 1.17 , 阿里出品,高性能,內置官方的Stream模塊可直接支持TCP代理。
目前缺少 stream 模塊的 upstream 的健康檢測功能。OpenResty 最新版
Nginx 的 "集成打包版",基于較新的 Nginx ,luajit 生態的擴展版。
推薦安裝額外的插件 vts ngx_healthcheck_moduleNginx 最新版
官方開源版本。
以上三個版本各有千秋,目前我們線上用的是 Tengine 。
優化因素(風險由低到高)
- 操作系統
- ulimit 參數
- ip_vs 參數
- 網卡參數
- irq 軟中斷
- CPU 親緣
- nginx 參數優化
- 內核啟動參數
- 內核sysctl參數
- 內核版本
操作系統
推薦使用 Ubuntu 18.04、16.04 或是 CentOS 7.7+ 操作系統。
如果現在還是要堅持使用CentOS 6,總有一天也會因為內核版本老、軟件版本老、不支持Docker、不支持 Systemd 等原因而主動或被動升級。再升級早享受。
irqbalance 服務可以自動綁定 cpu 負載軟中斷,需要內核高于 2.4 的版本。
ulimit 參數
推薦設置為 1048576
ip_vs 參數
很多文章提到,通過修改內核參數配置,重新編譯內核實現 ip_vs 模塊的參數優化。事實上較新的系統(包括 CentOS 6.7 及以上)完全沒必要重新編譯內核,僅需要配置加載參數 options ip_vs conn_tab_bits=20 即可實現。
網卡參數
關閉 gso gro tso
irq 軟中斷
開啟 irqbalance 服務能顯著減低軟中斷 ksoftirqd 引起的cpu負載。(內核須高于2.4版本)
nginx 流量高于 300Mb/s 時,如果發現 ksoftirqd 的 cpu 負載很高, 而且網絡延遲加大,可以檢查 irqbalance 服務是否開啟。
CPU 親緣
nginx 配置中,進行如下設置: worker_processes auto; worker_cpu_affinity auto;
nginx 參數優化
http 參數各種常用設定。
內核啟動參數
主要是 nohz=off transparent_hugepage=never numa=off
內核sysctl參數
## NAT,GATEWAY:1
## net.ipv4.ip_forward = 0
## net.ipv4.ip_forward = 1
## NAT,GATEWAY:0
## net.ipv4.tcp_tw_recycle = 0
## net.ipv4.tcp_tw_recycle = 1
fs.aio-max-nr = 16777216
fs.file-max = 16777216
fs.nr_open = 16777216
kernel.core_pipe_limit = 0
kernel.core_uses_pid = 1
kernel.exec-shield = 1
kernel.randomize_va_space = 1
kernel.msgmax = 65536
kernel.msgmnb = 65536
kernel.sem = 250 32000 100 128
kernel.shmall = 4294967296
kernel.shmmax = 68719476736
kernel.sysrq = 0
kernel.pid_max = 4194303
net.bridge.bridge-nf-call-arptables = 0
net.bridge.bridge-nf-call-ip6tables = 0
net.bridge.bridge-nf-call-iptables = 0
net.core.netdev_max_backlog = 524288
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
net.core.somaxconn=65535
net.core.wmem_default = 8388608
net.core.wmem_max = 16777216
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.all.arp_notify = 1
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.default.arp_announce = 2
net.ipv4.conf.eth0.accept_source_route = 0
net.ipv4.conf.lo.accept_source_route = 0
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.neigh.default.gc_stale_time = 120
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_max_tw_buckets = 16777216
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_rmem = 4096 87380 4194304
net.ipv4.tcp_sack = 1
net.ipv4.tcp_slow_start_after_idle = 1
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_window_scaling = 1
net.ipv4.tcp_wmem = 4096 65536 4194304
net.nf_conntrack_max = 25000000
net.netfilter.nf_conntrack_max=25000000
net.netfilter.nf_conntrack_generic_timeout = 120
net.netfilter.nf_conntrack_tcp_timeout_close = 10
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 60
net.netfilter.nf_conntrack_tcp_timeout_established = 180
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_last_ack = 30
net.netfilter.nf_conntrack_tcp_timeout_max_retrans = 300
net.netfilter.nf_conntrack_tcp_timeout_syn_recv = 60
net.netfilter.nf_conntrack_tcp_timeout_syn_sent = 120
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 120
net.netfilter.nf_conntrack_tcp_timeout_unacknowledged = 300
vm.overcommit_memory=1
vm.swappiness = 0
###vm.min_free_kbytes=65536
net.ipv4.tcp_fastopen = 3
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.ip_local_reserved_ports =10050,11215,18000-18099,27017,60000-60099
kernel.printk_ratelimit = 30
kernel.printk_ratelimit_burst = 200
vm.max_map_count=262144
# recommended for hosts with jumbo frames enabled
#net.ipv4.tcp_mtu_probing=1
fs.inotify.max_user_watches = 30000000
#bbr
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr
內核版本
內核的版本對性能和功能有顯著的影響,4.9 版本加入了 BBR 功能,對性能有顯著的提升。
** 升級內核版本風險較高,操作需要慎重。**
CentOS 7 內核版本為 3.10,可以使用 centos-release-xen 倉庫,安裝 4.9 的內核。
Ubuntu 16.04.3 HWE 內核版本的為 4.13。 如果你的內核還是默認的4.4.0,可以安裝 linux-image-generic-hwe-16.04 升級到 4.13 版本。
Ubuntu 18.04 使用 4.15 版本的內核。
內核 4.9 和 4.14 為 LTS 版本,維護期一般至少為 2至 3年。
Ubuntu LTS 支持時間一般為5年,大版本之間可以平滑升級。
附升級內核版本的操作
Ubuntu :
apt-get update
apt-get dist-upgrade -y
# 安裝制定版本的內核
apt-get install linux-image-4.13.0-32-generic
# 自動選擇最新穩定的內核
apt-get install -y linux-image-generic-hwe
CentOS 7 方法一 :
yum install -y centos-release-xen && yum install -y kernel
CentOS 7 方法二:
安裝 ELRepo,可以升級 kernel 版本,滾動升級最新的版本,后續如果內核安全補丁升級,使用穩定的版本存在安全隱患。
使用新內核
test -f /usr/sbin/update-grub && sudo update-grub
test -f /usr/sbin/grub2-mkconfig && sudo grub2-mkconfig -o /boot/grub2/grub.cfg
安全相關
1.1 配置跳轉使用 $request_uri
獲取用戶的請求路徑,而不是 $uri
或 $document_uri
location / {
return 302 https://$host$request_uri;
}
1.2 location 和 alias 配置的時候,結尾有沒有斜杠保持一致。
location /files/ {
alias /home/;
}
1.3 注意子模塊如果設置 add_header,會覆蓋上級模塊的全部add_header設置的信息
自動重試
自動重試機制雖然能盡量保障請求盡量得到執行。不過在并發壓力大的情況下,后端服務抗不住壓力,再加上自動重試,會造成系統負載更高,從而引起雪崩效應。建議關閉自動重試機制。
關閉自動重試
proxy_next_upstream off;允許重試一次
proxy_next_upstream_tries 1;
cms系統大文章保存,文件上傳,報錯 502 或是 408
http 字段,增加 __ client_body_buffer_size 8192k; __ 如果沒有解決,可以再適當增加這個值。
瀏覽器報錯 ERR_INCOMPLETE_CHUNKED_ENCODING
原因:后端服務(比如 netty )只支持 http 1.1,nginx 默認使用 http 1.0 去請求后端服務。
解決辦法,location 增加如下配置,讓 nginx 使用 http 1.1 協議去請求后端服務。
另外,啟用這兩個參數可以使用 keepalived 等功能,減少對后端的 tcp 并發連接數,我們線上已經默認啟用這兩個參數。
proxy_http_version 1.1;
proxy_set_header Connection "";
Chacha20-Poly1305 + X25519 ,需要 openssl 1.1.0 + nginx-1.12.1+/1.13.3+
ssl_ciphers EECDH+AES:EECDH+CHACHA20:!SHA;
ssl_prefer_server_ciphers on;
ssl_ecdh_curve X25519;
反向代理下載報錯 transfer closed with bytes remaining to read
proxy_buffering off;
日志
不同用途的日志格式保持一致,如果不需要打印某些變量,就用 - 來代替。
例如,main 格式里面不打印 $request_body
, spider 格式不打印 $upstream_addr
.
log_format main '[$time_iso8601] $http_x_forwarded_for $remote_addr '
'$request_method $status $server_protocol '
'$scheme://$http_host$request_uri '
'"$http_referer" "$http_user_agent" - $request_time '
'$body_bytes_sent $upstream_addr $upstream_response_time ';
log_format debug '[$time_iso8601] $http_x_forwarded_for $remote_addr '
'$request_method $status $server_protocol '
'$scheme://$http_host$request_uri '
'"$http_referer" "$http_user_agent" $request_body $request_time '
'$body_bytes_sent $upstream_addr $upstream_response_time ';
log_format spider '[$time_iso8601] $http_x_forwarded_for $remote_addr '
'$request_method $status $server_protocol '
'$scheme://$http_host$request_uri '
'"$http_referer" "$http_user_agent" $request_body $request_time '
'$body_bytes_sent - $upstream_response_time ';
decode $request_body
python2>
line = '{\x22id\x22:\x22user id\x22}'
line.decode('unicode_escape')
>> u'{"id":"user id"}'
python3>
line='{\x22....}'
bytes(line, 'utf-8').decode('unicode_escape')
ruby irb>
require 'yaml'
line = '{\x22id\x22:\x22user id\x22}'
YAML.load(%Q(---\n"#{line}"\n))
=> "{\"id\":\"user id\"}"
參考來源:
https://www.leavesongs.com/PENETRATION/nginx-insecure-configuration.html
https://stackoverflow.com/questions/30361486/nginx-logging-request-body-as-hexadecimal