我们来到了一个第一个开源软件的专题,Nginx专题,Nginx在国内用的比较多,在高并发的场景下经常使用nginx,所以Nginx对我们来说比较重要,要好好分析分析。

1.1 nginx安装

老规矩,先讲nginx的安装,nginx的安装比较复杂,没有像redis那样简单,印象中的redis只需要make即可,这个nginx依赖的文件比较多,我们就不说怎么安装了,可以看下我之前写的docker,docker介绍也可以看下我的其他文章。

这个就是nginx安装参数说明,因为nginx是高度可配置,参数确实好多,可以看下这篇文章。nginx安装参数说明

前段时间因为环境的问题,需要多次装nginx,所以学了一些docker,学了之后发现docker真好用个,就再也不想用其他的了,我那篇文章就是用docker安装nginx为例,可以去看看安装过程,可以主要看看过程就可以了,docker部分可以忽略,也可以用我做好的镜像,链接:docker安装nginx

然后运行起来(拉取镜像的命令就不用多说了吧):

docker run -d -p 8080:80 1033177205/nginx:v1

我这里就直接用docker运行,反正都差不多的,我映射的端口是8080,所以访问一下网页:
Nginx专题(一、安装和使用)_nginx安装

用docker就是爽。

1.2 nginx快速入门

在linux中,需要使用命令行来控制nginx服务器启动和停止、重新配置文件、回滚日志文件、平滑升级等行为。默认情况下nginx被安装再目录/usr/local/nginx/中,二进制文件的路径为/usr/local/nginx/sbin/nginx,配置文件路径为/usr/local/nginx/conf/nginx.conf。当然在configure的时候,是可以指定安装的目录的,不过这里,我们就按默认的安装目录。

1.2.1 启动

  • 默认方式启动

    /usr/local/nginx/sbin/nginx
    

    默认启动方式会读取默认配置下的配置文件,路径:/usr/local/nginx/conf/nginx.conf,然后启动nginx

  • 指定配置文件方式启动

    /usr/local/nginx/sbin/nginx -c /tmp/nginx.conf
    

    -c 参数后就是我们指定的配置文件的路径,会去指定路径下读取,然后启动nginx

  • 重新启动

    /usr/local/nginx/sbin/nginx -s reload
    

    事实上,nginx会先检查新的配置项是否有误,如果全部正确就以“优雅”的方式关闭,再重新启动nginx来实现这个目的

1.2.2 停止

  • 快速关闭服务
/usr/local/nginx/sbin/nginx -s stop

用-s stop可以强制停止nginx服务,-s 参数其实告诉Nginx程序向正在运行的Nginx服务发送信号量,Nginx程序通过nginx.pid文件中获取到master进程的进程ID,再向运行中的master进程发送TERM信号来快速关闭Nginx服务。

  • “优雅”地停止服务

    /usr/local/nginx/sbin/nginx -s quit
    

    Nginx服务会正常的处理完当前所有请求再停止服务,该命令和快速关闭Nginx是有区别的。当快速停止服务时,worker进程与master进程在收到信号后立刻跳出循环,退出线程。而“优雅”地停止服务时,首先会关闭监听端口,停止接收新的连接,然后把当前正在处理的连接全部处理完,最后再退出进程。

1.2.3 日志

/usr/local/nginx/sbin/nginx -s reopen

使用-s reopen参数可以重新打开日志文件,这样可以先把当前日志文件改名或转移到其他目录中进行备份,再重新打开时就会生成新的日志文件。

1.2.4 平滑升级

当nginx服务升级到新的版本时,必须将旧的二进制文件Nginx替换掉,通常情况下这是需要重启服务的,但nginx支持不重启服务来完成新版本的平滑升级。

升级步骤如下:

  1. 通知正在运行的旧版本Nginx准备升级,通过向master进程发送USR2信号可达到目的。例如:

    kill  -s SIGUSR@ <nginx master pid>
    

    这是,运行中的nginx会将pid文件重命名,如将/usr/local/nginx/logs/nginx.pid重命名为/usr/local/nginx/logs/nginx.pid.oldbin,这样才有可能启动成功。

  2. 启动新版本nginx

    可以使用上面的任一方法启动,这式通过ps命令可以发现新旧版本的nginx在同时运行。

  3. 通过kill命令向旧版本的master进程发送SIGQUIT信号,以优雅的方式关闭旧版本的Nginx。随后将只有新版本nginx服务运行,此时平滑升级完毕。

