Iawen's Blog

我喜欢这样自由的随手涂鸦,因为我喜欢风......

一、Nginx 的安装

<一> 安装依赖

<二> 编译Nginx(结合Lua 模块)

注:Lua 模块在Nginx1.4以上版本兼容不太文档

1、下载安装LuaJit(2.0/2.1)并安装

下载地址 编辑 Makefile文件,找到ldconfig位置(75行) 原内容是:LDCONFIG= ldconfig -n 修改为:LDCONFIG= /sbin/ldconfig -n

make --PREFIX=/usr/local/luajit && make intall

2、下载 ngx_devel_kit (NDK) 及 ngx_lua

ngx_devel_kit lua-nginx-module

3、编译安装NGINX

设置变量:

export LUAJIT_LIB=/usr/local/lib
export LUAJIT_INC=/usr/local/include/luajit-2.0
 
./configure --prefix=/usr/local/nginx \
    --with-http_ssl_module --with-http_flv_module \
    --with-http_gzip_static_module --with-http_stub_status_module \
    --with-openssl=/home/download/openssl-1.1.1-pre9 \
    --http-log-path=/var/log/nginx/access.log \
    --http-client-body-temp-path=/var/tmp/nginx/client \
    --http-proxy-temp-path=/var/tmp/nginx/proxy \
    --http-fastcgi-temp-path=/var/tmp/nginx/fcgi \
    --with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \
    --add-module=/home/download/ngx_devel_kit-0.3.1rc1 \
    --add-module=/home/download/lua-nginx-module-0.10.12

二、环境设置

<一>、Nginx 用户组

groupadd www-data
useradd -g www-data www-data

<二>、设置介绍

nginx 配置文件主要分成四个部分:

  • main,全局设置,影响其它部分所有设置
  • server,主机服务相关设置,主要用于指定虚拟主机域名、IP和端口
  • location,URL匹配特定位置后的设置,反向代理、内容篡改相关设置
  • upstream,上游服务器设置,负载均衡相关配置 他们之间的关系式:server继承main,location继承server;upstream既不会继承指令也不会被继承。

<三>、Nginx Https 设置

http {
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen              443 ssl;
        server_name example.com;
        keepalive_timeout   70;

        access_log /var/log/xxx.log;
        root /xxx/xxx/;

        #证书在服务器的绝对位置
        ssl_certificate /xxx/213996598310998.pem;
        ssl_certificate_key /xxx/213996598310998.key;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
    }
}

<四>、日志按月滚动

vim /etc/logrotate.d/nginx

