操作系统调优

配置扩展文件描述符

目的

在Nginx环境中,需要配置最大打开文件数为102400,否则在测试过程中可能会导致软件最大打开文件数被限制在1024,影响服务器性能。

方法

1、打开limits.conf文件。

vim /etc/security/limits.conf

2、在文件中写入以下配置后,保存并退出文件。

*   soft     nofile      102400
*   hard     nofile      102400

说明:
*:表示所有用户;
hard:表示严格的设定,必定不能超过这个设定的数值;
soft:表示警告的设定,可以超过这个设定值,但是若超过则有警告信息。

3、重启服务器使文件配置生效。

配置内核参数

目的

对于不同的操作系统,通过调优内核参数,可以有效的提高服务器的性能。

方法

1、打开sysctl.conf文件。
vim /etc/sysctl.conf

2、写入以下内核参数后,保存配置文件。

net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_fin_timeout = 1
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.ip_local_port_range = 1024    65500
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 262144
net.core.netdev_max_backlog = 262144
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

内核参数说明

Linux内核参数参数定义当前值默认值
net.ipv4.tcp_tw_reuse将TIME-WAIT sockets重新用于新的TCP连接。1表示开启;0表示关闭。10
net.ipv4.tcp_keepalive_timeTCP发送keepalive探测消息的间隔时间(秒),用于确认TCP连接是否有效。607200
net.ipv4.tcp_fin_timeoutsocket保持在 FIN_WAIT_2 状态的最大时间。160
net.ipv4.tcp_max_tw_buckets减少TIME_WAIT连接数,避免过多TIME_WAIT连接占用网络资源导致新建连接资源紧张,时延增加。5000262144
net.ipv4.ip_local_port_range增加可用端口范围,避免大量连接占用端口时,新建连接不断寻找可用端口而导致的性能跳水。1024 6550032768 61000
net.core.somaxconn定义了系统中每一个端口最大的监测队列的长度,属于全局的参数。65535128
net.ipv4.tcp_max_syn_backlog表示SYN队列的长度,加大队列长度可以容纳更多等待连接的网络连接。2621441024
net.core.netdev_max_backlog当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。2621441000
net.core.rmem_max系统套接字读最大缓冲区。16777216131071
net.core.wmem_max系统套接字写最大缓冲区,增加buffer大小,避免大量新建连接导致buffer溢出,出现无法建立连接情况。16777216131071
net.nf_conntrack_max最大跟踪连接数。065536

说明:
当使用容器时,由于不能关闭nf_conntrack模块,需要在文件“/etc/sysctl.conf”中增加nf_conntrack相关内核参数设置,防止table记录满产生丢包问题,具体参数如下:
net.netfilter.nf_conntrack_max=0
net.nf_conntrack_max=0

3、使配置生效。

/sbin/sysctl -p

关闭nf_conntrack模块(容器中不要配置此项)

目的

内核的nf_conntrack模块可以用来实现NAT,但是在没有使用NAT的场景,开启nf_conntrack模块会导致不需要的CPU消耗,甚至可能导致table记录满产生丢包问题,因此可以考虑关闭nf_conntrack模块提升性能。

方法

1、打开blacklist.conf文件。
vim /etc/modprobe.d/blacklist.conf

2、添加如下参数后,保存并退出文件。

install nf_conntrack /bin/false
blacklist nf_conntrack
blacklist nf_conntrack_ipv6
blacklist xt_conntrack
blacklist nf_conntrack_ftp
blacklist xt_state
blacklist iptable_nat
blacklist ipt_REDIRECT
blacklist nf_nat
blacklist nf_conntrack_ipv4

3、重启服务器使文件配置生效。

关闭SELinux

目的

为了减少SELinux的性能损耗,本文关闭SELinux。

方法

1、打开SELinux文件。
vim /etc/sysconfig/selinux

2、将SELINUX参数由“enforcing”改为“disabled”。

3、保存文件,并重启服务器。

4、确认SELinux是否关闭。

/usr/sbin/sestatus -v

配置网卡中断绑核

目的

  1. 每个CPU对应一个网卡,每个CPU内的中断绑核只绑在属于本CPU的网卡上,按node各自绑核。
  2. 中断个数在尽量少的情况下满足当前所有业务core在客户端满压下满CPU运作即可。
  3. 容器在使用主机网络共享和IPVLAN网络两种网络方式下使用同样的绑核脚本,且都是在物理机上执行绑核脚本。

方法

