nginx配置

nginx配置

这是一篇比较全面的,关于nginx如何配置的手册。包含一般配置。示例等。须明白,配置不是代码,不一定严格按着配置顺序生效。

参考:

思路:

  • 配置讲解
  • nginx 命令
  • 线上的示例配置讲解。线上配置、避坑
  • 使用场景
  • 升级原有的镜像、增加组件,如何操作

配置讲解

整体

整体结构

main
http {
    upstream {}
    split_clients {}
    map {}
    geo {}
    server {
        if () {}
        location {
            limit_except {}
        }
        location {
            location {
            }
        }
    }
    server {
    }
}

包含配置块、动作指令、值指令等。

语法

注释只能用#,不能用//,报错如下:

nginx: [emerg] unknown directive "//"

配置块使用花括号,不用加分号。普通的指令,必须结尾加分号。

指令块的嵌套

在 Nginx 配置文件中,指令块是可以互相嵌套的,例如上面的示例,http 块中可以包含多个 server 块,server 块中还会包含多个 location 块,每一个块中都有相应的指令。

而每一个指令都有 Context 上下文,也就是生效的环境,这在 Nginx 的官方文档中说的很清楚,例如下面的两条指令,Context 中都表明了各自可以生效的环境,access_log 指令可以在多个上下文中生效:

Syntax:  access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
             access_log off;
Default: access_log logs/access.log combined; 
Context: http, server, location, if in location, limit_except

Syntax:  log_format name [escape=default|json|none] string ...;
Default: log_format combined "..."; 
Context: http

常用的上下文环境:

http, server, location, if

全局指令

user

定义Nginx运行的用户和用户组。需要系统存在这个用户组。

user nginx;

daemon

在docker中,需要前台启动时,这个很重要。

daemon on;  #后台启动
daemon off;  #前台启动,容器内部可能用到。

worker_processes

nginx进程数,建议设置为等于CPU总核心数。即每个CPU运行1个工作进程。

worker_processes 8;
worker_processes auto;

CPU核心数,输入 :

grep processor  /proc/cpuinfo  |  wc -l

worker_cpu_affinity

设置worker进程的cpu亲和力,达到充分利用多核cpu 。参见:worker_cpu_affinity

worker_cpu_affinity auto;
  • 2核cpu,开启2个进程
worker_processes     2;
worker_cpu_affinity 01 10;

解释:01表示启用第一个CPU内核,10表示启用第二个CPU内核

worker_cpu_affinity 01 10;表示开启两个进程,第一个进程对应着第一个CPU内核,第二个进程对应着第二个CPU内核。

  • 2核cpu,开启4个进程
worker_processes     4;
worker_cpu_affinity 01 10 01 10;  #二进制
  • 4个cpu,开启4个进程
worker_processes     4;
worker_cpu_affinity 0001 0010 0100 1000;

error_log

全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]

error_log /var/log/nginx/error.log info;

access_log

同上。具体参见:日志部分。

rewrite_log

rewrite记录。注意,只有on|off。

rewrite on;

log_format

#日志格式设定
log_format access '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $http_x_forwarded_for';

pid

进程文件。

pid        /usr/local/nginx/logs/nginx.pid;

worker_rlimit_nofile

一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(系统的值ulimit -n)与nginx进程数相除,但是nginx分配请求并不均匀,所以建议与ulimit -n的值保持一致。

worker_rlimit_nofile 65535;

events

events
	{
		# 参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。
        use epoll;
        #单个进程最大连接数(最大连接数=连接数*进程数)
        worker_connections 51200;
        multi_accept off;
        accept_mutex off;
    }

multi_accept

multi_accept可以让nginx worker进程尽可能多地接受请求。它的作用是让worker进程一次性地接受监听队列里的所有请求,然后处理。如果multi_accept的值设为off,那么worker进程必须一个一个地接受监听队列里的请求。

accept_mutex_delay

当accept_mutex功能启用后,只有一个持有mutex锁的worker进程会接受并处理请求,其他worker进程等待。

events {
   accept_mutex_delay 500ms;
}

http

通用设置

server_names_hash_bucket_size 128; #服务器名字的hash表大小
client_header_buffer_size 32k; #上传文件大小限制
large_client_header_buffers 4 64k; #设定请求缓
client_max_body_size 8m; #设定请求缓
sendfile on; #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。
autoindex on; #开启目录列表访问,合适下载服务器,默认关闭。
tcp_nopush on; #防止网络阻塞
tcp_nodelay on; #防止网络阻塞
keepalive_timeout 120; #长连接超时时间,单位是秒

fast_cgi

fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

gzip模块设置

gzip on; #开启gzip压缩输出
gzip_min_length 1k; #最小压缩文件大小
gzip_buffers 4 16k; #压缩缓冲区
gzip_http_version 1.0; #压缩版本(默认1.1,前端如果是squid2.5请使用1.0)
gzip_comp_level 2; #压缩等级
gzip_types text/plain application/x-javascript text/css application/xml;

压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。

gzip_vary on;

index显示列表

(一般为企业内部使用)

autoindex on;//自动显示目录
autoindex_exact_size off;//人性化方式显示文件大小否则以byte显示
autoindex_localtime on;//按服务器时间显示,否则以gmt时间显示

default_type

设置响应的默认MIME类型。默认:text/plain

default_type text/plain;

server

每一个server配置相当于一个虚拟主机。

虚拟主机是一种特殊的软硬件技术,它可以将网络上的每一台计算机分成多个虚拟主机,每个虚拟主机可以独立对外提供www服务,这样就可以实现一台主机对外提供多个web服务,每个虚拟主机之间是独立的,互不影响的

通过nginx可以实现虚拟主机的配置,nginx支持三种类型的虚拟主机配置

  • 基于ip的虚拟主机, (一块主机绑定多个ip地址)
  • 基于域名的虚拟主机(servername)
  • 基于端口的虚拟主机(listen如果不写ip端口模式)

listen

listen 指令在 server 块中生效,用来配置监听哪些端口,由这些端口来处理请求。listen 指令的配置如下:

img

如示例所示,listen 指令可以监听的类型有多种,可以配置监听地址和端口,也可以是仅地址和仅端口,还可以仅监听 IPv6 等等。

监听80端口,默认服务器,开启ipv6。

listen [::]:80 default_server ipv6only=on;

server_name