/data/logs/nginx/*.log {
    missingok
    dateext
    dateformat -%Y%m%d
    notifempty
    monthly
    rotate 7
    size 200M
    sharedscripts
    postrotate
        if [ -f /usr/local/nginx/logs/nginx.pid ]; then
            kill -USR1 `cat /usr/local/nginx/logs/nginx.pid`
        fi
    endscript
}

crontab -e #添加以下代码
0 0 1 * * /usr/sbin/logrotate -vf /etc/logrotate.d/nginx #每天凌晨定时执行脚本

<五>、worker_processes与worker_connections

1. worker_processes

worker角色的进程个数(nginx启动后有多少个worker处理http请求。master不处理请求,而是根据相应配置文件信息管理worker进程. master进程主要负责对外揽活(即接收客户端的请求),并将活儿合理的分配给多个worker,每个worker进程主要负责干活(处理请求))。 最理想的worker_processes值取决于很多因素,包含但不限于CPU的核数,存储数据的硬盘驱动器个数(跟这个有什么关系?难道和cpu一样,存在跨区域读取数据问题),以及负载模式(?这个是什么?)当其中任何一个因素不确定的时候,将其设置为cpu核数或许是一个比较好的初始值,“自动”也基本是如此确认一个参数值的。 “自动”这个参数值是从nginx 1.3.8和nginx 1.2.5 开始进行支持的,自动参数可以自动检测 cpu cores 并设置 worker_processes 参数 。 如果nginx处理的是cpu密集型(比较耗费cpu的)的操作,建议将此值设置为cpu个数或cpu的核数。

2.worker_connections

官方解释如下,个人认为是每一个worker进程能并发处理(发起)的最大连接数(包含所有连接数)。 不能超过最大文件打开数:在linux终端中输入ulimit -a进行查看

<五>、杂项

# 隐藏Nginx版本信息:
server_tokens off;

三、Nginx模型

<一> 进程

Nginx採用多进程模型,单Master—多Worker,由Master处理外部信号、配置文件的读取及Worker的初始化。Worker进程採用单线程、非堵塞的事件模型(Event Loop,事件循环)来实现port的监听及client请求的处理和响应,同一时候Worker还要处理来自Master的信号。 因为Worker使用单线程处理各种事件。所以一定要保证主循环是非堵塞的,否则会大大减少Worker的响应能力。

<二> 协程(Coroutine)

协程类似一种多线程,与多线程的差别有:

    1. 协程并不是os线程,所以创建、切换开销比线程相对要小。
    1. 协程与线程一样有自己的栈、局部变量等,可是协程的栈是在用户进程空间模拟的,所以创建、切换开销非常小。
    1. 多线程程序是多个线程并发运行。也就是说在一瞬间有多个控制流在运行。而协程强调的是一种多个协程间协作的关系,仅仅有当一个协程主动放弃运行权,还有一个协程才干获得运行权,所以在某一瞬间,多个协程间仅仅有一个在运行。
    1. 因为多个协程时仅仅有一个在执行,所以对于临界区的訪问不须要加锁。而多线程的情况则必须加锁。
    1. 多线程程序因为有多个控制流。所以程序的行为不可控,而多个协程的运行是由开发人员定义的所以是可控的。 Nginx的每一个Worker进程都是在epoll或kqueue这种事件模型之上,封装成协程,每一个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的,所以即使ngx_lua须要运行Lua,相对C有一定的开销,但依旧能保证高并发能力。

<三> 负载均衡

  • 1、轮询,即Round Robin,根据Nginx配置文件中的顺序,依次把客户端的Web请求分发到不同的后端服务器。
  • 2、最少连接
  • 3、IP地址哈希
  • 4、基于权重的负载均衡

<四> 子请求(subrequest)

事实上在Nginx 世界里有两种类型的“请求”。一种叫做“主请求”(main request),而还有一种则叫做“子请求”(subrequest)。 所谓“主请求”。就是由 HTTP client从 Nginx 外部发起的请求。比方。从浏览器訪问Nginx就是一个“主请求”。 而“子请求”则是由 Nginx 正在处理的请求在 Nginx 内部发起的一种级联请求。“子请求”在外观上非常像 HTTP 请求,但实现上却和 HTTP 协议乃至网络通信一点儿关系都没有。它是 Nginx 内部的一种抽象调用,目的是为了方便用户把“主请求”的任务分解为多个较小粒度的“内部请求”,并发或串行地訪问多个 location 接口。然后由这些 location 接口通力协作,共同完毕整个“主请求”。当然。“子请求”的概念是相对的,不论什么一个“子请求”也能够再发起很多其它的“子子请求”。甚至能够玩递归调用(即自己调用自己)。 当一个请求发起一个“子请求”的时候,依照 Nginx 的术语,习惯把前者称为后者的“父请求”(parent request)。

location /main {
    echo_location /foo; # echo_location发送子请求到指定的location
    echo_location /bar;
}
location /foo {
    echo foo;
}
location /bar {
    echo bar;
}
$ curl location/main
$ foo 03. bar

这里,main location就是发送2个子请求,分别到foo和bar。这就类似一种函数调用。 “子请求”方式的通信是在同一个虚拟主机内部进行的。所以 Nginx 核心在实现“子请求”的时候,就仅仅调用了若干个 C 函数,全然不涉及不论什么网络或者 UNIX 套接字(socket)通信。我们由此能够看出“子请求”的运行效率是极高的。

四、Nginx处理Http请求的过程

表面上看,当Nginx处理一个来自client的请求时,先依据请求头的host、ip和port来确定由哪个server处理,确定了server之后,再依据请求的uri找到相应的location。这个请求就由这个location处理。 实际Nginx将一个请求的处理划分为若干个不同阶段(phase)。这些阶段依照前后顺序依次运行。也就是说NGX_HTTP_POST_READ_PHASE在第一个,NGX_HTTP_LOG_PHASE在最后一个。

NGX_HTTP_POST_READ_PHASE,                 //0读取请求phase
NGX_HTTP_SERVER_REWRITE_PHASE,        //1这个阶段主要是处理全局的(server block)的rewrite
NGX_HTTP_FIND_CONFIG_PHASE,             //2这个阶段主要是通过uri来查找相应的location,然后依据loc_conf设置r的相应变量
NGX_HTTP_REWRITE_PHASE,                     //3这个主要处理location的rewrite
NGX_HTTP_POST_REWRITE_PHASE,             //4postrewrite,这个主要是进行一些校验以及收尾工作。以便于交给后面的模块。
NGX_HTTP_PREACCESS_PHASE,                 //5比方流控这样的类型的access就放在这个phase,也就是说它主要是进行一些比較粗粒度的access。

NGX_HTTP_ACCESS_PHASE,                         //6这个比方存取控制,权限验证就放在这个phase,一般来说处理动作是交给以下的模块做的.这个主要是做一些细粒度的access
NGX_HTTP_POST_ACCESS_PHASE,                 //7一般来说当上面的access模块得到access_code之后就会由这个模块依据access_code来进行操作
NGX_HTTP_TRY_FILES_PHASE,                 //8try_file模块,就是相应配置文件里的try_files指令。可接收多个路径作为參数。当前一个路径的资源无法找到,则自己主动查找下一个路径 
NGX_HTTP_CONTENT_PHASE,                     //9内容处理模块 
NGX_HTTP_LOG_PHASE                                 //10log模块

每一个阶段上能够注冊handler。处理请求就是执行每一个阶段上注冊的handler。Nginx模块提供的配置指令仅仅会一般仅仅会注冊并执行在当中的某一个处理阶段。 比方,set指令属于rewrite模块的,执行在rewrite阶段,deny和allow执行在access阶段。

五、常见问题

<一>、修复Nginx 502错误

1. upstream sent too big header while reading response header from upstream

Nginx的错误日志,发现如下错误

2015/03/19 10:46:40 [error] 6412#0: *16436265 upstream sent too big header while reading response header from upstream, client: 192.168.101.100, server: localhost, request: "GET /search_rst.html?word=%E7%88%B1%E6%82%A0 HTTP/1.1", upstream: "fastcgi://127.0.0.1:9000", host: ......

看来还是Nginx的配置问题。 在Nginx配置文件的的http段,加入下面的配置

proxy_buffer_size 128k;
proxy_buffers 32 32k;
proxy_busy_buffers_size 128k;

重启Nginx错误依旧。再在host配置的php段加入下面配置

fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;

重启Nginx就不再报错了。

2. recv() failed (104: Connection reset by peer) while reading response header from upstream

此问题在于,nginx的buffer太小了,timeout太小了。在nginx配置文件目录grep buffer * -irn、grep timeout * -irn,然后把所有的buffer加倍,改完之后的效果如下:

client_header_buffer_size   64k;
client_body_buffer_size     20m;
large_client_header_buffers 4 64k;
gzip_buffers                16 8k;
keepalive_timeout           240;

proxy_buffer_size           64k;
proxy_buffers               4 128k;
proxy_busy_buffers_size     256k;
proxy_connect_timeout       600s;
proxy_send_timeout          1200;
proxy_read_timeout          1200;

fastcgi_connect_timeout     600;
fastcgi_send_timeout        600;
fastcgi_read_timeout        600;
fastcgi_buffer_size         128k;
fastcgi_buffers             4 128k;
fastcgi_busy_buffers_size   256k;

同步修改配置php-fpm.conf

#php程序执行时间超过request_terminate_timeout的值,然后php-fpm进程立即退出,
此时nginx得不到php-fpm进程的正确结果,响应502
request_terminate_timeout = 0
#增加进程数
pm.max_requests = 500

<二> 重定向

1. nginx重定向规则详细介绍

nginx的rewrite相当于apache的rewriterule(大多数情况下可以把原有apache的rewrite规则加上引号就可以直接使用),它可以用在server,location 和IF条件判断块中,命令格式如下: rewrite 正则表达式 替换目标 flag标记 flag标记可以用以下几种格式: last – 基本上都用这个Flag。 break – 中止Rewirte,不在继续匹配 redirect – 返回临时重定向的HTTP状态302 permanent – 返回永久重定向的HTTP状态301 例如下面这段设定nginx将某个目录下面的文件重定向到另一个目录,$2对应第二个括号(.*)中对应的字符串:

location /download/ { 
    rewrite ^(/download/.*)/m/(.*)\..*$ $1/nginx-rewrite/$2.gz break; 
}

2. 重定向的IF条件判断

在server和location两种情况下可以使用nginx的IF条件判断,条件可以为以下几种:

A 正则表达式

如: 匹配判断

~      为区分大小写匹配;
!~     为区分大小写不匹配 
~*     为不区分大小写匹配;
!~     为不区分大小写不匹配 

例如下面设定nginx在用户使用ie的使用重定向到/nginx-ie目录下:

if ($http_user_agent ~ MSIE) { 
    rewrite ^(.*)$ /nginx-ie/$1 break; 
}
B 文件和目录判断
-f和!-f         判断是否存在文件 
-d和!-d        判断是否存在目录 
-e和!-e        判断是否存在文件或目录 
-x和!-x        判断文件是否可执行 

例如下面设定nginx在文件和目录不存在的时候重定向:

if (!-e $request_filename) { 
    proxy_pass http://127.0.0.1/; 
}
C return

返回http代码,例如设置nginx防盗链:

location ~* \.(gif|jpg|png|swf|flv)$ { 
    valid_referers none blocked http://www.jefflei.com/ http://www.leizhenfang.com/; 
    if ($invalid_referer) { 
        return 404; 
    } 
}
D set

设置nginx变量

E 301重定向方法

进行了301重定向,把www.jb51.net和jb51.net合并,并把之前的域名也一并合并. 有两种实现方法,第一种方法是判断nginx核心变量host(老版本是http_host):

server { 
    server_name www.jb51.net jb51.net ; 
    if ($host != 'www.jb51.net' ) { 
        rewrite ^/(.*)$ http://www.jb51.net/$1 permanent; 
    } 
} 

第二种方法:

server { 
    server_name jb51.net; 
    rewrite ^/(.*) http://www.jb51.net/$1 permanent; 
}

测试了第一种方法ok,这两种方法中, permanent是关键,详细说明见nginx重定向规则说明。

last – 基本上都用这个Flag。 
break – 中止Rewirte,不在继续匹配 
redirect – 返回临时重定向的HTTP状态302 
permanent – 返回永久重定向的HTTP状态301
好了,现在可以检查结果,这里可以看返回的HTTP头信息:http://www.seoconsultants.com/tools/headers.asp

第二种方法没有测试成功…

3. 重定向方法总结

A. 访问 A 站定向到 B 站
server { 
    server_name www.a.com ; 
    rewrite ^(.*) http://www.b.com$1 permanent; 
}
B. 不是访问 A 站的全部重定向到指定页面
server { 
    server_name www.a.com; 
    if ($host != ‘a.com' ) {
        rewrite ^/(.*)$ http://www.b.com/$1 permanent;
    }
}

如果写在第一个server段 使用IP访问时也将被重定向

C. 带WWW或者不带WWW之间的跳转
server { 
    server_name c.net 
    rewrite ^/(.*)$ http://www.c.net/$1 permanent; 
}