1、关闭irqbalance。
systemctl stop irqbalance.service
systemctl disable irqbalance.service

2、网卡中断绑核,绑定的核专门用于处理网卡中断。
以4core的HTTP短连接场景的绑核脚本为例,如果要修改绑核脚本,只需修改要绑定的网口名eth1以及要绑定的core。

#!/bin/bash
# filename: irq_http_4core.sh
cnt=2
eth1=enp3s0
ethtool -L $eth1 combined $cnt                 
irq1=`cat /proc/interrupts| grep -E ${eth1} | head -1 | awk -F ':' '{print $1}'`
irq1=`echo $irq1`
i=0
while(( $i < 1))
do
        for cpunum in 2 3
        do
                echo $cpunum "->" $irq1
                echo  $cpunum > /proc/irq/$irq1/smp_affinity_list
                let "irq1++"
        done
        let "i++"
done

脚本参数说明

参数命令名称说明
cnt设置网口队列数:cnt为变量,2为队列数。
eth1设置通信中使用的网口名:eth1为变量,enp3s0为网口名。
irq1网口eth1对应的中断号:irq1为变量。
cpunum分配给网口eth1用于处理网卡中断的核,cpunum为变量。
ethtool -L $eth1 combined $cnt设置队列数为核数。
cat /proc/interrupts | grep $eth1 | awk -F ':' '{print $1}'查询网口中断号。
echo $cpunum > /proc/irq/$irq/smp_affinity_list根据中断号,将每个中断各绑定在一个核上。

Nginx 调优

nginx.conf配置文件示例