server_name 指令是用来配置究竟是哪个 server 来处理我们的请求的。有时候,一个 server_name 中可能会有多个域名,这时候是如何选择的呢?

类型:

  1. server_name 指令后可以跟多个域名,第一个是主域名,多个域名之间空格分隔
  2. 泛域名:仅支持在最前或最后加 *,例如:server_name *.taohui.tech
  3. 正则表达式匹配:server_name www.taohui.tech ~^www\d+\.taohui\.tech$;

匹配规则:

  1. 精确匹配(与顺序无关)
  2. * 在前的泛域名(与顺序无关)
  3. * 在后的泛域名(与顺序无关)
  4. 按文件中的顺序匹配正则表达式域名
  5. default server
    • 第 1 个
    • listen 指定 default

当 server_name 指令后有多个域名时,会有一个 server_name_in_redirect 的配置,这个配置默认关闭,它使用来控制域名重定向的,也就是这个配置开启之后,请求过来会重定向到主域名访问。

Syntax  server_name_in_redirect on | off;
Default server_name_in_redirect off; 
Context http, server, location
  1. 还可以用正则表达式创建变量

    • # 使用 $1/$2 的方式引用变量
      server { 
          server_name ~^(www\.)?(.+)$; 
          location / { root /sites/$2; } 
      }
      
      
      - ```
        # 还可以通过加一个 ?<> 的方式来命名变量
        server { 
            server_name ~^(www\.)?(?<domain>.+)$;
            location / { root /sites/$domain; } 
        }
  2. 特殊的配置规则

    • .test.tech 可以匹配 test.tech *.test.tech
    • _ 匹配所有域名请求
    • “” 匹配没有传递 host 头部的请求

root

root /var/www/example;

include

include指令加载文件位置,相当于nginx.conf位置。

#加载vhost配置文件。
include vhost/*.conf;
include       mime.types;

error_page

使用该error_page指令,您可以配置返回自定义页面以及错误代码,在响应中替换其他错误代码,或将浏览器重定向到其他URI。

error_page 404             /404.html;
error_page 500 502 503 504 /50x.html;

在以下示例中,当NGINX Plus无法找到页面时,它将代码替换301为代码404,并将客户端重定向到http:/example.com/new/path.html。当客户端仍尝试访问其旧URI的页面时,此配置很有用。该301代码通知浏览器该页面已永久移动,并且需要在返回时自动用新地址替换旧地址。

location /old/path.html {
    error_page 404 =301 http://example.com/new/path.html;
}

index

index index.html index.htm index.php;

server_tokens

启用或禁用在错误页和“服务器”响应头字段中发出nginx版本。

Syntax:	server_tokens on | off | build | string;
Default:	server_tokens on;
Context:	http, server, location

location

基本语法:location [=||*|^~] /uri/ { … }

注意:上面location、符号、路径、花括号之间有空格。尤其是正则表达式,空格不能少。

示例:

location = / {
 # 只匹配 / 查询。
}
location / {
 # 匹配任何查询,因为所有请求都以 / 开头。但是正则表达式规则和长的块规则将被优先和查询匹配。
}
location ^~ /images/ {
 # 匹配任何以 /images/ 开头的任何查询并且停止搜索。任何正则表达式将不会被测试。
}
location ~*.(gif|jpg|jpeg)$ {
 # 匹配任何以 gif、jpg 或 jpeg 结尾的请求。
}
location ~*.(gif|jpg|swf)$ {
  valid_referers none blocked start.igrow.cn sta.igrow.cn;
  if ($invalid_referer) {
    #防盗链
    rewrite ^/ http://$host/logo.png;
  }
}

# 嵌套模式
# 首先,匹配到/路径
location / { 
    # 然后我们匹配到一个最具体的子字符串,注意这不是regluar表达式
    location ^~ /css{
        # 下面是匹配的正则表达式
        location ~ /css/.*\.css$ {
            return 302;
        }
        return 402;
    }
}
# 上面,访问 /css  /cssjkjk/  /css/1.txt  返回402。
# 访问  /css/1.css  /css/2/3.css 返回302 。

表述1:

location URI {} 对当前路径及子路径下的所有对象都生效;
location = URI {} 注意URL最好为具体路径。 精确匹配指定的路径,不包括子路径,因此,只对当前资源生效;
location ~ URI {} location ~* URI {} 模式匹配URI,此处的URI可使用正则表达式,~区分字符大小写,~*不区分字符大小写;
location ^~ URI {} 禁用正则表达式

表述2:

=:对URI做精确匹配;
	location = / {
		...
	}
~:对URI做正则表达式模式匹配,区分字符大小写;
~*:对URI做正则表达式模式匹配,不区分字符大小写;
^~:对URI的左半部分做匹配检查,不区分字符大小写;注意,不是正则表达式。
不带符号:匹配起始于此uri的所有的url;

表述3:

= 严格匹配。如果这个查询匹配,那么将停止搜索并立即处理此请求。
~ 为区分大小写匹配(可用正则表达式)
!~为区分大小写不匹配
~* 为不区分大小写匹配(可用正则表达式)
!~*为不区分大小写不匹配
^~ 如果把这个前缀用于一个常规字符串,那么告诉nginx 如果路径匹配那么不测试正则表达式。

总结:

优先级:= 大于 ^~ 大于 |* 大于 /|/dir/

备注:优先级理解为,最后一次匹配的优先级,即谁最可能成为最后一次匹配。或者理解为,结束匹配的优先级。

location配置规则

location 的执行逻辑:

1、“普通 location ”的匹配规则是“最大前缀”,因此“普通 location ”的确与 location 编辑顺序无关;

2、“正则 location ”的匹配规则是“顺序匹配,且只要匹配到第一个就停止后面的匹配”;

“普通location ”与“正则 location ”之间的匹配顺序是:先匹配普通 location ,再“考虑”匹配正则 location 。

注意这里的“考虑”是“可能”的意思,也就是说匹配完“普通 location ”后,有的时候需要继续匹配“正则 location ”,有的时候则不需要继续匹配“正则 location ”。两种情况下,不需要继续匹配正则 location :

( 1 )当普通 location 前面指定了“ ^~ ”,特别告诉 Nginx 本条普通 location 一旦匹配上,则不需要继续正则匹配;

( 2 )当普通location 恰好严格匹配上,不是最大前缀匹配,则不再继续匹配正则

@路径

@路径,相当于起别名。与proxy_pass配合使用。

location / {
    try_files $uri $uri/ @backend;
}

location @backend {
    proxy_pass http://backend.example.com;
}

IP访问控制

参见 Linux运维 | nginx访问控制与参数调优

location / {
    deny IP /IP段;
    deny 192.168.1.109;
    allow 192.168.1.0/24;192.168.0.0/16;192.0.0.0/8;
}

deny all;拒绝所有。

另外,配合location的正则, if模块等,完成基于特定路径、请求头的访问控制。

用户认证访问

模块ngx_http_auth_basic_module 允许使用“HTTP基本认证”协议验证用户名和密码来限制对资源的访问

location ~ (.*)\.avi$ {
    auth_basic "closed site";
    auth_basic_user_file conf/users;
}

账号密码通过httpd-tools配置

yum install httpd
htpasswd -c -d /usr/local/users zhangyang

nginx访问状态监控

location /basic_status {
    stub_status on;
}

字符编码

location / {
     charset utf-8; #一般是在个别的location中加入此项,具体情况具体对待
     rewrite .* /index.html break;
 }

expires缓存

指定缓存时间。

#图片缓存时间设置
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
    expires 10d;
}
#JS和CSS缓存时间设置
location ~ .*\.(js|css)?$
{
     expires 1h;
}

etag缓存

etag on | off;
  • 开启缓存
location~ .*\.(gif|jpg|jpeg|png|bmp|ico|rar|css|js|zip|xml|txt|flv|swf|mid|doc|cur|xls|pdf|txt|)$ 
{
    FileETag on;
    etag_format "%X%X";
    expires 30d;
}
  • 禁用缓存配置
location ~ index\.html$
{
    add_header Cache-Control "no-store";
    expires -1;
    etag off;
}

last-modify

add_header

添加响应头信息。

add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Origin "http://local.dev.com";
add_header Access-Control-Allow-Credentials "true";

rewrite重写机制

参考:

格式:

rewrite [flag];

关键字 正则 替代内容 flag标记

正则:perl兼容正则表达式语句进行规则匹配

替代内容:将正则匹配的内容替换成replacement

flag标记:rewrite支持的flag标记

last #本条规则匹配完成后,继续向下匹配新的location URI规则

break #本条规则匹配完成即终止,不再匹配后面的任何规则

redirect #返回302临时重定向,浏览器地址会显示跳转后的URL地址

permanent #返回301永久重定向,浏览器地址栏会显示跳转后的URL地址

rewrite参数的标签段位置:

server,location,if

示例:(url增加前缀)

rewrite ^ /prefix$1;

示例:

http {
    # 定义image日志格式
    log_format imagelog '[$time_local] ' $image_file ' ' $image_type ' ' $body_bytes_sent ' ' $status;
    # 开启重写日志
    rewrite_log on;
    server {
        root /home/www;
        location / {
            # 重写规则信息
            error_log logs/rewrite.log notice;
            # 注意这里要用‘’单引号引起来,避免{}
            rewrite '^/images/([a-z]{2})/([a-z0-9]{5})/(.*)\.(png|jpg|gif)$' /data?file=$3.$4;
            # 注意不能在上面这条规则后面加上“last”参数,否则下面的set指令不会执行
            set $image_file $3;
            set $image_type $4;
        }
        location /data {
            # 指定针对图片的日志格式,来分析图片类型和大小
            access_log logs/images.log mian;
            root /data/images;
            # 应用前面定义的变量。判断首先文件在不在,不在再判断目录在不在,如果还不在就跳转到最后一个url里
            try_files /$arg_file /image404.html;
        }
        location = /image404.html {
            # 图片不存在返回特定的信息
            return 404 "image not found\n";
        }
    }
}

try_files

同rewrite一样,也能达到重写url的目的。

try_files指令可用于检查指定的文件或目录是否存在;NGINX会进行内部重定向,否则会返回指定的状态码。例如,要检查是否存在与请求URI相对应的文件,请使用try_files伪指令和$uri变量,如下所示:

server {
    root /www/data;

    location /images/ {
        try_files $uri /images/default.gif;
    }
}

该文件以URI的形式指定,该URI使用在当前位置或虚拟服务器的上下文中设置的rootalias指令进行处理。在这种情况下,如果不存在与原始URI对应的文件,则NGINX将内部重定向到由最后一个参数指定的URI,返回/www/data/images/default.gif

最后一个参数也可以是状态代码(直接在等号之后)或位置名称。在以下示例中,如果try_files指令的任何参数都无法解析到现有文件或目录,则将返回404错误。

location / {
    try_files $uri $uri/ $uri.html =404;
}

请求将被重定向到指定位置,该位置会将其传递给代理服务器。

location / {
    try_files $uri $uri/ @backend;
}

location @backend {
    proxy_pass http://backend.example.com;
}

return

location / {
     return 403; # 或者 '403'。
 }
location /permanently/moved/url {
    return 301 http://www.example.com/moved/here;
}

或者直接返回文本内容:

location /scc {
    default_type    text/plain;
    return 502 "服务正在升级,请稍后再试……";
}

返回json也是可以的:

location ~ ^/get_json {
    default_type application/json;
    return 200 '{"status":"success","result":"nginx json"}';
}

利用上请求的参数:

location ~ ^/get_text/article/(.*)_(\d+).html$ {
    default_type text/html;
    set $s $1;
    set $d $2;
    return 200 str:$s$d;
}

proxy_pass反向代理

参考:代理如何去掉或增加前缀

通常的代理服务器,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中由代理服务器向Internet上的web服务器发起请求,最终达到客户机上网的目的。

反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器

注意事项:

如果location包含了正则表达式,则 “proxy_pass”不能包含URI part 。即 proxy_pass http://localhost:8080;结尾不能带任务URI部分。

location /basic_status {
     proxy_pass http://localhost/;
}

更复杂的配置:

location /ucarapi/ {
     proxy_pass http://httpds/; #已定义的upstream
     proxy_connect_timeout 3; //连接超时时间
     proxy_read_timeout 30;
     proxy_set_header Host tapi.51ucar.cn; //HTTP头信息,后端服务器根据此来找到特定虚拟主机
     proxy_set_header X-Real-IP $remote_addr; //HTTP头信息,真实IP
     proxy_set_header X-Scheme $scheme;
}

另外,公司常用的:

upstream php {
        server 127.0.0.1:81;
    }
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /yd;
            index  index.php index.html index.htm;
            if (!-e $request_filename) {
                proxy_pass http://php;
            }
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
        location ~ \.php$ {
            proxy_set_header  Host $host:APP_PORT;
            #proxy_set_header  X-Real-IP $remote_addr;
            proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_pass   http://127.0.0.1:81;
        }
    }

proxy_pass

proxy_connect_timeout

proxy_set_header

代理时,添加请求头。

upstream

反向代理配合upstream使用

upstream httpds {
    server 192.168.43.152:80;
    server 192.168.43.153:80;
}

weight(权重)

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

upstream httpds {
    server 127.0.0.1:8050 weight=10 down;
    server 127.0.0.1:8060 weight=1;
    server 127.0.0.1:8060 weight=1 backup;
}

down:表示当前的server暂时不参与负载

weight:默认为1.weight越大,负载的权重就越大。

backup: 其它所有的非backup机器down或者忙的时候,请求backup机器。

max_conns

可以根据服务的好坏来设置最大连接数,防止挂掉,比如1000,我们可以设置800

upstream httpds {
    server 127.0.0.1:8050 weight=5 max_conns=800;
    server 127.0.0.1:8060 weight=1;
}

max_fails、 fail_timeout

max_fails:失败多少次 认为主机已挂掉则,踢出,公司资源少的话一般设置2~3次,多的话设置1次

max_fails=3 fail_timeout=30s代表在30秒内请求某一应用失败3次,认为该应用宕机,后等待30秒,这期间内不会再把新请求发送到宕机应用,而是直接发到正常的那一台,时间到后再有请求进来继续尝试连接宕机应用且仅尝试1次,如果还是失败,则继续等待30秒…以此循环,直到恢复。

upstream httpds {
    server 127.0.0.1:8050 weight=1 max_fails=1 fail_timeout=20;
    server 127.0.0.1:8060 weight=1;
}

负载均衡算法

轮询、 weight、 ip_hash、 url_hash、 least_conn、 least_time

健康检查模块

配置一个status的location

location /status {
	check_status;
}

在upstream配置如下

check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;

fast_pass

参考:ngx_http_fastcgi_module 的那些事

示例:

location ~ \.php($|/) {
 fastcgi_pass unix:/dev/shm/php-fpm.unix;  #最重要的一项,根据实际情况来配置(根据php的配置文件listen的配置来配置,其值可以是一个域名、IP地址:端口、或者是一个Unix的Socket文件。fastcgi_index index.php;  #当请求以/结尾的时候,会将请求传递给所设置的index.php文件处理。
 fastcgi_split_path_info ^(.+\.php)(.*)$; #Nginx默认获取不到PATH_INFO的值,得通过fastcgi_split_path_info指定定义的正则表达式来给$fastcgi_path_info赋值。
 fastcgi_param PATH_INFO $fastcgi_path_info; 
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 include fastcgi.conf;
}

配置php的fastcgi:

server {
    listen       80;
    server_name  localhost;
    location / {
        root   /usr/share/nginx/html;
        index  index.php index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
    location ~ \.php$ {
        root           html;
        fastcgi_pass   php:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html$fastcgi_script_name;
        include        fastcgi_params;
    }
}

include指令加载文件位置,相当于nginx.conf位置,fastcgi_params内容如下:

fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  REQUEST_SCHEME     $scheme;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

ngx_http_fastcgi_module用来处理FastCGI的模块。PHP一般是以PHP-CGI的形式在运行,它就是一种FastCGI,我们在进程中看到的PHP-FPM是PHP-CGI的管理调度器。

四个常见、重要的配置项

fastcgi_pass

作用域:location, if in location

设置FastCGI服务,其值可以是一个域名、IP地址:端口、或者是一个Unix的Socket文件。

同时,它也只支持一个FastCGI服务集群。

# TCP形式传递
fastcgi_pass localhost:9000;

# Socket形式传递
fastcgi_pass unix:/tmp/fastcgi.socket;

# 传递给集群
upstream cloud {
    server cgi_1.cloud.com;
    server cgi_2.cloud.com;
}
fastcgi_pass cloud;

fastcgi_param

作用域:http, server, location

设置一个传递给FastCGI服务的参数,可以是文本或者是变量。

# 例如在接入层Nginx上面传递如下5个参数
fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;

# 那么在FastCGI上面,例如PHP-CGI上面就可以通过$_SERVER这个超全局变量获取。
$_SERVER['REMOTE_ADDR']
$_SERVER['REMOTE_PORT']
$_SERVER['SERVER_ADDR']
$_SERVER['SERVER_PORT']
$_SERVER['SERVER_NAME']

可传递的参数,遵循CGI/1.1规范定义。

可以从Github上面看到Nginx在3年前实现FastCGI的参数传递后,基本就没变过了。

fastcgi_index

作用域:http, server, location

当请求以/结尾的时候,会将请求传递给所设置的index.php文件处理。

fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/www/scripts/php$fastcgi_script_name;

fastcgi_split_path_info

作用域:location

Nginx默认获取不到PATH_INFO的值,得通过fastcgi_split_path_info指定定义的正则表达式来给$fastcgi_path_info赋值。

其正则表达式必须要有两个捕获。

  • 第一个捕获的值会重新赋值给$fastcgi_script_name变量。
  • 第二个捕获到的值会重新赋值给$fastcgi_path_info变量。

例子:

location ~ ^(.+\.php)(.*)$ {
    fastcgi_split_path_info       ^(.+\.php)(.*)$;
    fastcgi_param SCRIPT_FILENAME /path/to/php$fastcgi_script_name;
    fastcgi_param PATH_INFO       $fastcgi_path_info;
}

原始请求是 /show.php/article/0001

通过分割,FastCGI得到的结果是:

  • SCRIPT_FILENAME: /path/to/php/show.php
  • PATH_INFO: /article/0001

Nginx在0.7.31以前是没有fastcgi_split_path_info这个指令的,而0.7.x这个版本一直存活了好多年,后面才高歌猛进,导致网上存在大量旧版本通过正则自己设置PATH_INFO的方法。

踩了好多次依旧不记得怎么设置的ThinkPHP

为什么总是踩坑?因为我们都会通过重写来隐藏index.php文件,而ThinkPHP的教程,默认教的是旧版Nginx写法,且URL_MODE必须设置为3也说得很隐晦(URL_MODE默认为0)。

例如ThinkPHP的说明有一段旧版的Nginx设置指引。

location / { //..省略部分代码
  if (!-e $request_filename) {
  rewrite  ^(.*)$  /index.php?s=$1  last;
  break;
   }
}

该规则是通过将请求rewrite给/index.php?s=来实现的,其ThinkPHP的URL_MODE配置必须为3,也就是兼容模式。

如果使用本文中的传递PATH_INFO方式,且隐藏index.php,则ThinkPHP的URL_MODE需要改为2。

如果使用本文中的传递PATH_INFO方式,但不隐藏index.php,则ThinkPHP的URL_MODE改为1。

cgi.fix_pathinfo

cgi.fix_pathinfo参数,藏在PHP-FPM的php.ini配置里面,其默认值为1。

这里存在一个安全风险,我也不通,详情不表,看鸟哥的文章:http://www.laruence.com/2010/05/20/1495.html

习惯性将其设置为0即可。

if 模块

if语句块长用在做单独的限制,如限制访问特定的资源,然后对此类请求做处理,rewire或者deny或者proxy_pass等等。

if ($http_user_agent ~ MSIE) {proxy_pass
  ^(.*)$ /msie/$1 break;
} #如果UA包含"MSIE",rewrite请求到/msid/目录下

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
 set $id $1;
 } #如果cookie匹配正则,设置变量$id等于正则引用部分

if ($request_method = POST) {
 return 405;
} #如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302

if ($slow) {
 limit_rate 10k;
} #限速,$slow可以通过 set 指令设置

if (!-f $request_filename){  #-e 也行,表示存在
 break;
 proxy_pass http://127.0.0.1;
} #如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查

if ($args ~ post=140){
 rewrite ^ http://example.com/ permanent;
} #如果query string中包含"post=140",永久重定向到example.com

location ~* \.(gif|jpg|png|swf|flv)$ {
 valid_referers none blocked www.jefflei.com www.leizhenfang.com;
 if ($invalid_referer) {
 return 404;
 } #防盗链
}

文件判断

不管filename是什么类型,!-e加了!就取反

-e filename 如果 filename存在,则为真
-d filename 如果 filename为目录,则为真 
-f filename 如果 filename为常规文件,则为真
-L filename 如果 filename为符号链接,则为真
-r filename 如果 filename可读,则为真 
-w filename 如果 filename可写,则为真 
-x filename 如果 filename可执行,则为真
-s filename 如果文件长度不为0,则为真
-h filename 如果文件是软链接,则为

https

在http配置下新增一个server

server {
        listen 443 ssl;
        server_name localhost;
        ssl_certificate /usr/local/nginx/ssl/nginx.crt;
        ssl_certificate_key /usr/local/nginx/ssl/nginx.key;
        ssl_session_cache    shared:SSL:1m;
        ssl_session_timeout  5m;
        #禁止在header中出现服务器版本,防止黑客利用版本漏洞攻击
        server_tokens off;
        #如果是全站 HTTPS 并且不考虑 HTTP 的话,可以加入 HSTS 告诉你的浏览器本网站全站加密,并且强制用 HTTPS 访问
        fastcgi_param   HTTPS               on;
        fastcgi_param   HTTP_SCHEME         https;
        access_log /usr/local/nginx/logs/httpsaccess.log;
        root /home/movie/;
        location / {
             proxy_set_header X-Forwarded-For $remote_addr;
             #proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
             proxy_pass http://localhost:55055;
        }
        location /mp4 {
                index index.html index.htm;
        }
    }

http转https请求

server {
 listen 80;
 server_name dev-payment.xxxx.cn;
 return 307 https://dev-payment.xxxx.cn$request_uri;
}

注意:曾经遇到过这么一个坑,http转https的时候会将POST转换为GET请求,此时需要这样配置

日志

参考:Nginx 日志配置实践

前言

Nginx日志对于统计、系统服务排错很有用。

Nginx日志主要分为两种:access_log(访问日志)和error_log(错误日志)。通过访问日志我们可以得到用户的IP地址、浏览器的信息,请求的处理时间等信息。错误日志记录了访问出错的信息,可以帮助我们定位错误的原因。

本文将详细描述一下如何配置Nginx日志。

设置access_log

访问日志主要记录客户端的请求。客户端向Nginx服务器发起的每一次请求都记录在这里。客户端IP,浏览器信息,referer,请求处理时间,请求URL等都可以在访问日志中得到。当然具体要记录哪些信息,你可以通过log_format指令定义。

语法
access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]]; 
# 设置访问日志

access_log off; 
# 关闭访问日志
  • path 指定日志的存放位置。
  • format 指定日志的格式。默认使用预定义的combined。
  • buffer 用来指定日志写入时的缓存大小。默认是64k。
  • gzip 日志写入前先进行压缩。压缩率可以指定,从1到9数值越大压缩比越高,同时压缩的速度也越慢。默认是1。
  • flush 设置缓存的有效时间。如果超过flush指定的时间,缓存中的内容将被清空。
  • if 条件判断。如果指定的条件计算为0或空字符串,那么该请求不会写入日志。

另外,还有一个特殊的值off。如果指定了该值,当前作用域下的所有的请求日志都被关闭。

作用域

可以应用access_log指令的作用域分别有http,server,location,limit_except。也就是说,在这几个作用域外使用该指令,Nginx会报错。

以上是access_log指令的基本语法和参数的含义。下面我们看一几个例子加深一下理解。

基本用法
access_log /var/logs/nginx-access.log

该例子指定日志的写入路径为/var/logs/nginx-access.log,日志格式使用默认的combined。

access_log /var/logs/nginx-access.log buffer=32k gzip flush=1m

该例子指定日志的写入路径为/var/logs/nginx-access.log,日志格式使用默认的combined,指定日志的缓存大小为32k,日志写入前启用gzip进行压缩,压缩比使用默认值1,缓存数据有效时间为1分钟。

使用log_format自定义日志格式

Nginx预定义了名为combined日志格式,如果没有明确指定日志格式默认使用该格式:

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

如果不想使用Nginx预定义的格式,可以通过log_format指令来自定义。

语法
log_format name [escape=default|json] string ...;
  • name 格式名称。在access_log指令中引用。
  • escape 设置变量中的字符编码方式是json还是default,默认是default。
  • string 要定义的日志格式内容。该参数可以有多个。参数中可以使用Nginx变量。

下面是log_format指令中常用的一些变量:

$bytes_sent
发送给客户端的总字节数

$body_bytes_sent
发送给客户端的字节数,不包括响应头的大小

$connection
连接序列号

$connection_requests
当前通过连接发出的请求数量

$msec
日志写入时间,单位为秒,精度是毫秒

$pipe
如果请求是通过http流水线发送,则其值为"p",否则为“."

$request_length
求长度(包括请求行,请求头和请求体)

$request_time
请求处理时长,单位为秒,精度为毫秒,从读入客户端的第一个字节开始,直到把最后一个字符发送张客户端进行日志写入为止

$status
响应状态码

$time_iso8601
标准格式的本地时间,形如“2017-05-24T18:31:27+08:00”

$time_local
通用日志格式下的本地时间,如"24/May/2017:18:31:27 +0800"

$http_referer
请求的referer地址。

$http_user_agent
客户端浏览器信息。

$remote_addr
客户端IP

$http_x_forwarded_for
当前端有代理服务器时,设置web节点记录客户端地址的配置,此参数生效的前提是代理服务器也要进行相关的x_forwarded_for设置。

$request
完整的原始请求行,如 "GET / HTTP/1.1"

$remote_user
客户端用户名称,针对启用了用户认证的请求

$request_uri
完整的请求地址,如 "https://daojia.com/"

下面演示一下自定义日志格式的使用:

access_log /var/logs/nginx-access.log
   mainlog_format  main  '$remote_addr - $remote_user [$time_local] "$request" ' 
    '$status $body_bytes_sent "$http_referer" ' 
     '"$http_user_agent" "$http_x_forwarded_for"';

我们使用log_format指令定义了一个main的格式,并在access_log指令中引用了它。假如客户端有发起请求:https://suyunfe.com/,我们看一下我截取的一个请求的日志记录:

112.195.209.90 - - [20/Feb/2018:12:12:14 +0800] "GET / HTTP/1.1" 200 190 "-" "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Mobile Safari/537.36" "-"

我们看到最终的日志记录中$remote_user$http_referer$http_x_forwarded_for都对应了一个-,这是因为这几个变量为空。

设置error_log

错误日志在Nginx中是通过error_log指令实现的。该指令记录服务器和请求处理过程中的错误信息。

语法

配置错误日志文件的路径和日志级别。

error_log file [level];
Default: 
error_log logs/error.log error;

第一个参数指定日志的写入位置。

第二个参数指定日志的级别。level可以是debug, info, notice, warn, error, crit, alert,emerg中的任意值。可以看到其取值范围是按紧急程度从低到高排列的。只有日志的错误级别等于或高于level指定的值才会写入错误日志中。默认值是error。

基本用法
error_log /var/logs/nginx/nginx-error.log

它可以配置在:main, http, mail, stream, server, location作用域。

例子中指定了错误日志的路径为:/var/logs/nginx/nginx-error.log,日志级别使用默认的error。

open_log_file_cache

每一条日志记录的写入都是先打开文件再写入记录,然后关闭日志文件。如果你的日志文件路径中使用了变量,如access_log /var/logs/$host/nginx-access.log,为提高性能,可以使用open_log_file_cache指令设置日志文件描述符的缓存。

语法
open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
  • max 设置缓存中最多容纳的文件描述符数量,如果被占满,采用LRU算法将描述符关闭。
  • inactive 设置缓存存活时间,默认是10s。
  • min_uses 在inactive时间段内,日志文件最少使用几次,该日志文件描述符记入缓存,默认是1次。
  • valid:设置多久对日志文件名进行检查,看是否发生变化,默认是60s。
  • off:不使用缓存。默认为off。
基本用法
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;

它可以配置在http、server、location作用域中。

例子中,设置缓存最多缓存1000个日志文件描述符,20s内如果缓存中的日志文件描述符至少被被访问2次,才不会被缓存关闭。每隔1分钟检查缓存中的文件描述符的文件名是否还存在。

总结

Nginx中通过access_log和error_log指令配置访问日志和错误日志,通过log_format我们可以自定义日志格式。如果日志文件路径中使用了变量,我们可以通过open_log_file_cache指令来设置缓存,提升性能。

变量

nginx自带的变量如下,除此之外,还可以通过set来设置自己的变量。详见调试->set。

变量 说明
$args 请求中的参数,如www.123.com/1.php?a=1&b=2的$args就是a=1&b=2
$content_length HTTP请求信息里的”Content-Length”
$conten_type HTTP请求信息里的”Content-Type”
$document_root nginx虚拟主机配置文件中的root参数对应的值
$document_uri 当前请求中不包含指令的URI,如www.123.com/1.php?a=1&b=2的$document_uri就是1.php,不包含后面的参数
$host 主机头,也就是域名
$http_user_agent 客户端的详细信息,也就是浏览器的标识,用curl -A可以指定
$http_cookie 客户端的cookie信息
$limit_rate 如果nginx服务器使用limit_rate配置了显示网络速率,则会显示,如果没有设置, 则显示0
$remote_addr 客户端的公网ip
$remote_port 客户端的port
$remote_user 如果nginx有配置认证,该变量代表客户端认证的用户名
$request_body_file 做反向代理时发给后端服务器的本地资源的名称
$request_method 请求资源的方式,GET/PUT/DELETE等
$request_filename 当前请求的资源文件的路径名称,相当于是document_uri的组合
$request_uri 请求的链接,包括和args
$scheme 请求的协议,如ftp,http,https
$server_protocol 客户端请求资源使用的协议的版本,如HTTP/1.0,HTTP/1.1,HTTP/2.0等
$server_addr 服务器IP地址
$server_name 服务器的主机名
$server_port 服务器的端口号
$uri 和$document_uri相同
$http_referer 客户端请求时的referer,通俗讲就是该请求是通过哪个链接跳过来的,用curl -e可以指定

geo

参见:Nginx通过geo模式实现限速白名单和全局负载均衡 - 运维笔记

启动命令

Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

示例:

# 帮助
nginx -h
# 启动
nginx
# 前台启动
nginx -g 'daemon off;'  # -g 可以指定全局参数
# 加载新配置
nginx -s reload
# 停止
nginx -s stop
nginx -s quit
# 测试配置文件是否正确,另外此命令可以看到配置文件的位置。
nginx -t #默认位置下的配置
nginx -t -c /path/to/nginx.conf
# 查看nginx是否启动
ps aux |grep nginx
# 查看安装时,使用的配置,或者安装了哪些模块
nginx -V

模块

模块安装

模块解读

ngx_http_stub_status_module

参见:Nginx解读内置非默认模块

附录

Nginx服务器性能优化的三大方面

摘抄: 原文

Nginx服务器非常快,但是Nginx的默认设置并没有针对具体的硬件进行调优。在这篇文章中,我们要把Nginx的性能发挥到极限。Nginx的配置分为三大部分:worker进程配置、I/O配置、TCP配置。我们将分别对这三大配置展开讨论,并在最后给出综合性的配置。

Nginx的worker进程配置

worker_processes

worker_processes directive指定nginx worker进程的数量。它是一个全局性配置,不属于events模块,也不属于http或location模块。

worker_processes 1;

默认的值是1,意味着nignx只打开一个worker进程。最优的设置是worker进程数量要与CPU的核数相等。我们可以用lscpu命令来找出CPU的核数。

lscpu

也可以用

cat /proc/cpuinfo | grep 'processor' | wc -l

另外,我们也可以将worker_processes的值设为auto,这样nginx会自动检测CPU核数并打开相同数量的worker进程。

当nginx添加了SSL证书时,最好要打开多个worker进程。SSL握手会进行硬盘I/O操作。 所以打开多个worker进程有利于性能的提升。

accept_mutex

当我们为nginx打开了多个worker进程后,我们需要配置如何选择worker进程来完成相应的请求处理。在events模块中,我们可以设置

events {
 accept_mutex on;
 }

accept_mutex会轮流来选择worker进程。Nginx默认开启了accept_mutex。

如果accept_mutex的值被设为off,那么当有请求需要处理时,所有的worker进程都会从waiting状态中唤醒,但是只有一个worker进程能处理请求,这造成了thundering herd现象,这个现象每一秒钟会发生多次。它使服务器的性能下降,因为所有被唤醒的worker进程在重新进入waiting状态前会占用一段CPU时间。

accept_mutex_delay

当accept_mutex功能启用后,只有一个持有mutex锁的worker进程会接受并处理请求,其他worker进程等待。accept_mutex_delay指定的时间就是这些worker进程的等待时间,过了等待时间下一个worker进程便取得mutex锁,处理请求。accept_mutex_delay在events模块中指定,默认的值为500ms。

events {
 accept_mutex_delay 500ms;
 }

worker_connections

worker_connections的默认值是512,它在events模块中。它指定了一个worker进程在同一时间可以处理的最大请求数。

events {
 worker_connections 512;
 }

将它的值增加到1024左右。

web服务器同时处理的请求数并不等于它同时服务的客户端数量。一个浏览器会打开多个并发连接来下载网页的各个部分,如图片、脚本等等。而且不同的浏览器对同一个网页打开的并发连接数量也会有所不同。

worker_rlimit_nofile

由于每一个socket都会打开一个文件描述符,所以服务器可以同时处理连接数量受到系统文件描述符数量的限制。如果nginx打开的socket数量超过了文件描述符的数量,那么在error.log文件中会出现too many opened files错误。我们可以用下面的命令来查看文件描述符的数量:

$ ulimit -n

Nginx worker进程默认的用户名是www-data,用户www-data所拥有的文件描述符的数量要大于worker进程数量与worker_connections之乘积。 nginx有一个worker_rlimit_nofile directive,可以用来设置系统可用的文件描述符。这与ulimit设置可用文件描述符的作用是一样的。如果它们都设置了可用文件描述符,那么worker_rlimit_nofile会覆盖ulimit的设置。

worker_rlimit_nofile 20960;

查看操作系统对一个进程施加的限制,我们可以用命令读取/etc/$pid/limits文件,$pid是进程的pid。

multi_accept

multi_accept可以让nginx worker进程尽可能多地接受请求。它的作用是让worker进程一次性地接受监听队列里的所有请求,然后处理。如果multi_accept的值设为off,那么worker进程必须一个一个地接受监听队列里的请求。

events {
 multi_accept on;
 }

默认Nginx没有开启multi_accept。

如果web服务器面对的是一个持续的请求流,那么启用multi_accept可能会造成worker进程一次接受的请求大于worker_connections指定可以接受的请求数。这就是overflow,这个overflow会造成性能损失,overflow这部分的请求不会受到处理。

use

Nginx处理请求的方法有很多种,每一个方法都允许Nginx Worker进程监测多个socket文件描述符。这些方法都分别依赖于特定的平台,用于生成Nginx二进制文件的configure命令会选择适合当前平台的最有效的方法。如果要使用另外的方法,那么我们必须先启用这些方法。

我们可以用use这个directive来选择另外的处理请求的方法。use directive属于events模块。

events {
 use select;
 }

Nginx支持以下请求处理方法:

  • select: 这是一种标准的请求处理方法。如果一个平台上缺少相应的更加有效的方法,那么Nginx会自动使用select方法。
  • poll: 这是一种标准的请求处理方法。如果一个平台上缺少相应的更加有效的方法,那么Nginx会自动使用poll方法。
  • kqueue: 这是BSD家族操作系统上可用的一种高效的请求处理方法。可用于FreeBSD, OpenBSD, NetBSD和OS X。kqueue方法会忽略multi_accept。
  • epoll: 这是Linux系统上可用的一种高效的请求处理方法,类似于kqueue。它有一个额外的directive,那就是epoll_events。epoll_events指定了Nginx可以向内核传递的事件数量。默认的值是512。

Nginx的I/O配置

sendfile

当一个程序需要传输文件时,Linux内核首先将文件数据缓冲,然后将文件数据传送给程序缓冲,最后程序将文件数据传输到目的地。Sendfile方法是一种数据传输的更高效的方法,数据在内核中的文件描述符之间传输,而不需要将数据传输给程序缓冲。这种方法的结果是改善了对操作系统资源的利用。

我们可以用sendfile directive来启用sendfile方法,在http,server,location三个模块都可以定义。

http {
 sendfile on ;
 }

默认情况下,sendfile 的值是on。

Direct I/O

通常,Linux内核会尝试优化和缓存读写请求。数据缓存在内核里,将来任何的读取相同数据的请求会变得更快,因为不需要从缓慢的硬盘中读取数据。

Direct I/O是文件系统的一个功能,它允许程序绕过内核缓存,直接在硬盘上读写数据,这可以充分利用CPU频数,改善缓存的有效性。Directo I/O适用于访问率少的数据。这些数据不需要缓存在任何位置。我们可以用directio directive来启用这一功能,在http, server和location当中定义。

location /video/ {
 directio 4m;
 }

在上面的设置中,任何大于4M的文件都将以Direct I/O的形式直接从硬盘读取。默认directio没有启用。

如果某个请求是通过directo I/O,那么这个请求不能使用sendfile功能。

Direct I/O取决于硬盘的块大小。Nginx可以使用directio_alignment directive来设置块大小,在http, server, location中定义

location /video/ {
 directio 4m;
 directio_alignment 512;
 }

512字节适用于大多数情况。如果Linux文件系统是XFS,那么块大小应该为4KB。

异步I/O

异步I/O允许一个进程在不阻塞和等待的情况下进行I/O操作。

aio在http, server和location中定义。Linux2.6.22+和FreeBSD4.3支持aio。

location /data {
 aio on;
 }

默认情况下,aio是关闭的。在Linux发行版上,要先启用directio后才能启用aio。在FreeBSD上,必须先关闭sendfile才能让aio生效。

如果Nginx没有–with-file-aio这个模块,那么启用aio会导致unknown directive aio错误。

aio可以指定线程数,这个多线程功能只在Linux发行版上才可用,并且只能在epoll, kqueue 或 eventport方法下可用。

为了使用多线程,在编译Nginx时要添加–with-threads选项。然后在/etc/nignx/nignx.conf文件中使用一个全局设置。

thread_pool io_pool threads=16;
 http {
 ....
 location /data {
 sendfile on;
 aio threads=io_pool;
 }
 }

综合I/O设置

这三个directive可以综合起来实现不同的目标。在下面的配置中,如果文件的大小小于directio指定的大小,那么使用sendfile功能。以异步I/O的方式读取directio服务的文件。

location /archived-data/ {
 send file on;
 aio on;
 directio 4m;
 }

Nginx的TCP配置

HTTP是一个应用层协议,在传输层使用TCP协议。在TCP协议中,数据是以一块一块的TCP数据包传输的。Nginx提供了多个directive,可以用来调整TCP栈。

TCP_NODELAY

TCP/IP网络有一个“小数据包”的问题,如果一条信息中只有一个字符,那么网络可能会堵塞。这样的数据包大小为41字节,其中TCP信头40字节,内容为1字节。像这种小数据包,它们的开销是4000%。大量的小数据包可以迅速让网络饱和。

John Nagle发明了Nagle算法,它在一定的时间段,将小数据包暂存,将这些小数据包集合起来,整合为一个数据包发送,在下一个时间段又是如此。这改善了网络传输的效率。时间段通常为200ms。

但值得注意的是,小数据包的问题在telnet的通信过程中仍然存在。在telnet中,每一次敲键都分别在网络发送。不过这跟web服务器没有关联。web服务器发送的文件大多是大数据包,所以它们会被立即发送,而不需要等待200ms。

TCP_NODELAY可以用来关闭Nagle的缓冲算法,将数据尽快地发送。Nginx可以在http, server, location当中定义

http {
tcp_nodelay on;
}

Nginx默认启用了tcp_nodelay。Nginx在keep-alive模式下会使用tcp_nodelay。

TCP_CORK

除了Nagle算法外,Linux内核提供了一个TCP_CORK选项,也可以解决小数据包问题。TCP_CORK告诉TCP栈将数据包附加在另外一个数据包,当数据包饱和时或程序明确地指示发送时再发送。在FreeBSD和Mac OS系统上,TCP_NOPUSH选项相当于TCP_CORK。

Nginx可以在http, server和location模块中定义tcp_nopush。

http {
 tcp_nopush on;
 }

Nginx默认启用了tcp_nopush

上面的两个directives负责不同的任务。tcp_nodelay可以降低网络延迟,而tcp_nopush可以优化发送的数据包。

同时启用tcp_nopush和sendfile可以确保在传输文件时,内核总是生成最大量的完整TCP数据包以供传输,而最后一个TCP数据包可以是不完整的,这个不完整的数据包

大综合

下面的配置综合了前面所讨论的worker进程配置、I/O配置、TCP配置。

worker_processes 1;     #假设CPU为单核
worker_rlimit_nofile 8000;

events {
 multi_accept on;
 use epoll;
 worker_connections 1024;
 }

http {
 sendfile on;
 aio on;
 directio 4m;
 tcp_nopush on;
 tcp_nodelay on;
 }

做完上述配置后,重启nginx

sudo service nginx restart

性能测试对比

做上述修改前,主页负载测试结果如下,平均花费4814毫秒加载完主页。

Nginx服务器性能优化

做修改后,主页负载测试结果如下,平均花费3851毫秒加载完主页,节约1秒钟。不过从下图中可以看到虽然样本时间下降了,延迟却有所上升。当连接数少时,延迟在500ms左右,而当连接数多时,延迟却升到了2000多。

Nginx服务器性能优化

调试

nginx设置,如location的匹配顺序、rewrite等,单从手册和别人的说明,都不如调试更加直观。调试,能直观的看到location等配置的过程,效果,这样就能更加确定匹配方式。个人目前,想到一下方式进行调试。

set方法

通过set来测试是否匹配到特殊的指令块,如if、location等。

server{
    set $loginfo 0;
    if (!-e $request_filename){
        set $loginfo 1234;
        rewrite ^/(.*) /index.php/$1 last;
    }
    add_header 'Log-Info' $loginfo;
}

调试过程:

1586766155163

查看匹配结果

http://dev.announce.com:30029/admin/index/index.html

1586760006026

但是,如果proxy_pass返回错误,结果标记 Loginfo 就看不到了。

add_header

add_header不能在if模块内设置,但是可以通过set 变量,然后将此变量在通过add_header的来显示。

deny

deny all。先匹配到的location会直接返回。

return

return 302;
return 403;

配合nodejs

调试环境搭建

使用docker-compose等方式。