1.3 nginx配置文件

1.3.1 nginx介绍

在正式使用nginx的情况下,nginx都是使用一个master进程来管理多个worker进程,一般情况下,worker进程的数量与服务器上的CPU核心数相等。每一个worker进程都是繁忙的,它们在真正地提供互联网服务,master进程则很“清闲”,只负责监控管理worker进程。

master进程可以是唯一的,它负责管理工作,为管理员提供命令行服务,包括启动服务、停止服务、重载服务等。多个worker进程处理互联网请求不但可以提高服务的健壮性(一个进程出错后,可以使用其他进程),最重要,这样可以充分利用现在常见的SMP多核架构,从而实现微观上真正的多核并发处理。

1.3.2 配置文件通用语法

nginx的配置文件其实是一个普通的文本文件。下面来看一下nginx配置文件:

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

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

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   html;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}

        location /test {
            fastcgi_pass 127.0.0.1:8001;
            fastcgi_index test;
            include fastcgi.conf;
        }

        location /group1/M00 {
            root /root/storage/data;
            ngx_fastdfs_module;
        }

    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

以#符号开头的是注释。

1.3.3 块配置项

块配置项由一个块配置项名和一对大括号组成。具体示例如下:

events {
    ...
}

http {
	upstream backend {
		server 127.0.0.1:8080;
	}
	
	gzip on;
	server { 
		listen       80;
        server_name  localhost;
	}
}

其中events和http都是块配置项,块配置项之后是否要添加参数,取决于解析这个块配置项的模块。块配置项会用大括号把一系列的所属的配置项全包含进来,表示大括号内的配置项同时生效。

块配置项可以嵌套,比如:server块就是嵌套在http块中,当内外层冲突的时候,大部分都是以内层的为准,有点像变量的作用域。

1.3.4 配置项的语法格式

最基本的配置项语法格式如下:

配置项名  配置项1  配置项2  ...

下面解释一下配置项的构成部分。

在行首的是配置项名,这些配置项名必须是Nginx的某一个模块想要处理的,否则nginx会认为配置文件出现了非法的配置名。配置项名输入结束后,将以空格作为分隔符。

其次是配置项值,它可以是数字或者字符串(当初也包括正则表达式)。针对一个配置项,即可以只有一个值,也可以包含多个值,配置项值之间仍然由空格符来分割。当然,一个配置项对应的值究竟有多少个,取决于解析这个配置项的模块。

