Nginx学习笔记

作者: 无知者云 | 来源:发表于2018-12-20 11:03 被阅读5次

    首先提醒一下,国内绝大多数程序员都把Nginx的读音搞错了(甚至包括很多国外的程序员),Nginx官方推荐的正确的读音应该是:Engine-X,而不是En-jingks.

    Nginx分为主进程(master process)和工作进程(worker process),每个进程中只有一个线程(也可以配置线程池),通过IO多路复用(底层使用epoll/kqueue等技术)和事件循环达到高并发(这点跟Node.js比较相似)。主进程负责总体协调工作,比如在配置文件更新后重新应用配置、协调哪个worker process应该退役等等。工作进程的个数一般设置为CPU的个数。

    还有个Nginx Plus,它是Nginx背后的商业公司,提供比Nginx开源版更多功能的Nginx软件,以及一些有关Nginx的技术支持服务,Nginx和Nginx Plus的对比在这里

    安装Nginx

    通过Yum安装

    首先安装epel-release源:

    sudo yum install epel-release
    

    然后安装Nginx:

    sudo yum install nginx
    

    最后启动Nginx:

    sudo systemctl start nginx
    

    查看安装之后的文件路径:

     rpm -ql nginx
    

    在笔者的CentOS/7虚拟机上,Nginx的配置文件位于/etc/nginx/nginx.conf,默认静态资源目录为/usr/share/nginx/html/

    对于不同的操作系统或安装方式,在安装完Nginx之后,配置文件放置的位置可能不一样,因此如果一个地方找不到,不妨试试其他目录。以下是几个比较常见的地方:

    • /etc/nginx/nginx.conf
    • /usr/local/nginx/conf/nginx.conf
    • /usr/local/etc/nginx/nginx.conf

    默认静态资源文件目录:

    • /usr/share/nginx/html

    通过Docker安装

    Docker官网提供了Nginx镜像,运行Docker:

    $ sudo docker run -p 8081:80  -d nginx
    

    如果要对外提供静态资源文件:

    $ sudo docker run -p 8081:80 -v /some/content:/usr/share/nginx/html:ro -d nginx
    

    此时,将本机静态资源文件夹/some/content映射成了nginx的默认静态资源文件夹,然后将Docker的80端口映射到了宿主机的8081端口,此时访问http://localhost:8081/便可以访问宿主机中/some/content/下的静态文件了。

    当然,要指定静态资源文件,更好的方式是通过Dockerfile将资源拷贝到Docker容器中,通常前端项目便可以通过这种方式构建Docker镜像:

    FROM nginx
    COPY /some/content /usr/share/nginx/html
    

    另外,如果要定制化Nginx的配置,也可以通过Dockerfile:

    FROM nginx
    COPY nginx.conf /etc/nginx/nginx.conf
    

    以上Dockerfile将宿主机的nginx.conf文件拷贝到Docker容器中。

    更多安装方式请参考Nginx官网

    Nginx命令行

    可以通过发信号的方式对Nginx进行控制,发信号有两种方式,一种是通过nginx命令,一种是通过kill,详细请参考官网,这里列出通过该nginx控制Nginx的功能,命令格式:

    nginx -s <SIGNAL>
    

    其中,SIGNAL取值和功能如下:

    • quit – 安全地关闭
    • reload – 重新加载配置文件
    • reopen – 重新打开日志文件
    • stop – 直接关闭

    配置文件

    Nginx的主配置文件nginx.conf通过上下文context组织结构,context中可以包含配置项directive,而directive又可以反过来包含context

    Nginx包含以下顶层的context

    • events - 通用的与连接相关的配置
    • http - http流量相关配置
    • mail - 邮件流量相关配置
    • stream - TCP/UDP流量相关配置

    位于顶层context之外的directive也被称为在main context中。

    Virtual Server(Server Block)

    在每个流量配置context中(http,mailstream),都可以通过server配置多个虚拟服务器(Virtual Server),server下的配置基于context的不同而不同。比如对于httpserver下可以配置多个location来处理不同的URL,而对于mailstreamserver中则可以配置端口或者Socket。

    示例配置文件

    user nobody; # a directive in the 'main' context
    
    events {
        # configuration of connection processing
    }
    
    http {
        # Configuration specific to HTTP and affecting all virtual servers  
    
        server {
            # configuration of HTTP virtual server 1       
            location /one {
                # configuration for processing URIs starting with '/one'
            }
            location /two {
                # configuration for processing URIs starting with '/two'
            }
        } 
        
        server {
            # configuration of HTTP virtual server 2
        }
    }
    
    stream {
        # Configuration specific to TCP/UDP and affecting all virtual servers
        server {
            # configuration of TCP virtual server 1 
        }
    }
    

    配置继承

    有些directive可以位于多种context中, 此时便形成了一种继承关系,即子context将继承父context中的directive配置,当然子context也可以通过显式配置的方式覆盖继承自父context的配置。

    另外,主配置文件nginx.conf可以通过include来包含其他配置文件,一个常用的实践是nginx.conf只负责配置nginx本身,而将server相关的配置放到各自的配置文件中,然后在nginx.conf文件中include这些文件。

    虚拟服务器选择

    Nginx处理HTTP请求原理:先根据server中的listenserver_name等判断该请求应该由哪个server来处理,然后通过匹配server下的location来决定应该由哪个location来处理,服务器配置示例如下:

    server {
        listen      80;
        server_name example.org www.example.org;
        ...
    }
    

    需要注意一下几点:

    • 如果没有listen,那么Nginx默认监听所有网卡的80端口(以root启动nginx),或者默认监听所有网卡的8000端口(非root启动)
    • server_name的前后可以使用通配符*来匹配任意字符,但是*不能出现在域名中间
    • 可以使用~server_name进行正则匹配。

    Nginx通过将server_name与请求中的Host头信息进行匹配,通过以下顺序,第一个满足匹配条件的胜出:

    • 精确匹配
    • *开头的最长server_name
    • *结尾的最长server_name
    • 第一个匹配的正则表达式
    • 如果以上都没有匹配到,则匹配标有default_server的server
    • 如果以上都没有匹配到,则选择第一个server

    Location匹配

    在Nginx决定了由哪个server处理请求之后,将进一步将请求URL与该server中的各个location匹配,以最终决定由哪个location来处理该请求。

    location分为两

    • 前缀字符串,示例:
    location /some/path/ {
        ...
    }
    
    • 正则表达式,示例:
    location ~ \.html? {
        ...
    }
    

    匹配规则如下:

    • 先将请求URL与所有location进行前缀匹配
    • 如果某个前缀命中的的location=修饰符,则选择之并停止匹配
    • 如果最长前缀的location^~修饰符,则停止匹配,并选择之
    • 保存最长的前缀匹配location
    • 进行正则表达式匹配,根据location出现的顺序进行匹配
    • 找到第一个匹配成功的正则表达式location,选择之,停止匹配(请注意,只要有正则表达式匹配上,那么先前保存的最长前缀匹配将失效)
    • 如果没有匹配上的正则表达式,那么匹配先前保存的最长前缀匹配

    神奇的斜杠/

    todo

    服务静态文件

    先将静态资源拷贝到/data/www目录下,然后在http中配置:

    server {
        location / {
            root /data/www;
        }
    }
    

    此时访问http://localhost/,如果/data/www下有index.html文件,那么将直接返回index.html文件内容。

    另外,还可以通过index直接指定默认的index文件:

    location / {
        root /data/www;
        index index.html index.php;
    }
    

    当前很多单页面应用采用了HTML5的History API,在使用Nginx时,由于所有的URL都将被Nginx处理,但是单页面有只有一个页面,也即所有URL都将路由到同一个页面,比如Index.html,再有浏览器根据URL做客户端路由。此时可以通过try_files对Nginx进行配置:

    server {
      ...
      location / {
        try_files $uri /index.html;
      }
    }
    

    在上例中,Nginx会先尝试访问原URL资源,如果资源不存在则返回index.html。

    try_files的工作机制是会依次访问参数中的资源直到正常返回,需要注意,最后的参数资源必须存在,不然nginx将报错。更多详情请参考Nginx官网这里

    负载均衡

    首先在http中定义多台机器组,组名为backend:

        upstream backend {
            server backend1.example.com;
            server backend2.example.com;
            server 192.0.0.1 backup;
        }
    

    然后就可以在server中引用backend了,完整例子:

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

    配置负载均衡方式

    upstream中可以配置以哪种方式分发请求:

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

    Nginx有以下方式分发请求:

    • Round Robin(默认) - 依次轮训
    • Least Connection - 连接最少的节点胜
    • IP Hash - 通过IP地址的hash值进行路由,可以保证来源相同的请求总是路由到同一个节点,进而支持sticky session
    • 通用Hash - 通过自定义算法计算Hash值来决定路由

    每种方式都有其自定义的配置,更多详情请参考Nginx官网

    Health Check

    如果upstream服务器返回不正常,那么Nginx将在一段时间内不再向该服务代理请求:

    upstream backend {
        server backend1.example.com;
        server backend2.example.com max_fails=3 fail_timeout=30s;
        server backend3.example.com max_fails=2;
    }
    

    上例中,对于backend2.example.com,如果超过了3(max_fails)次返回失败,那么Nginx将在30秒内(fail_timeout)不再路由到该节点,30秒后再次启用该节点。

    默认情况下,max_fails=1,fail_timeout=10s。

    代理多个应用

    配置HTTPS

    最佳实践

    设置worker_processes

    通常的做法是将worker_processes设置成与CPU的数量相同,也可以设置为auto让Nginx自动为你决定。

    worker_processes auto; 
    

    停用access_log

    启动access_log后Nginx记录每一次请求,这将增加磁盘空间并给Nginx带来额外负担,如果你确定不需要access_log,可以:

    access_log off;
    
    

    去掉Nginx版本号

    server_tokens off; #为了安全
    

    基于server配置gzip

    配置gzip是把双刃剑,不配吧数据量太大,配了吧有安全风险。因此推荐的实践是只针对静态资源文件使用gzip,并且基于server单独配置:

    server {
        listen         80;
        server_name    example1.com;
        gzip           on;
        gzip_types text/html text/css image/jpg image/jpeg image/png image/svg;
    }
    
    server {
        listen         443 ssl;
        server_name    example2.com;
        gzip           off;
    }
    

    注意add_header

    add_header用于向最终返回给客户端的response中添加HTTP Header信息,需要注意的是add_header并不享受Nginx的继承机制,意味着如果子context中有add_header,那么它将覆盖所有的父context中的add_header配置。比如,在http中配置了3个add_header,然后在server中配置了1个add_header,那么server中的add_header会将http中的所有3个add_header给覆盖掉。

    防止加入iframe

    某些钓鱼网站会通过iframe的方式将你的网站加入钓鱼网站,此时我们可以通过Nginx配置声明自己的网站不应该被放入iframe中,在server中配置:

    add_header X-Frame-Options DENY;
    

    启用XSS过滤器

    以下配置中,Nginx会通知浏览器启用XSS过滤器,虽然对于多数浏览器来说这个是默认设置:

    add_header X-XSS-Protection "1; mode=block";
    

    调优

    • Nginx中,当使用sendfile函数时,TCP_NOPUSH才起作用,因为在sendfile时,Nginx会要求发送某些信息来预先解释数据,这些信息其实就是报头内容,典型情况下报头很小,而且套接字上设置了TCP_NODELAY。有报头的包将被立即传输,在某些情况下(取决于内部的包计数器),因为这个包成功地被对方收到后需要请求对方确认。这样,大量数据的传输就会被推迟而且产生了不必要的网络流量交换。而通过设置TCP_NOPUSH=on,表示将所有HTTP的header一次性发出去,参考这里
    • Nginx的TCP_NODELAY只有在配置长连接时才起作用,因为长连接可能引起小包的阻塞,配置TCP_NODELAY可以避免该阻塞,参考这里
    • Use the tcp_nopush directive together with the sendfile on;directive. This enables NGINX to send HTTP response headers in one packet right after the chunk of data has been obtained by sendfile().
    • 在 nginx 中,tcp_nopush 配置和 tcp_nodelay “互斥”。
    • 默认情况下,nginx已经自动开启了对client连接的keep alive支持(同时client发送的HTTP请求要求keep alive)。
    • 默认nginx访问后端都是用的短连接(HTTP1.0),一个请求来了,Nginx 新开一个端口和后端建立连接,后端执行完毕后主动关闭该链接)。
    • location值的最后含有斜杠,比如/hello/,然后该location有设置的proxy_pass,那么当客户端访问不带最后斜杠的/hello时,Nginx将301重定向到带有斜杠的/hello/参考这里

    相关文章

      网友评论

        本文标题:Nginx学习笔记

        本文链接:https://www.haomeiwen.com/subject/znkkhqtx.html