高性能 Nginx+Keepalived 2019版

本文目的

通过使用 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_module

  • Nginx 最新版
    官方开源版本。

以上三个版本各有千秋,目前我们线上用的是 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 223,726评论 6 521
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,697评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 170,734评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,508评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,522评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,051评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,429评论 3 427
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,403评论 0 278
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,930评论 1 323
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,977评论 3 343
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,122评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,763评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,454评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,931评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,047评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,613评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,150评论 2 363

推荐阅读更多精彩内容