1.3.5 nginx服务基本配置

  • error日志设置

    语法:error_log path/file level;

    默认:error_log logs/error.log error;

    nginx日志是定位nginx问题的最佳工具,我们可以根据自己的需要妥善设置error日志的路径和级别。

    /path/file是日志文件输出的路径,可以定义成/dev/null这样就不会输出任何日志了,这也是关闭nginx的日志的唯一手段

    level 是日志的输出的级别,取值为:debug、info、notice、warn、error、crit、alert、emerg,从左到右等级依次增大。

    注意:如果日志级别设置为debug,必须在configure时加入–with-debug配置项

  • 嵌入其他配置文件

    语法:include /path/file

    include配置项可以将其他配置文件嵌入到当前的nginx.conf文件中。

  • pid文件的路径

    语法:pid /path/file

    默认:pid logs/nginx.pid;

    保存master进程ID的pid文件存放路径。默认与configure执行时的参数–pid-path所指定的路径是相同的,也可以随时修改。

  • nginx worker进程运行的用户及用户组

    语法:user username [groupname]

    默认:user nobody nobody

    user用于设置master进程启动后,fork出的worker进程运行在哪个用户和用户组下。当按照“user username;”设置时,用户组与用户名相同。

    若用户在configure命令执行时使用了参数–user=username,–group=groupname,此时nginx.conf将使用参数中指定的用户名和用户组

  • nginx worker进程个数

    语法:worker_processes number

    默认:worker_processes 1;

    在master/worker运行方式下,定义worker进程的个数。

    worker进程的数量会直接影响性能。那么,用户配置多少个worker进程才好?其实际上与业务需求有关,

  • 绑定Nginx worker进程到指定CPU内核

    语法:worker_cpu_affinity cpumask [cpumask …]

    为什么要绑定worker进程到指定CPU内核呢?假设每一个worker进程都是非常忙的,如果多个worker进程都在抢同一个CPU,那么这样就会出现同步问题。反之,如果每个worker进程独享一个CPU核,就在内核的调度策略上实现了完全并发。

    例如:如果由4颗CPU内核,就可以进行如下配置:

    worker_processes 1;

    worker_cpu_affinity 1000 0100 0010 0001;

    注意:worker_cpu_affinity配置仅对linux操作系统有效。linux操作系统使用sched_setaffinity()系统调用实现这个功能。

  • nginx worker进程优先级设置

    语法:worker_priority nice;

    默认:worker_priority 0;

  • 选择事件模型

    语法:use [kqueue | rtsig | epoll | /dev/poll | select | epoll | eventport]

    默认:nginx会自动选择使用最合适的事件模型

    event控制块一般是描述连接处理的。epoll是性能最高的一种。

  • 每个worker的最大连接数

    语法:worker_connection number;

    定义每个worker进程可以同时处理的最大连接数。

1.4 nginx配置静态web服务器

接下来的目标是,用nginx配置成一个静态web服务器,有一些问题需要了解,定义它的处理哪些URL以及如何处理这些URL上的资源的HTTP请求。配置定义一组控制对特定域或IP地址的请求的处理的虚拟服务器。

HTTP的配置项必须全部在http{}块之内,通过分析http块之内的配置项,就可以了解怎么配置静态web服务器了。

1.4.1 虚拟服务器

nginx配置文件必须至少包含一个服务器指令来定义虚拟服务器。当nginx处理请求时,它首先选择提供请求的虚拟服务器。

虚拟服务器由http上下文中的服务器指令定义,例如:

http {
	server {
		# server configure
	}
}

可以将多个server指令添加到http上下文中以定义多个虚拟服务器。