user  root;
worker_processes 2;
worker_cpu_affinity
1
10;
error_log /dev/null;
worker_rlimit_nofile 102400;
events {
    worker_connections  102400;
    multi_accept on;
    use epoll;
    accept_mutex off;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    access_log /dev/null;
    sendfile        on;
    tcp_nopush     on;
    tcp_nodelay     on;
    sendfile_max_chunk 512k;
    keepalive_timeout  65;
    keepalive_requests 2000;
    client_header_buffer_size 4k;
    large_client_header_buffers 4 32k;
    server_names_hash_bucket_size 128;
    client_max_body_size 100m;
    open_file_cache max=102400 inactive=40s;
    open_file_cache_valid 50s;
    open_file_cache_min_uses 1;
    open_file_cache_errors on;
   upstream test{
       server 192.168.1.75:10000  ;
       server 192.168.2.75:11000  ;
       server 192.168.1.72:10000 ;
       server 192.168.2.72:11000 ;
       keepalive 300;
        }
   server {
        listen       10000 reuseport;
        server_name  localhost;
        access_log off;
        location / {
            root   html;
            index  index.html index.htm;
                   proxy_pass http://test;
                proxy_connect_timeout 75;
                proxy_read_timeout 300;
                proxy_send_timeout 300;
                proxy_buffer_size 4k;
                proxy_buffers   4 32k;
                proxy_busy_buffers_size 64k;
                proxy_temp_file_write_size 64k;
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                proxy_headers_hash_max_size 51200;
                proxy_headers_hash_bucket_size 6400;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    # HTTPS server
        server {
         listen       20000 ssl ;
         server_name  localhost;
         ssl_certificate      /usr/local/nginx/server_2048.crt;
         ssl_certificate_key  /usr/local/nginx/server_2048.key;
         ssl_session_cache    shared:SSL:1m;
         ssl_session_timeout  5m;
        ssl_protocols  TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
        ssl_ciphers  "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";
         ssl_prefer_server_ciphers  on;
         location / {
             root   html;
             index  index.html index.htm;
         }
     }
}

Nginx基本配置与参数说明

Nginx的配置是区块型的,包括全局配置(如user、worker_processes等)和模块配置(其中events为events模块,http为http模块),每个模块配置中又分为各个区块,如http模块中有server区块,server有location区块。

全局配置:

参数说明
user root;定义Nginx运行的用户和用户组。
worker_processes 3;Nginx进程数,建议设置为等于CPU总核心数。
worker_cpu_affinity 1 10 100;通过worker_cpu_affinity给每个worker进程绑定一个CPU, 1表示启用core0,10表示启用core1,100表示启动core2,依次类推,100000表示启用core5,1后面有n个0就表示启动core n。
error_log /dev/null;错误日志文件路径,/dev/null表示关闭Nginx日志。
worker_rlimit_nofile 102400;指定一个Nginx进程打开的最多文件描述符数目,受系统进程的最大打开文件数量(ulimit-n)限制。

配置events模块

参数说明
worker_connections 102400;定义每个进程的最大连接数,受系统进程的最大打开文件数量限制,并发总数是worker_processes和 worker_connections 的乘积。
multi_accept on;设置一个进程是否同时接受多个网络连接,如果multi_accept被禁止了,Nginx一个工作进程只能同时接受一个新的连接。
use epoll;设置工作模式为epoll,除此之外还有select,poll,kqueue,rtsig和/dev/poll模式。
accept_mutex on;打开accept_mutex,避免了worker争夺资源造成的上下文切换以及try_lock的锁开销。

配置HTTP模块

参数说明
include mime.types;文件扩展名与文件类型映射表。
default_type application/octet-stream;默认文件类型。
access_log /dev/null;Nginx访问日志路径,/dev/null表示关闭访问日志。
sendfile on;开启高效文件传输模式,获取文件跨过用户态;.gzip压缩器需要在用户态进行,因此无法和sendfile共存。
tcp_nopush on;开启防止网络阻塞,返回数据的首个数据包会携带从sendfile中获取大块的数据后才会被发送。
tcp_nodelay on;开启防止网络阻塞。
sendfile_max_chunk 512k;限制最大sendfile的文件大小,防止过大的文件占据整个工作进程默认为“无限制”。
keepalive_timeout 65;设置客户端连接保存活动的超时时间。
keepalive_requests 2000;设置一个keep-alive连接上可以服务的请求的最大数量。当最大请求数量达到时,连接被关闭。
client_header_buffer_size 4k;客户端请求头部的缓冲区大小,这个可以根据你的系统分页大小来设置,一般一个请求的头部大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令getconf PAGESIZE取得。
large_client_header_buffers 4 32k;设置客户端请求的Header头缓冲区大小:4为个数,32k为大小,表示申请4个32k。默认会用client_header_buffer_size这个buffer来读取header值,如果header过大,它会使用large_client_header_buffers来读取。
server_names_hash_bucket_size 128;保存服务器名字的hash表的大小。
client_max_body_size 100m;控制全局nginx所有请求报文大小,100M。
open_file_cache max=102400 inactive=40s;max设置缓存中的最大元素数,在缓存溢出时,删除最近最少使用(LRU)的元素;inactive定义一个时间,如果在此期间未访问该元素,则从该缓存中删除该元素。
open_file_cache_valid 50s;多长时间检查一次缓存的有效信息。也就是说即使我一直访问这个文件,50s后会检查此文件的更改信息是否变化,发现变化就更新。
open_file_cache_min_uses 1;open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件更改信息一直是在缓存中打开的。
open_file_cache_errors on;启用文件查找错误的缓存。

配置upstream test

反向代理服务器组test。Nginx会根据配置,将请求分发给组里的某一台服务器。

参数说明
server 192.168.1.75:10000;
server 192.168.2.75:11000;
server 192.168.1.73:10000;
server 192.168.2.73:11000;
配置处理最终请求的服务器IP及端口。
keepalive 300;设置连接超时时间。

设定主机配置

参数说明
listen 10000 reuseport;监测10000端口,http短连接场景建议打开reuseport,长连接可复用。
server_name localhost;定义使用localhost访问。
access_log off;关闭本虚拟主机的访问日志。
默认请求
root html;定义服务器的默认网站根目录位置,root是web根目录。
index index.html index.htm;定义首页索引文件的名称及顺序。
proxy_pass http://test;表示将所有请求转发到服务器组test配置的服务器中。
proxy_connect_timeout 75;设置后端服务器的连接超时时间。
proxy_read_timeout 300;设置Nginx与后端服务器建立连接后,等待后端服务器的响应时间
proxy_send_timeout 300;指定请求转移到后端服务器的超时时间,超时Nginx将关闭连接。
proxy_buffer_size 4k;设置用于保存用用户的头部信息的代理缓冲区大小。
proxy_buffers 432k;设置缓冲区的数目和大小。
proxy_busy_buffers_size 64k;用于当系统负载较大,缓冲区不够用时,可以设置更大的proxy_buffers。
proxy_temp_file_write_size 64k;用于指定缓存临时文件的大小。
proxy_http_version 1.1;设置Nginx服务器提供代理服务的http协议版本1.1。
proxy_set_header Connection "";允许重新定义和添加一些将被转移到被代理服务器的请求头部信息。
proxy_headers_hash_max_size 51200;存放http报文头的哈希表容量上限。
proxy_headers_hash_bucket_size 6400;nginx服务器申请存放http报文头的哈希表容量大小。
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;}
定义错误提示页面。

配置SSL加密

参数说明
listen 20000 ssl;监测20000端口。
server_name localhost;定义使用localhost访问。
ssl_certificate /usr/local/nginx/server_2048.crt;ssl_certificate证书是公钥,它会被发送到连接服务器的每个客户端。
ssl_certificate_key /usr/local/nginx/server_2048.key;ssl_certificate_key私钥是用来解密的,所以它的权限要得到保护但nginx的主进程能够读取。当然私钥和证书可以放在一个证书文件中,这种方式也只有公钥证书才发送到client。
ssl_session_cache shared:SSL:1m;设置ssl会话缓存的类型和大小,shared:SSL:1m表示我所有的nginx工作进程共享ssl会话缓存,1M可以存放约4000个sessions。
ssl_session_timeout 5m;客户端可以重用会话缓存中ssl参数的过期时间,5分钟。
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;用于启动特定的加密协议。
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4";选择加密套件。
ssl_prefer_server_ciphers on;设置协商加密算法时,优先使用我们服务端的加密套件,而不是客户端浏览器的加密套件。
默认请求
root html;定义服务器的默认网站根目录位置,root是web根目录。
index index.html index.htm;定义首页索引文件的名称及顺序。

reuseport特性

在使用Nginx过程中,过多的并发网络连接会导致对socket的操作成为性能瓶颈,严重限制内核性能,需要使用内核的reuseport特性。Nginx使用reuseport特性后,会给每个worker进程创建一个listen的socket,能显著降低对socket的争抢。

listen       83     reuseport;

后端连接优化

在测试环境中,Nginx与Memcached之间的连接如果是短连接会导致Memcached主线程耗费大量时间分发连接给其他线程,主线程核占用会达到100%成为性能瓶颈。因此需要设置为长连接,减少主线程的开销,提升服务端整体性能。

在nginx.conf配置文件中的upstream模块添加keepalive配置。

upstream memcached_backend{
server 172.19.22.53:11211;
keepalive 500; }

Nginx记录用户请求Header到access log

把自定义头部加入日志

在nginx的http段里面对access log做如下的设置:

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$http_mycheck"';
    access_log  logs/access.log  main;
}

