首页 > 代码库 > nginx那些事儿

nginx那些事儿

本文为我学习nginx时的笔记与心得,如有错误或者不当的地方,还望不吝指出

1 基本概念

1.1 正向代理和反向代理

正向代理

一般来说,我们说的代理技术就是指正向代理技术。
使用正向代理技术一般用来访问我们无法访问的服务器。正向代理服务器介于用户和目标服务器之间,比如用户A想去访问目标服务器B,但是各种原因无法直接访问,这时就可以通过使用正向代理服务器C,用户A向代理服务器C发送一个请求并指定目标服务器B,代理服务器会将请求转发给B并将获取的结果返回给用户A。
使用正向代理,往往需要客户去进行相关的配置。典型的例子是,为防火墙内的局域网用户提供访问访问Internet的途径,有些公司内部的局域网本身是无法访问互联网的,但是可以通过设置代理实现访问。还有另一个典型的例子就是“翻墙”。
反向代理
与正向代理技术相对的就是反向代理技术。
使用反向代理时,客户端的用户不需要任何设置,而且用户始终认为他访问的代理服务器B就是目标服务器,用户将请求发送给反向代理服务器,由反向代理服务器判断应该将请求转交给哪台目标服务器,然后将结果返回给用户。

对比
通过对比,我们可以有一个更清晰的认识。
a.谁在“墙”里边
对使用正向代理的环境来说,用户位于墙里边,墙限制了用户与外界沟通的能力,这时,我们使用正向代理技术来赋予用户访问外界环境的能力。
对使用反向代理的环境来说,目标服务器位于墙里边 ,这面墙有个门,但只向反向代理服务器开放。本来任何用户都无法访问目标服务器,但反向代理服务器的出现使之变成了可能,用户直接访问反向代理服务器即可。

b.用户访问的是谁
对正向代理来说,用户访问的就是目标服务器。比如使用代理的局域网用户访问IPA时,他实际上是就是向IPA发起的请求,只是这个请求实际发给了正向代理服务器,然后由代理服务器做了个转发,用户需要告诉代理服务器请求的内容以及请求的目标。

对于反向代理来说,用户访问的是反向代理服务器。先拿nginx举个例子,当我们使用nginx做负载均衡时,一台nginx后边是多台目标服务器,比如nginx的地址为IPA,目标服务器的地址为IPB1,IPB2,IPB3。用户实际请求的是IPA,然后由反向代理服务器来决定将请求转发给哪一台目标服务器.

c.用户是否需要配置
对于正向代理来说,用户需要进行相应的配置。
对于反向代理来说,用户不需要配置,而是需要对反向代理服务器配置与之关联的目标服务器。

1.2 nginx

是什么?
nginx是一个高性能的web和反向代理服务器,同时也支持IMAP/POP3/SMTP 代理。
做什么?
nginx主要就是用来做反向代理及负载均衡。

2 nginx

2.1 安装

一般情况,使用如下命令直接安装即可

sudo apt-get install nginx

安装之后启动nginx进行测试,使用下面两种命令都可以启动

 sudo /etc/init.d/nginx start

或者

sudo service nginx start

启动后在浏览器地址栏输入:http://localhost 进行确认,如果进入nginx的欢迎界面,说明nginx已安装并且启动成功

2.2 命令

命令 功能
sudo service nginx start 启动nginx
sudo /etc/init.d/nginx start 启动nginx
sudo service nginx stop 停止nginx
sudo /etc/init.d/nginx stop 停止nginx
sudo service nginx restart 重启nginx
sudo /etc/init.d/nginx restart 重启nginx

3 nginx进程及运行机制

3.1 master & worker

大多数情况下nginx是以多进程的方式执行的(好像也支持单进程模式:nginx的单进程模式)
nginx启动后,会在后台产生一个master进程和多个worker进程。master和worker是主进程与子进程的关系。
master进程类似于监工,而worker进程类似于工人。master进程主要负责管理worker进程。
master进程的工作包含:
1.接收外界信号,并向各个worker进程发送信号。
2.监控worker进程(工作进程)的运行状态,当worker进程异常退出后,会自动重新启动新的worker进程。
worker进程的工作为:
基本的网络事件会由worker进程来执行,多个worker一同竞争客户端请求,而这个竞争是公平的,每个worker进程都有同等的机会获取到客户端请求。一个请求只能被一个worker进程处理,一个worker也不能处理其他进程处理的请求。

3.2 具体流程