一个server块相当于一个虚拟服务器,server配置块通常包括一个listen指令,用于指定服务器侦听请求的IP地址和端口(或Unix域套接字和路径)。IPv4和IPv6地址均被接受;

  1. 监听端口

    语法:listen address:port

    默认:listen 80

    默认块:server

    listen参数决定了nginx服务器如何监听端口,在listen后可以只加IP,端口后者主机名非常灵活。

    例如:

    listen 127.0.0.1; #默认端口80

    如果使用ipv6的地址,可以这样写

    listen [::]:8000

    listen 还有其他参数,可以自行查看看

  2. 主机名称

    语法:server_name name […]

    默认:server_name “”;

    默认块:server

    server_name 后可以跟多个主机名称,如server_name www.testweb.com、download.testweb.com

    在开始处理一个http请求时,Nginx会取出header头中的HOST,与每个server中的server_name进行匹配,以此决定到底由哪一个server块来处理这个请求。有可能一个HOST与多个server块中的sever_name都匹配,这时就会根据匹配优先级来选择实际处理的server块,(server可以是ip地址,这种匹配策略可以学习)。

    server_name与host的匹配优先级如下:

    1)首先选择所有字符串完全匹配的server_name,如www.testweb.com

    2)其次选择通配符在前面的server_name,如*.testweb.com

    3)再次选择通配符在后面的server_name,如www.testweb.*

    4)最后选择使用正则表达式才匹配的server_name,如~^.testweb.com$

    如果host与所有的server_name都不匹配,这时就将按照下面的顺序选择处理的server块:

    1)优先选择在listen配置项后加入[default | default_server ]的server块。

    2)找到匹配listen端口的第一个server块

    如果server_name后面跟着空字符串(如server_name “”;)那么表示匹配没有host这个http头部请求。

    注意:nginx正是使用server_name配置项针对特定的host域名的请求提供不同的服务,依次实现虚拟主机。

  3. location

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

    配置块:server

    location会尝试根据用户请求中的URI来匹配上面的/uri表达式,如果可以匹配,就选择location{}块中的配置来处理用户请求。当然匹配规则比较多样,下面介绍location匹配规则:

    1)= 表示把URI作为字符串,以便与参数中的uri做完全匹配。例如:

    location = / {

    ​ # 只有当用户请求是/时,才会使用该location下的配置

    ​ …

    }

    2)~ 表示匹配URI时是字母大小写敏感

    3)~*表示匹配URI时忽略字母大小写问题

    4)^~表示匹配URI时只需要其前半部分与uri参数匹配即可。例如:

    location ^~ /images/ {

    ​ # 以/images/开始的请求都会匹配上

    ​ …

    }

    5)@表示仅用于nginx服务内部请求之间的重定向,带有@的location不直接处理用户请求。

    当然,在uri参数里可以用正则表达式。

    如果都不能匹配的话,最后有一个/作为参数,任意一个http请求都可以匹配,

  4. 以root方式设置资源路径

    语法:root path

    默认:root html;

    配置块:http、server、location、if

    例子:

    location / {
    root html;
    index index.html index.htm;
    }

    定义资源文件相对于HTTP请求的根目录

  5. 访问首页

    语法:index file …;

    默认:index index.html;

    配置块:http、server、location

    有时,访问站点的URI是/,这时一般返回网页的首页

  6. 根据HTTP返回码重定向页面

    语法:error_page code [code …] [ = | =answer-code ] uri @named_location

    配置块:http、server、location、if

    例:

    error_page 500 502 503 504 /50x.html;

    把50x的错误全部重定向到50x.html页面

  7. keepalive超时时间

    语法: keepalive_timeout time;(默认单位:秒)

    默认: keepalive_timeout 75;

    keepalive_timeout 65;

  8. sendfile系统调用

    语法:sendfile on | off;

    默认:sendfile off;

    配置块:http、server、location

    可以启动linux上的sendfile系统调用发送文件,它减少了内核态与用户态之间的两次内存复制,这样就会从磁盘中读取文件后直接在内核态发送到网卡设备,提高文件发送效率。

1.4.2 静态网站服务器

经过我们上面的分析,nginx中默认的配置文件就已经支持静态网站服务器了,这里就不粘贴处配置文件的,可以直接访问80端口就可以访问到nginx服务器的网页了。

1.4.3 静态服务器支持获取图片

如果需要支持图片的话,我们需要在配置文件中添加一个新的资源项:

location /images/ {
	root /usr/local/nginx/html;
}

这个新添加的资源项就是图片的资源项,把图片资源放到这个目录下:
Nginx专题(一、安装和使用)_nginx使用_02
如果有几个location项,nginx将会选择具有最长前缀来匹配location快,所以请求图片的时候,需要加/images/这样才能匹配到,所以请求的链接是:
http://192.168.1.177:8080/images/buyi.jpg
这是局域网的连接,结果:
Nginx专题(一、安装和使用)_linux_03
是不是比较简单。

1.5 nginx代理服务器

代理通常用于在多个服务器之间分配负载,无缝地显示来自不同网站的内容,或者通过HTTP以为的协议将请求传递给应用服务器。

1.5.1 设置简单的代理服务器

代理服务器比较简单,也是修改配置文件:

 proxy_pass http://192.168.1.178:8080/;

加了这个,把之前的那个指定路径屏蔽掉即可,看看结果:
Nginx专题(一、安装和使用)_nginx_04
我在另一台虚拟机上修改了一下文件内容,所以看到了192.168.1.178的内容,所以代理成功。

完整的配置文件:

events {
    worker_connections  1024;
}


http {
        server {
                listen       80;

                location / {
#                       root   /usr/local/nginx/html/;
                        proxy_pass http://192.168.1.178:8080/;
                }

                location /images/ {
                        root /usr/local/nginx/html;
                }
        }

}

1.5.2 反向代理