我在日志格式的最后面加入了$http_mycheck,那么,Nginx会记录mycheck这个头部,保存到access log里面。

重启Nginx,然后curl测试:

./nginx -s reload
curl -H "mycheck: justtestlog" localhost/whatever.html
curl localhost/whatever.html

然后查看两次请求的日志记录

tail -2 logs/access.log

请求头部中没有mycheck字段的时候,日志字段里记为"-",header有mycheck字段的时候,最后一段是mycheck的值。

127.0.0.1 - - [xxx] "GET /whatever.html HTTP/1.1" 200 21 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" "justtestlog"
127.0.0.1 - - [xxx] "GET /whatever.html HTTP/1.1" 200 21 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.19.1 Basic ECC zlib/1.2.3 libidn/1.18 libssh2/1.4.2" "-" "-"
    set $dm_cookie "";
    if ($http_cookie ~* "(.+)(?:;|$)") {
        set $dm_cookie $1;
    }

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$http_mycheck" "$dm_cookie"';
    access_log  logs/access.log  main;

这样日志里面就可以看到cookie了,据说可以监控用户和行为。但是在实际中,cookie太长,加上cookie之后,日志量会成倍增长,会加大服务器的压力,如非必要,不建议在日志中添加该字段。

记录用户访问的 $request_body

log_format  main  '$remote_addr - $remote_user [$time_local] "$request" $request_body '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "$http_mycheck" "$dm_cookie"';
access_log  logs/access.log  main;

$request_body 变量已经增加到上述文件里面,可以记录到客户端请求体也就是域名后面进行的传参值,记录这个主要时判断用户名密码一类的,建议生产服务器也不要添加,日志量会增大。

给Nginx配置日志格式和调整日期格式

官方默认日志格式

官方默认日志格式

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

输出结果

127.0.0.1 - - [01/Jul/2020:03:25:17 +0800] "GET /resource/js/bootstrap.min.js HTTP/1.1" 200 36816 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" "192.168.0.1"
127.0.0.1 - - [01/Jul/2020:03:25:17 +0800] "GET /resource/js/jquery.min.js HTTP/1.1" 200 95962 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36" "192.168.0.1"