首先了解一下linux的fork函数:
fork的作用是创建子进程,fork创建的子进程会基本复制主进程的全部信息,也就是说子进程会继承主进程的所有属性。
对于nginx来说,master进程会首先建立好需要的socket,然后fork生成子进程worker(worker进程的个数一般与机器cpu的核数一致),此时每个worker进程都继承了master进程的属性,包括socket,当然,这个socket不是同一个,每个进程都有一个自己的socket,只不过都会监听同一个IP和端口。当有一个连接请求发生时,由于每个worker进程在获取请求上都是公平的,所以每个worker进程都会收到通知。为了确保一个请求只被一个进程处理,所以nginx中使用了互斥锁accept_mutex,所有的worker进程首先去争夺互斥锁,只有抢到互斥锁的进程才能去处理该请求。一个worker进程处理请求的过程包括:获取连接,读取请求,解析请求,处理请求,断开连接。

3.3 同步&异步 阻塞&非阻塞

通过3.2我们知道一个worker进程只能处理一个请求,而worker进程的数量又是有限的,那nginx何来处理高并发一说呢?
首先我们先了解一下 同步&异步 阻塞&非阻塞
偶然在网上看到一个例子,很生动,给大家分享一下:

故事背景:隔壁老王煮开水
出场人物:老王,普通水壶,鸣笛水壶(水烧开后会响)
第一天:
老王把普通水壶放在火上,盯着水壶等着水开。(同步阻塞
老王觉得这样太浪费时间。
第二天:
老王把普通水壶放在火上,去客厅看电视,时不时去看一下水是否开了(同步非阻塞
老王觉得这样太麻烦。
第三天:
老王把响水壶放火上,盯着水壶等着水开鸣笛(异步阻塞
老王觉得自己有点傻
第四天:
老王把响水壶放火上,去客厅看电视,等水壶响了再去拿壶(异步非阻塞)

在这个故事中,
同步&异步是针对水壶来说的,普通水壶是同步,响水壶是异步。所谓同步,就是需要调用者主动去等待调用的结果,异步是调用发生后,调用者不会立即得到调用结果,而是被调用者在处理完后将结果通知给他。
阻塞&非阻塞是针对老王来说的。傻等的老王是阻塞,看电视的老王是非阻塞。阻塞&非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。所以阻塞的老王只能等水开,而非阻塞的老王在水开前可以看电视。

而nginx使用的就是异步非阻塞,比如nginx会在一个线程中处理所有的IOq请求(IO多路复用技术),当一个IO操作A开始时,nginx并不会等待这个IO操作完成,而是会继续处理一下IO操作B,直到获得这个IO操作A已完成的通知再进行下一步处理。所以nginx可以处理高并发的请求。

4 nginx配置

使用nginx -t命令可以查看nginx配置文件的存放位置。
首先看一下默认配置:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    # server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    ##
    # Gzip Settings
    ##

    gzip on;
    gzip_disable "msie6";

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}


#mail {
#   # See sample authentication script at:
#   # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
# 
#   # auth_http localhost/auth.php;
#   # pop3_capabilities "TOP" "USER";
#   # imap_capabilities "IMAP4rev1" "UIDPLUS";
# 
#   server {
#       listen     localhost:110;
#       protocol   pop3;
#       proxy      on;
#   }
# 
#   server {
#       listen     localhost:143;
#       protocol   imap;
#       proxy      on;
#   }
#}

首先需要了解的就是nginx是基于模块化的配置,配置的结构图如下:
技术分享

4.1 高级配置

其中,文件顶部的为高级配置:

user www-data;
worker_processes auto;
pid /run/nginx.pid;

对于user和pid,我们应该保持默认设置。
worker_processes表示worker进程的数量,设置为auto时将与可用的CPU内核数保持一致。
除此之外,下面的配置用来设置worker进程打开文件数的限制:

worker_rlimit_nofile 100000;

如果没有设置的话,该值将与操作系统的限制值一样,设大一点可以避免“too many open files”的问题

4.2 events 模块

events模块中包含nginx中所有处理连接的设置
默认配置为:

events {
    worker_connections 768;
    # multi_accept on;
}

worker_connections:表示一个worker进程可以同时打开的最大连接数。
multi_accept :告诉nginx收到一个新连接通知后接受尽可能多的连接。
初次之外,还提供了use属性

use epoll;

设置用于复用客户端线程的轮询方法。如果你使用Linux 2.6+,你应该使用epoll。如果你使用*BSD,你应该使用kqueue。

4.3 http模块

HTTP模块控制着nginx http处理的所有核心特性.
对比上边的配置文件

sendfile

服务器响应一个http请求的步骤为:
1.把磁盘文件读入内核缓冲区
2.从内核缓冲区读到内存
3.处理(静态资源不需处理)
4.发送到网卡的内核缓冲区(发送缓存)
5.网卡发送数据
而使用linux的sendfile()可以跳过2,3步,实现数据在磁盘和socket之间的互相拷贝。sendfile属性就是让sendfile()函数发挥作用

tcp_nopush

该参数表示将在一个数据包中发送所有头文件,而不是一个一个发送。

tcp_nodelay

告诉nginx不要缓存数据,而是一段一段的发送

keepalive_timeout

表示与客户端链接的超时时间,超过设置的时间后将会断开连接

types_hash_max_size

types_hash_bucket_size 设置了每个散列块占用的内存大小,types_hash_max_size影响散列表的冲突率。types_hash_max_size越大,就会消耗更多的内存,但散列key的冲突率会降低,检索速度就更快。types_hash_max_size越小,消耗的内存就越小,但散列key的冲突率可能上升

server_tokens

用于隐藏页面中的nginx版本号

server_names_hash_bucket_size

该属性可以解决虚拟主机多域名的问题,当配置多个虚拟主机时,必须配置该属性,并适当太大对应的值,以32的倍数为宜。

server_name_in_redirect

nginx的重定向规则。
当 URL 指向一个目录并且在最后没有包含“/”时,Nginx 内部会自动的做一个 301 重定向,这时会有两种情况:
1、server_name_in_redirect on(默认),URL 重定向为: server_name 中的第一个域名 + 目录名 + /;
2、server_name_in_redirect off,URL 重定向为: 原 URL 中的域名 + 目录名 + /。

client_header_timeout

设置请求头的超时时间

client_body_timeout

设置请求体的超时时间。


以上为nginx的http模块的基础设置。

access_log

设置nginx是否将存储访问日志。关闭这个选项可以让读取磁盘IO操作更快(aka,YOLO)。

error_log

设置为只记录严重的错误。

reset_timeout_connection

设置为on时,将会关闭不响应的客户端链接

send_timeout

客户端的响应超时时间。


以上为nginx关于log的配置。

gzip

告诉nginx采用gzip压缩的形式发送数据。这将会减少我们发送的数据量。

gzip_disable

为指定的客户端禁用gzip功能。我们设置成IE6或者更低版本以使我们的方案能够广泛兼容。

gzip_static

告诉nginx在压缩资源之前,先查找是否有预先gzip处理过的资源。这要求你预先压缩你的文件(在这个例子中被注释掉了),从而允许你使用最高压缩比,这样nginx就不用再压缩这些文件了(想要更详尽的gzip_static的信息,请点击这里)。

gzip_proxied

允许或者禁止压缩基于请求和响应的响应流。我们设置为any,意味着将会压缩所有的请求。

gzip_min_length

设置对数据启用压缩的最少字节数。如果一个请求小于1000字节,我们最好不要压缩它,因为压缩这些小的数据会降低处理此请求的所有进程的速度。

gzip_comp_level

设置数据的压缩等级。这个等级可以是1-9之间的任意数值,9是最慢但是压缩比最大的。我们设置为4,这是一个比较折中的设置。

gzip_type

设置需要压缩的数据格式。上面例子中已经有一些了,你也可以再添加更多的格式。

4.4 负载配置

在http模块中添加如下代码:

upstream proxy_set{
         server 10.0.2.16:80 weight=1;
         server 10.0.2.17:80 weight=1;
        }
server{
   listen 80;
   server_name localhost 127.0.0.1;

location / {
  proxy_pass http://proxy_set
  proxy_set_header Host $host;
  proxy_set_header X-Real_IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  #client_max_body_size    10m;
}
}

首先,在http节点下新增upstream节点,名称为proxy_set,这个名称可以自己指定,并在节点内配置后端服务器的IP:port以及权重,权重可以省略。
然后配置server节点,listen为监听的端口,server_name为服务器地址,多个地址以空格隔开,我这里设置了两个个,分别为localhost和127.0.0.1,.然后配置server的location节点,proxy_pass的至为http://+upstream的名称,其他配置保持一致(client_max_body_size为设置文件上传大小的限制,nginx默认值为1M)。
配置完以后重启nginx服务。
在浏览器地址栏输入localhost:80/项目名或者127.0.0.1:80/项目名即可访问。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    nginx那些事儿