反向代理方式是指用代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络中的上游服务器,并将从上游服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外的表现就是一个web服务器。

与squid等其他反向代理服务器相比、nginx并不会立刻转发到上游服务器,而是先把用户的请求(包括http包体)完整的接收到nginx所在服务器的硬盘或者内存中,然后再向上游服务器发起连接,把缓存的客户端请求转发到上游服务器。

nginx为什么这样设计呢?狠明显,缺点是延长了一个请求的处理时间,并增加了用于缓存请求内容的内存和磁盘空间,而优点则是降低了上游服务器的负载,尽量把压力放在nginx服务器上。

反向代理和正向代理配置差不多,这里就不写了。

1.5.3 正向代理和反向代理区别

正向地理,架设在客户机和目标主机之间,客户端必须指定代理服务器,并将本来要直接发送到web服务器上的http请求发送到代理服务器上。正向代理是客户端知道服务器在哪里,然后通过代理去访问客户端不能直接访问的目标服务器。

反向代理服务器架设在服务端,通过缓冲经常被请求的页面来缓解服务器的工作量,将客户机请求转发给内部网络上的目标服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器与目标主机一起对外表现为一个服务器。

1.6 nginx负载均衡

作为代理服务器,一般都需要向上游服务器的集群转发请求,这里的负载均衡是指选择一种策略,尽量把请求平均到每一台上游服务器上。

1.6.1 负载均衡基本配置

  1. upstream块

    语法:upstream name […]

    配置块:http

    upstream块定义了一个上游服务器的集群,便于反向代理中的proxy_pass使用。例如:

    upstream backend {
    	server backend1.example.com;
    	server backend2.example.com;
    }
    
    server {
    	location / {
    	proxy_pass http://backend;
    	}	
    }
    
  2. server

    语法:server name [parameters]

    配置块:upstream

    server配置项指定了一台服务器的名字,这个名字可以是域名,IP地址端口,unix句柄等,后面有一些参数:

    weight=number:设置相这台上游服务器转发的权重,默认为1.

    down:表示所在的上游服务器永久下线,只在使用ip_hash配置项时才有用

  3. ip_hash

    语法:ip_hash

    配置块:upstream

    在有些场景下,我们可能希望来自某一个用户的请求始终落到固定的一台上游服务器上。ip_hash就是用来解决这个问题的,它首先根据客户的ip地址计算出一个key,将key按照upstream集群里的上游服务器数量进行取模,然后以取模后的结果把请求转发到相应的上游服务器中。这样就确保了同一个客户端的请求只会转发到指定的上有服务器中。

    upstream backend {
    	ip_hash;
    	server backend1.example.com;
    	server backend2.example.com;
    }
    

1.6.2 负载均衡的策略

nginx目前支持自带3种负载均衡策略,还有2种常用的第三方策略

  1. RR(默认)

    每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动删除。

    具体配置就是上块的upstream配置,因为是默认的,所有不需要更多的配置

  2. 权重

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

    upstream test {
    	server localhost:8080 weight=9;
    	server localhost:8081 weight=1;
    }
    

    那么 10 次一般只会有 1 次会访问到 8081,而有 9 次会访问到 8080

  3. ip_hash

    使用客户端的ip地址做为键值,然后去查找上游服务器。

    例子看上一节ip_hash

  4. fair(第三方)

    按后端服务器的相应时间来分配请求,相应时间短的优先分配

    upstream backend {
    fair;
    server localhost:8080;
    server localhost:8081;
    }  
    
  5. url_hash(第三方)

    按访问 url 的 hash 结果来分配请求,使每个 url 定向到同一个后端服务器,后端服务器为缓
    存时比较有效。 在 upstream 中加入 hash 语句, server 语句中不能写入 weight 等其他的参
    数, hash_method 是使用的 hash 算法

    upstream backend {
    hash $request_uri;
    hash_method crc32;
    server localhost:8080;  
    server localhost:8081;
    }
    

    以上 5 种负载均衡各自适用不同情况下使用,所以可以根据实际情况选择使用哪种策
    略模式,不过 fair 和 url_hash 需要安装第三方模块才能使用。