修改后的日志格式

# 自定义格式
log_format main '$year-$month-$day $hour:$minutes:$seconds|$request_method|$request_uri|$http_host|$server_name|$status|$request_time|$remote_addr|$http_x_forwarded_for|$http_referer|$http_user_agent';

输出结果

2020-07-01 11:30:54|GET|/|www.baidu.com|www.baidu.com|304|0.000|127.0.0.1|192.168.0.1|-|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
2020-07-01 11:30:59|GET|/favicon.ico|www.baidu.com|www.baidu.com|200|0.000|127.0.0.1|192.168.0.1|https://www.www.com/|Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36

自定义时间

年月日这些时间变量均为自定义,需要在每一个server下面添加这个代码

server {
	if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})") {
			set $year $1;
			set $month $2;
			set $day $3;
			set $hour $4;
			set $minutes $5;
			set $seconds $6;
	}
}

Nginx 内置变量

变量名称变量描述
$arg_PARAMETER客户端GET请求中PARAMETER 字段的值
$args客户端请求中的参数
$binary_remote_addr远程地址的二进制表示
$body_bytes_sent已发送的消息体字节数
$content_lengthHTTP请求信息中content-length的字段
$content_type请求信息中content-type字段
$cookie_COOKIE客户端请求中COOKIE头域的值
$document_root针对当前请求的根路径设置值
$document_uri与$uri相同
$host请求信息中的host头域,如果请求中没有Host行,则等于设置的服务器名
$http_HEADERHTTP请求信息里的HEADER地段
$http_host与$host相同,但是如果请求信息中没有host行,则可能不同客户端cookie信息
$http_cookie客户端cookie信息
$http_referer客户端是从哪一个地址跳转过来的
$http_user_agent客户端代理信息,也就是你客户端浏览器
$http_via最后一个访问服务器的IP
$http_x_forwarded_for相当于访问网路访问的路径
$is_args如果有args的值,则等于”?”,否则为空
$limit_rate对连接速率的限制
$nginx_version当前Nginx的版本
$pid当前Nginx服务器的进程的进程ID
$query_string与$args相同
$remote_addr客户端IP地址
$remote_port客户端的端口
$remote_user客户端的用户名,用于 auth basic module验证
$request客户端请求
$request_body客户端发送的报文体
$request_body_file发送后端服务器的本地临时缓存文件的名称
$request_filename当前请求的文件路径名,由root或alias指令与URI请求生成
$request_method请求后端数据的方法,例如”GET”,”POST”
$request_uri请求的URI,带参数,不包含主机名
$scheme所用的协议,如http或者HTTPS,比如rewrite^(.+)$$scheme://mysite.name$redirect
$sent_http_cache_control对应http请求头中的Cache-Control,需要打开chrome浏览器,右键检查,选中network,点中其中一个请求的资源
$sent_http_connection对应http请求中的Connection
$sent_http_content_type对应http请求中的Content-Type
$sent_last_modified对应请求中的Last-Modified
$server_addr服务端的地址
$server_name请求到达的服务器名
$server_port请求到达服务器端口号
$server_protocol请求协议的版本号,HTTP1.0/HTTP1.1
$uri请求的不带请求参数的URI,可能和最初的值有不同,比如经过重定向之类的

Nginx 限流

限流(rate limiting)是Nginx众多特性中最有用的,也是经常容易被误解和错误配置的,特性之一。该特性可以限制某个用户在一个给定时间段内能够产生的HTTP请求数。请求可以简单到就是一个对于主页的GET请求或者一个登陆表格的POST请求。

限流可以用于安全目的上,通过限制请求速度来防止外部暴力扫描,或者减慢暴力密码破解攻击,可以结合日志标记出目标URL来帮助防范DDoS攻击,也可以解决流量突发的问题(如整点活动),一般地说,限流是用在保护上游应用服务器不被在同一时刻的大量用户请求湮没。

Nginx在处理请求的时候是在1s内对请求的具体个数做拆分的,以2request/s为例,拆分为500ms内处理一个请求,下一个500ms才会处理第二个请求;除此之外,我们发现,在限制2request/s后,发现发送多个请求(requests>=2)也可以被处理掉,在此基础上上,调研了缓冲队列的相关配置。

按请求速率限速

ngx_http_limit_req_module 模块提供限制请求处理速率能力,使用了漏桶算法(leaky bucket)。下面例子使用 nginx limit_req_zone 和 limit_req 两个指令,限制单个IP的请求处理速率。

在 nginx.conf http 中添加限流配置:

http {
    limit_req_zone $binary_remote_addr zone=myRateLimit:10m rate=10r/s;
}

配置 server,使用 limit_req 指令应用限流。

server {
    location / {
	limit_req zone=myRateLimit;
        proxy_pass http://my_upstream;
    }
}
  • key :定义限流对象,binary_remote_addr 是一种key,表示基于 remote_addr(客户端IP) 来做限流,binary_ 的目的是压缩内存占用量。
  • zone:定义共享内存区来存储访问信息, myRateLimit:10m 表示一个大小为10M,名字为myRateLimit的内存区域。1M能存储16000 IP地址的访问信息,10M可以存储16W IP地址访问信息。
  • rate 用于设置最大访问速率,rate=10r/s 表示每秒最多处理10个请求。Nginx 实际上以毫秒为粒度来跟踪请求信息,因此 10r/s 实际上是限制:每100毫秒处理一个请求。这意味着,自上一个请求处理完后,若后续100毫秒内又有请求到达,将拒绝处理该请求。

上面例子限制 10r/s,如果有时正常流量突然增大,超出的请求将被拒绝,无法处理突发流量,可以结合 burst 参数使用来解决该问题。

server {
    location / {
	limit_req zone=myRateLimit burst=20;
        proxy_pass http://my_upstream;
    }
}

burst 译为突发、爆发,表示在超过设定的处理速率后能额外处理的请求数。

当 rate=10r/s 时,将1s拆成10份,即每100ms可处理1个请求。

此处,burst=20,若同时有21个请求到达,Nginx 会处理第一个请求,剩余20个请求将放入队列,然后每隔100ms从队列中获取一个请求进行处理。若请求数大于21,将拒绝处理多余的请求,直接返回503.

不过,单独使用 burst 参数并不实用。假设 burst=50 ,rate依然为10r/s,排队中的50个请求虽然每100ms会处理一个,但第50个请求却需要等待 50 * 100ms即 5s,这么长的处理时间自然难以接受。

因此,burst 往往结合 nodelay 一起使用。

server {
    location / {
		limit_req zone=myRateLimit burst=20 nodelay;
        proxy_pass http://my_upstream;
    }
}

nodelay 针对的是 burst 参数,burst=20 nodelay 表示这20个请求立马处理,不能延迟,相当于特事特办。不过,即使这20个突发请求立马处理结束,后续来了请求也不会立马处理。burst=20 相当于缓存队列中占了20个坑,即使请求被处理了,这20个位置这只能按 100ms一个来释放。

这就达到了速率稳定,但突然流量也能正常处理的效果。

控制并发连接数

ngx_http_limit_conn_module 提供了限制连接数的能力,利用 limit_conn_zone 和 limit_conn 两个指令即可。下面是 Nginx 官方例子

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}
  • limit_conn perip 10 作用的key 是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。
  • limit_conn perserver 100 作用的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。
  • 需要注意的是:只有当 request header 被后端server处理后,这个连接才进行计数。

设置白名单

限流主要针对外部访问,内网访问相对安全,可以不做限流,通过设置白名单即可。利用 Nginx ngx_http_geo_module 和 ngx_http_map_module 两个工具模块即可搞定。

在 nginx.conf 的 http 部分中配置白名单:

geo $limit {
    default 1;
    10.0.0.0/8 0;
    192.168.0.0/24 0;
    172.20.0.35 0;
}

map $limit $limit_key {
    0 "";
    1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=myRateLimit:10m rate=10r/s;
  • geo 对于白名单(子网或IP都可以) 将返回0,其他IP将返回1。
  • map 将 $limit $limit_key,如果是 $limit 是0(白名单),则返回空字符串;如果是1,则返回客户端实际IP。
  • limit_req_zone 限流的key不再使用 $binary_remote_addr, 而是通过 $limit_key 来动态获取值。如果是白名单,limit_req_zone 的限流key则为空字符串,将不会限流;若不是白名单,将会对客户端真实IP进行限流。

限制下载速度

除限流外,ngx_http_core_module 还提供了限制数据传输速度的能力(即常说的下载速度)。

location /flv/ {
    flv;
    limit_rate_after 20m;
    limit_rate       100k;
}

这个限制是针对每个请求的,表示客户端下载前20M时不限速,后续限制100kb/s。