Nginx 原理总结

为什么要使用 Nginx

Nginx 优点

  1. 轻量级,采用 C 进行编写,同样的 web 服务,会占用更少的内存及资源
  2. 抗并发,nginx 以 epoll 和 kqueue 作为开发模型,处理请求是异步非阻塞的,负载能力比 apache 高很多,而 apache 则是阻塞型的。在高并发下 nginx 能保持低资源低消耗高性能 ,而 apache 在 PHP 处理慢或者前端压力很大的情况下,很容易出现进程数飙升,从而拒绝服务的现象。
  3. nginx 处理静态文件好,静态处理性能比 apache 高三倍以上
  4. nginx 的设计高度模块化,编写模块相对简单
  5. nginx 配置简洁,正则配置让很多事情变得简单,而且改完配置能使用 -t 测试配置有没有问题,apache 配置复杂 ,重启的时候发现配置出错了,会很崩溃
  6. nginx 作为负载均衡服务器,支持 7 层负载均衡
  7. nginx 本身就是一个反向代理服务器,而且可以作为非常优秀的邮件代理服务器
  8. 启动特别容易, 并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动,还能够不间断服务的情况下进行软件版本的升级
  9. 社区活跃,各种高性能模块出品迅速

Nginx 优点(说出原因)

  1. Nginx 在核心代码都使用了与操作系统无关的代码实现,在与操作系统相关的系统调用上则分别针对各个操作系统都有独立实现,这最终造就了 Nginx 的可移植性。
  2. 非阻塞、高并发连接:处理 2-3 万并发连接数,官方监测能支持 5 万并发
  3. 内存消耗小:开启 10 个 nginx 才占 150M 内存,Nginx 采取了分阶段资源分配技术
    nginx 处理静态文件好,耗费内存少
  4. 内置的健康检查功能:如果有一个服务器宕机,会做一个健康检查,再发送的请求就不会发送到宕机的服务器了。重新将请求提交到其他的节点上。
    节省宽带:支持 GZIP 压缩,可以添加浏览器本地缓存
    稳定性高:宕机的概率非常小
  5. master/worker 结构:一个 master 进程,生成一个或者多个 worker 进程
    接收用户请求是异步的:浏览器将请求发送到 nginx 服务器,它先将用户请求全部接收下来,再一次性发送给后端 web 服务器,极大减轻了 web 服务器的压力
    一边接收 web 服务器的返回数据,一边发送给浏览器客户端
    网络依赖性比较低,只要 ping 通就可以负载均衡
    可以有多台 nginx 服务器
  6. 事件驱动:通信机制采用 epoll 模型

Apache 优点

  1. apache 的 rewrite 比 nginx 强大,在 rewrite 频繁的情况下,用 apache
  2. apache 发展到现在,模块超多,基本想到的都可以找到
  3. apache 更为成熟,少 bug ,nginx 的 bug 相对较多
  4. apache 超稳定
  5. apache 对 PHP 支持比较简单,nginx 需要配合其他后端用
  6. apache 在处理动态请求有优势,nginx 在这方面是鸡肋,一般动态请求要 apache 去做,nginx 适合静态和反向。
  7. apache 仍然是目前的主流,拥有丰富的特性,成熟的技术和开发社区

Nginx 和 Apache 区别总结

两者最核心的区别在于 apache 是同步多进程模型,一个连接对应一个进程,而 nginx 是异步的,多个连接(万级别)可以对应一个进程
一般来说,需要性能的 web 服务,用 nginx 。如果不需要性能只求稳定,更考虑 apache ,后者的各种功能模块实现得比前者,例如 ssl 的模块就比前者好,可配置项多。epoll(freebsd 上是 kqueue ) 网络 IO 模型是 nginx 处理性能高的根本理由,但并不是所有的情况下都是 epoll 大获全胜的,如果本身提供静态服务的就只有寥寥几个文件,apache 的 select 模型或许比 epoll 更高性能。当然,这只是根据网络 IO 模型的原理作的一个假设,真正的应用还是需要实测了再说的。
更为通用的方案是,前端 nginx 抗并发,后端 apache 集群,配合起来会更好。

接入层演进

通过研究接入层的发展历程,我们可以一窥 Nginx 在互联网架构中的地位。

接入层结构

接入层结构
可以看到,每一个下游都有多个上游调用,只需要做到,每一个上游都均匀访问每一个下游,就能实现整体的均匀分摊。

  1. 客户端层->反向代理层
    DNS 轮询
    DNS-server 对于一个域名配置了多个解析 ip,每次 DNS 解析请求来访问 DNS-server,会轮询返回这些 ip,保证每个 ip 的解析概率是相同的。这些 ip 就是 nginx 的外网 ip,以做到每台 nginx 的请求分配也是均衡的。
  2. 反向代理层->站点层
    反向代理层到站点层的负载均衡,是通过“nginx”实现的。
    修改 nginx.conf,可以实现多种均衡策略:
    2.1 请求轮询:和 DNS 轮询类似,请求依次路由到各个 web-server;
    2.2 最少连接路由:哪个 web-server 的连接少,路由到哪个 web-server;
    2.3 ip 哈希:按照访问用户的 ip 哈希值来路由 web-server,只要用户的 ip 分布是均匀的,请求理论上也是均匀的,ip 哈希均衡方法可以做到,同一个用户的请求固定落到同一台 web-server 上,此策略适合有状态服务,例如 session;

    session 不推荐放到站点层,后期扩展会有问题,更好的方案是放到数据层。

  3. 站点层->服务层
    站点层到服务层的负载均衡,是通过“服务连接池”实现的。
    上游连接池会建立与下游服务多个连接,每次请求会“随机”选取连接来访问下游服务。除了负载均衡,服务连接池还能够实现故障转移、超时处理、限流限速、ID 串行化等诸多功能。
  4. 服务层->数据层
    在数据量很大的情况下,由于数据层(db/cache)涉及数据的水平切分,所以数据层的负载均衡更为复杂一些,它分为“数据的均衡”,与“请求的均衡”。
    数据的均衡是指:水平切分后的每个服务(db/cache),数据量是均匀的。
    请求的均衡是指:水平切分后的每个服务(db/cache),请求量是均匀的。

接入层演进

  1. 单机架构
    客户端用 DNS 解析出来的 IP 就是 web 服务器的地址。
    缺点:
    • 单点;
    • 扩展性差。
  2. DNS 轮询
    在 DNS 服务器上多配几个 IP,由域名服务器的解析策略实现负载均衡。
    缺点:
    • 非高可用,因为一个 web 服务器挂掉后 DNS 服务器仍然会将请求解析到该服务器对应的 IP 上;
    • 扩容非实时,DNS 服务器有一个配置生效的延时时间;
    • 暴露太多外网 IP。
  3. 反向代理 Nginx
    DNS 解析到 Nginx 的 IP,然后由 Nginx 将请求负载均衡到 web 服务器。
    缺点:基本解决了上一个架构存在的问题,且可以利用 Nginx 的探活机制实现 web 服务器的高可用,但是此时 Nginx 也会成为一个单点。
  4. keepalived
    两台 Nginx 组成集群,分别部署上 keepalived,设置成相同的虚 IP,保证 Nginx 的高可用。当一台 Nginx 挂了,keepalived 能够探测到并将流量迁移到另一台 Nginx 上,整个过程对调用方透明。
    缺点:
    • 资源利用率低;
    • 扩容不方便,如果吞吐量超过 Nginx 性能上线,要加机器配置起来比较麻烦。
  5. lvs/f5
    DNS 解析出来的 IP 是 lvs 的地址。由 lvs 反向代理 Nginx 服务器,lvs 的机器上部署 keepalived+VIP 实现高可用;
    f5 的性能比 lvs 更高,但是成本也会更高。
  6. DNS 轮询
    水平扩展才是解决性能问题的根本方案,能够通过加机器扩充性能的方案才具备最好的扩展性。
    可扩展高可用接入层架构
    • 通过 DNS 轮询来线性扩展入口 lvs 层的性能;
    • 通过 keepalived 保证高可用;
    • 通过 lvs 来扩展多个 Nginx;
    • 通过 Nginx 实现对业务服务器的七层负载均衡。

使用 Nginx

手动安装 Nginx

  1. 配置
    创建目录/var/temp/nginx
    这个目录保存临时文件,在安装配置中指定:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ./configure \
    --prefix=/usr/local/nginx \
    --pid-path=/var/run/nginx/nginx.pid \
    --lock-path=/var/lock/nginx.lock \
    --error-log-path=/var/log/nginx/error.log \
    --http-log-path=/var/log/nginx/access.log \
    --with-http_gzip_static_module \
    --http-client-body-temp-path=/var/temp/nginx/client \
    --http-proxy-temp-path=/var/temp/nginx/proxy \
    --http-fastcgi-temp-path=/var/temp/nginx/fastcgi \
    --http-uwsgi-temp-path=/var/temp/nginx/uwsgi \
    --http-scgi-temp-path=/var/temp/nginx/scgi
  2. 安装
    1
    2
    make
    make install
  3. 配置环境变量
    /etc/profile中编辑,这样就可以直接使用 nginx 命令启动了
    1
    export PATH=$PATH:/usr/local/nginx/sbin
  4. 启动
    1
    2
    3
    nginx
    # 指定配置文件
    nginx -c /usr/local/nginx/conf/nginx.conf
    如果不指定-c,nginx 在启动时默认加载 conf/nginx.conf 文件,此文件的地址也可以在编译安装 nginx 时指定./configure 的参数(–conf-path= 指向配置文件(nginx.conf))
  5. 停止
    1
    2
    nginx -s stop # 相当于先查出nginx进程id再kill
    nginx -s quit # 建议使用,这种方法是等nginx进程的任务处理完毕后再停止
  6. 重启
    1
    2
    nginx -s quit
    nginx
    要想在修改配置文件 nginx.conf 后生效:
    1
    nginx -s reload

开机自启

  1. 创建/etc/init.d/nginx
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    #!/bin/bash
    # nginx Startup script for the Nginx HTTP Server
    # it is v.0.0.2 version.
    # chkconfig: - 85 15
    # description: Nginx is a high-performance web and proxy server.
    # It has a lot of features, but it's not for everyone.
    # processname: nginx
    # pidfile: /var/run/nginx.pid
    # config: /usr/local/nginx/conf/nginx.conf
    nginxd=/usr/local/nginx/sbin/nginx
    nginx_config=/usr/local/nginx/conf/nginx.conf
    nginx_pid=/var/run/nginx.pid
    RETVAL=0
    prog="nginx"
    # Source function library.
    . /etc/rc.d/init.d/functions
    # Source networking configuration.
    . /etc/sysconfig/network
    # Check that networking is up.
    [ ${NETWORKING} = "no" ] && exit 0
    [ -x $nginxd ] || exit 0
    # Start nginx daemons functions.
    start() {
    if [ -e $nginx_pid ];then
    echo "nginx already running...."
    exit 1
    fi
    echo -n $"Starting $prog: "
    daemon $nginxd -c ${nginx_config}
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && touch /var/lock/subsys/nginx
    return $RETVAL
    }
    # Stop nginx daemons functions.
    stop() {
    echo -n $"Stopping $prog: "
    killproc $nginxd
    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && rm -f /var/lock/subsys/nginx /var/run/nginx.pid
    }
    # reload nginx service functions.
    reload() {
    echo -n $"Reloading $prog: "
    #kill -HUP `cat ${nginx_pid}`
    killproc $nginxd -HUP
    RETVAL=$?
    echo
    }
    # See how we were called.
    case "$1" in
    start)
    start
    ;;
    stop)
    stop
    ;;
    reload)
    reload
    ;;
    restart)
    stop
    start
    ;;
    status)
    status $prog
    RETVAL=$?
    ;;
    *)
    echo $"Usage: $prog {start|stop|restart|reload|status|help}"
    exit 1
    esac
    exit $RETVAL
  2. 设置文件访问权限
    1
    chmod a+x /etc/init.d/nginx
  3. 设置开机加载
    /etc/rc.local中加入一行
    1
    /etc/init.d/nginx start

通过 Docker 运行 Nginx

手动进行 Nginx 配置十分繁琐,可以使用 Docker 来简化部署流程:

1
docker run -d -p 80:80 nginx

Nginx 原理 - 进程

Nginx 代码的模块化结构

Nginx 的代码是由一个核心和一系列的模块组成的。
核心主要用于提供 WebServer 的基本功能,以及 Web 和 Mail 反向代理的功能;还用于启用网络协议,创建必要的运行时环境以及确保不同的模块之间平滑地进行交互。
不过,大多跟协议相关的功能和应用特有的功能都是由 nginx 的模块实现的。
换句话说, 每一个功能或操作都由一个模块来实现
这些功能模块大致可以分为事件模块、阶段性处理器、输出过滤器、变量处理器、协议、upstream 和负载均衡几个类别,这些共同组成了 nginx 的 http 功能。
事件模块主要用于提供 OS 独立的(不同操作系统的事件机制有所不同)事件通知机制如 kqueue 或 epoll 等。
协议模块则负责实现 nginx 通过 http、tls/ssl、smtp、pop3 以及 imap 与对应的客户端建立会话。
在 Nginx 内部,进程间的通信是通过模块的 pipelinechain 实现的。
换句话说,每一个功能或操作都由一个模块来实现。例如:压缩、通过 FastCGI 或 uwsgi 协议与 upstream 服务器通信、以及与 memcached 建立会话等。

进程结构

一个 Nginx 服务器实例由一个 master 进程和多个 worker 进程组成。
进程结构
master进程主要用来管理 worker 进程,还有一些对整个服务器的初始化和日志记录等工作。
管理 worker 的过程:接收来自外界的信号,向各 worker 进程发送 信号 ,监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动重新启动(fork)新的 worker 进程。

master 主要功能

  • 读取并验证配置信息;
  • 创建、绑定及关闭套接字;
  • 启动、终止 worker 进程及维护 worker 进程的个数;
  • 无须中止服务而重新配置工作;
  • 控制非中断式程序升级,启用新的二进制程序并在需要时回滚至老版本;
  • 重新打开日志文件;
  • 编译嵌入式 perl 脚本

对请求的实际处理由 worker 负责,且每个请求只能由一个 worker 负责(一对一)。在启动时,创建一组初始的监听套接字,HTTP 请求和响应之时,worker 连续接收、读取和写入套接字。

worker 主要功能

  • 接收、传入并处理来自客户端的连接;
  • 提供反向代理及过滤功能;
  • nginx 任何能完成的其它任务

Nginx 的启动

nginx 启动后,在 unix 系统中会以 daemon 的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程(你可以理解为工人和管理员)。

Nginx 处理连接过程

nginx 不会为每个连接派生进程或线程,而是由 worker 进程通过监听共享套接字接受新请求,并且使用高效的 循环 来处理数千个连接。
Nginx 不使用仲裁器或分发器来分发连接,这个工作由操作系统内核机制完成。 监听套接字 在启动时就完成初始化,worker 进程通过这些套接字接受、读取请求和输出响应。

一次请求过程大概执行过程为:

  1. nginx 在启动时,会解析配置文件,得到需要监听的端口与 ip 地址,然后在 nginx 的 master 进程里面先初始化好这个监控的 socket,再进行 listen(listenfd);
  2. 由 master 进程 fork 出多个 worker 进程;
  3. 此时客户端可以向 nginx 发起连接了,客户端会与 nginx 进行三次握手(TCP),与 nginx 建立好一个连接;
  4. 所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程会在注册 listenfd 读事件前抢 accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,然后在读事件里调用 accept 接受该连接
  5. 当一个 worker 进程在 accept 这个连接之后,然后创建 nginx 对连接的封装,即 ngx_connection_t 结构体,就开始读取请求、解析请求、处理请求( 异步非阻塞 ),主要是根据事件调用相应的事件处理模块,如 http 模块与客户端进行数据的交换
  6. 产生数据后,再返回给客户端,最后才断开连接,或者由客户端主动关闭连接。

进程间通信

在 Nginx 内部,进程间的通信是通过模块的 pipelinechain 实现的,其原理是信号机制,master 对 worker 进程采用信号进行控制。

事件驱动

所谓事件驱动架构,简单来说,就是由一些事件发生源来产生事件,由一个或多个事件收集器(epolld 等)来收集、分发事件,然后许多事件处理器会注册自己感兴趣的事件,同时会“消费”这些事件。nginx 不会使用进程或线程作为事件消费者,只能是某个模块,当前进程调用模块。
传统 web 服务器(如 Apache)的所谓事件局限在 TCP 连接建立、关闭上,其他读写都不再是事件驱动,这时会退化成按序执行每个操作的批处理模式,这样每个请求在连接建立后都将始终占用系统资源,直到连接关闭才会释放资源。大大浪费了内存、cpu 等资源。并且把一个进程或线程作为事件消费者。 传统 Web 服务器每个事件消费者独占一个进程资源,相对来说,Nginx 只是被事件分发者进程短期调用而已。
nginx 采用多 worker 的方式来处理请求,每个 worker 里面只有一个主线程,那能够处理的并发数很有限,多少个 worker 就能处理多少个并发,那么何来的高并发呢?
其实,Nginx 是采用了异步非阻塞的 IO 模型来处理请求的(epoll),异步的概念是和同步相对的,也就是不同事件之间不是同时发生的。非阻塞的概念是和阻塞对应的,阻塞是事件按顺序执行,每一事件都要等待上一事件的完成,而非阻塞是如果事件没有准备好,这个事件可以直接返回,过一段时间再进行处理询问,这期间可以做其他事情。
请求的多阶段异步处理只能基于事件驱动框架实现,就是把一个请求的处理过程按照事件的触发方式分为多个阶段,每个阶段都可以有事件收集、分发器(epoll 等)来触发。比如一个 http 请求可以分为七个阶段。
每种事件都有一个事件队列,按触发的先后顺序处理。

惊群现象

惊群是多个子进程在同一时刻监听同一个端口引起的;
Nginx 解决方法:同一个时刻只能有唯一一个 worker 子进程监听 web 端口,此时新连接事件只能唤醒唯一正在监听端口的 worker 子进程。这可以通过锁或互斥量实现。

为什么不使用多线程

  • Apache: 创建多个进程或线程,而每个进程或线程都会为其分配 cpu 和内存(线程要比进程小的多,所以 worker 支持比 perfork 高的并发),并发过大会榨干服务器资源。
  • Nginx: 采用单线程来异步非阻塞处理请求(管理员可以配置 Nginx 主进程的工作进程的数量)(epoll),不会为每个请求分配 cpu 和内存资源,节省了大量资源,同时也减少了大量的 CPU 的上下文切换。所以才使得 Nginx 支持更高的并发。

模块

模块命名

ngx_http_[module-name]_[main|srv|loc]_conf_t
前缀表示模块名,后面表示模块运行在哪一层

模块化结构

Nginx由内核和一系列模块组成,内核提供web服务的基本功能,如启用网络协议,创建运行环境,接收和分配客户端请求,处理模块之间的交互。Nginx的各种功能和操作都由模块来实现。
Nginx的模块从结构上分为核心模块、基础模块和第三方模块。

  • 核心模块: HTTP模块、EVENT模块和MAIL模块
  • 基础模块: HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块
  • 第三方模块: HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块及用户自己开发的模块

这样的设计使Nginx方便开发和扩展,也正因此才使得Nginx功能如此强大。Nginx的模块默认编译进nginx中,如果需要增加或删除模块,需要重新编译Nginx,这一点不如Apache的动态加载模块方便。如果有需要动态加载模块,可以使用由淘宝网发起的web服务器Tengine,在nginx的基础上增加了很多高级特性,完全兼容Nginx,已被国内很多网站采用。

模块大致结构如下图所示。
Nginx模块结构
Nginx模块,简单地讲,就是:在特定地方调用的函数。

模块执行过程

nginx的配置 指令作用域 分为以下几种:main,server,location
main作用域的范围为整个配置文件,而server是指某个具体的服务器(通过端口号来区分),而location就是指要访问这个server的哪个location。

Nginx 本身做的工作实际很少,当它接到一个 HTTP 请求时,它仅仅是通过查找配置文件将此次请求映射到一个 locationblock,而此 location 中所配置的各个指令则会启动不同的模块去完成工作。
通常一个 location 中的指令会涉及一个 handler 模块和多个 filter 模块(当然,多个 location 可以复用同一个模块)。handler 模块负责处理请求,完成响应内容的生成,而 filter 模块对响应内容进行处理。
模块处理请求的大致过程如下图所示。
Nginx模块执行流程

http index模块(ngx_http_index_module)

定义将要被作为默认页的文件。 文件的名字可以包含变量。 文件以配置中指定的顺序被 nginx 检查。 列表中的最后一个元素可以是一个带有绝对路径的文件。

1
2
3
4
location / {
root /home/ftpuser/www;
index index.html index.$haha.html index.htm;
}

需要注意的是,index 文件会引发内部重定向,请求可能会被其它 location 处理。
比如下面的配置,请求”/“实际上将会在第二个location中作为”/index.html”被处理:

1
2
3
4
5
6
location = / {
index index.html;
}
location / {
...
}

http log模块(ngx_http_log_module)

1
2
3
4
log_format  gzip '$remote_addr-$remote_user[$time_local]'
:'$request$status $bytes_sent'
:'" $ http _ referer" "$http_user_agent" "$gzip_ratio"';
access_log /spool/logs/nginx-access.log gzip buffer=32k;

指令 access_log 指派路径、格式和缓存大小:

1
2
3
4
# 格式
access_log path [format [buffer=size | off ]
# 默认
access_log log/access.log combined

其中参数 “off” 将清除当前级别的所有 access_log 指令。如果未指定格式,则使用预置的 “combined” 格式。缓存不能大于能写入磁盘的文件的最大值(在 FreeBSD 3.0-6.0 ,缓存大小无此限制)。
指令 log_format 指定日志格式:

1
2
3
4
# 格式
log_format name format [format ...]
# 默认
log_format combined "..."

Access模块(ngx_http_access_module)

此模块提供了一个简易的基于主机的访问控制。
ngx_http_access_module 模块让我们可以对特定 IP 客户端进行控制。 规则检查按照第一次匹配的顺序,此模块对网络地址有放行和禁止的权利。

1
2
3
4
5
6
7
# 仅允许网段 10.1.1.0/16 和 192.168.1.0/24 中除 192.168.1.1 之外的 ip 访问
location / {
: deny 192.168.1.1;
: allow 192.168.1.0/24;
: allow 10.1.1.0/16;
: deny all;
}
  1. 放行语法
    1
    allow [ address | CIDR | all ]
    作用域: http, server, location, limit_except
    allow 描述的网络地址有权直接访问
  2. 禁止语法
    1
    deny [ address | CIDR | all ]
    作用域: http, server, location, limit_except
    deny 描述的网络地址拒绝访问

Rewrite模块(ngx_http_rewrite_module)

执行 URL 重定向,允许你去掉带有恶意的 URL,包含多个参数(修改).利用正则的匹配,分组和引用,达到目的 配置范例:该模块允许使用正则表达式改变 URL,并且根据变量来转向以及选择配置

  1. if语法
    1
    if (condition) { ... }
    作用域: server, location
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    if ($http_user_agent ~ MSIE) {
    : rewrite ^(.*)$ /msie/$1 break;
    }
    if ($http_cookie ~* "id=([^;] +)(?:;|$)" ) {
    : set $id $1;
    }
    if ($request_method = POST ) {
    : return 405;
    }
    if (!-f $request_filename) {
    : break;
    : proxy_pass http://127.0.0.1;
    }
    if ($slow) {
    : limit_rate 10k;
    }
    if ($invalid_referer) {
    : return 403;
    }
  2. return语法
    这个指令根据规则的执行情况,返回一个状态值给客户端。可使用值包括:204,400,402-406,408,410,411,413,416 及 500-504。也可以发送非标准的 444 代码-未发送任何头信息下结束连接。
    1
    return cod
    作用域: server, location, if
  3. rewrite语法
    这个指令根据正则表达式或者待替换的字符串来更改 URL。指令根据配置文件中的先后顺序执行生效。
    1
    rewrite regex replacement flag
    flag可以有以下取值:
    • last :表示完成 rewrite
    • break:本规则匹配完成后,终止匹配,不再匹配后面的规则
    • redirect:返回 302 临时重定向,地址栏会显示跳转后的地址
    • permanent:返回 301 永久重定向,地址栏会显示跳转后的地址
      作用域: server, location, if

Proxy模块(ngx_http_proxy_module)

此模块能代理请求到其它服务器.也就是说允许你把客户端的 HTTP 请求转到后端服务器(这部分的指令非常多,但不是全部都会被用到,详细指令列表可以上官网查看,这里是比较常见的指令简介)

1
2
3
4
5
6
7
8
# 强制一些被忽略的头传递到客户端
proxy_pass_header Server;
# 允许改写出现在 HTTP 头却被后端服务器触发重定向的 URL,对响应本身不做任何处理
proxy_redirect off;
# 允许你重新定义代理 header 值再转到后端服务器,目标服务器可以看到客户端的原始主机名
proxy_set_header Host $http_host;
# 目标服务器可以看到客户端的真实 ip,而不是转发服务器的 ip
proxy_set_header X-Real-IP $remote_addr;

upstream模块(ngx_http_upstream_module)

该指令将来自客户端的一个请求分到多个上行服务器上,即我们常说的负载均衡。
默认情况下采用轮询策略,每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
可以使用weight来指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

1
2
3
4
5
6
7
8
9
10
upstream backend {
server backend1.example.com weight=5;
server backend2.example.com:8080;
server unix:/tmp/backend3;
}
server {
location / {
proxy_pass http://backend;
}
}
  1. upstream指令
    这个指令描述了一个服务器的集合,该集合可被用于 proxy_pass 和 fastcgi_pass 指令中,作为一个单独的实体。这些服务器可以是监听在不同的端口,另外,并发使用同时监听 TCP 端口和 Unix 套接字的服务器是可能的。 这些服务器能被分配不同的权重。如果没有指定,则都为 1 ,即默认的策略为轮询。
    1
    2
    3
    4
    5
    upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    }
    可以通过weight参数指定每个上行服务器的权重,weight和访问比率成正比,用于后端服务器性能不均的情况。
    1
    2
    3
    4
    upstream test{
    server localhost:8080 weight=9;
    server localhost:8081 weight=1;
    }
  2. ip_hash指令
    请求基于客户端的 IP 地址在服务器间进行分发。 IPv4 地址的前三个字节或者 IPv6 的整个地址,会被用来作为一个散列 key。 这种方法可以确保从同一个客户端过来的请求,会被传给同一台服务器。除了当服务器被认为不可用的时候,这些客户端的请求会被传给其他服务器,而且很有可能也是同一台服务器。
    如果其中一个服务器想暂时移除,应该加上 down 参数。这样可以保留当前客户端 IP 地址散列分布。
    作用域:upstream
    1
    2
    3
    4
    5
    6
    7
    upstream backend {
    ip_hash;
    server backend1.example.com;
    server backend2.example.com;
    server backend3.example.com down;
    server backend4.example.com;
    }
  3. fair(第三方)
    按后端服务器的响应时间来分配请求,响应时间短的优先分配。
    1
    2
    3
    4
    5
    upstream backend { 
    fair;
    server localhost:8080;
    server localhost:8081;
    }
  4. url_hash(第三方)
    按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。 在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。
    1
    2
    3
    4
    5
    6
    upstream backend {
    hash $request_uri;
    hash_method crc32;
    server localhost:8080;
    server localhost:8081;
    }
  5. server指令
    定义服务器的地址 address 和其他参数 parameters。 地址可以是域名或者 IP 地址,端口是可选的,或者是指定“unix:”前缀的 UNIX 域套接字的路径。如果没有指定端口,就使用 80 端口。 如果一个域名解析到多个 IP,本质上是定义了多个 server。
    作用域:upstream
    1
    2
    3
    4
    5
    upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    }

nginx配置文件优化

配置文件

默认配置文件位置在/usr/local/nginx/conf/nginx.conf,在configue时决定,也可以在运行nginx时指定配置文件

1
nginx nginx.conf

顶层配置

顶层配置即nginx.conf中前面、暴露在外面的那几项

1
2
3
#user  nobody;
worker_processes 1;
#pid logs/nginx.pid;

worker_processes 定义了 nginx 在为你的网站提供服务时,worker 进程的数量。
这个优化值受到包括 CPU 内核数、存储数据的磁盘数、负载值在内的许多因素的影响。如果不确定的话,将其设置为可用的 CPU 内核的数量是一个不错的选择(设置为“auto”,将会尝试自动检测可用的值)。
另外本机的CPU核心信息可以使用下面命令查看

1
cat /proc/cpuinfo | grep processor

events模块

events模块包括了 nginx 中处理链接的全部设置

1
2
3
4
events {
worker_connections 1024;
# multi_accept on;
}

worker_connections 设置了一个 worker 进程可以同时打开的链接数。
multi_accept 的作用是告诉 nginx 在收到新链接的请求通知时,尽可能接受链接。最好开着

http模块

当外部有 http 请求时,nginx 的 http 模块才是处理这个请求的核心。

  1. Basic Settings
    sendfile 指向 sendfile()函数。sendfile() 在磁盘和 TCP 端口(或者任意两个文件描述符)之间复制数据。
    在 sendfile 出现之前,为了传输这样的数据,需要在用户空间上分配一块数据缓存,使用 read() 从源文件读取数据到缓存,然后使用 write() 将缓存写入到网络。
    sendfile() 直接从磁盘上读取数据到操作系统缓冲。由于这个操作是在内核中完成的,sendfile() 比 read() 和 write() 联合使用要更加有效率。
    tcp_nopush 配置 nginx 在一个数据包中发送全部的头文件,而不是一个一个发送。
    tcp_nodelay 配置 nginx 不要缓存数据,应该快速的发送小数据——这仅仅应该用于频繁发送小的碎片信息而无需立刻获取响应的、需要实时传递数据的应用中。
    keepalive_timeout 指定了与客户端的 keep-alive 链接的超时时间。服务器会在这个时间后关闭链接。我们可以降低这个值,以避免让 worker 过长时间的忙碌。
  2. Logging Settings
    access_log 确定了 nginx 是否保存访问日志。将这个设置为关闭可以降低磁盘 IO 而提升速度。
    error_log 设置 nginx 应当记录错误日志。
  3. Gzip Settings
    gzip 设置 nginx gzip 压缩发送的数据。这会减少需要发送的数据的数量。
    gzip_disable 为指定的客户端禁用 gzip 功能。
    gzip_proxied 允许或禁止基于请求、响应的压缩。设置为 any,就可以 gzip 所有的请求。
    gzip_comp_level 设置了数据压缩的等级。等级可以是 1-9 的任意一个值,9 表示最慢但是最高比例的压缩。
    gzip_types 设置进行 gzip 的类型。有下面这些,不过还可以添加更多。

mail模块

略…

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
user nginxuser;
worker_processes 4;
pid logs/nginx.pid;

events {
worker_connections 1024;
multi_accept on;
}

http {

##
# Basic Settings
##


sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 15;
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;


##
# Logging Settings
##


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


##
# Gzip Settings
##


gzip on;
gzip_disable "msie6";


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


##
# nginx-naxsi config
##
# Uncomment it if you installed nginx-naxsi
##


#include /etc/nginx/naxsi_core.rules;


##
# nginx-passenger config
##
# Uncomment it if you installed nginx-passenger
##


#passenger_root /usr;
#passenger_ruby /usr/bin/ruby;


##
# 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;
# }
#}

优化 - Linux 最大连接数

查看 Linux 连接数

默认配置下,Linux 只支持有限的连接数。
Linux 的线程其实是一个进程,所以 java 的也是,具体来说,叫做“light weight process(LWP)”—轻量级进程。
LWP 与其它进程共享所有(或大部分)逻辑地址空间和系统资源,一个进程可以创建多个 LWP,这样它们共享大部分资源;LWP 有它自己的进程标识符,并和其他进程有着父子关系;。LWP 由内核管理并像普通进程一样被调度
使用以下命令可以看到某个用户使用了多少进程资源

1
ps -eLf | grep xjjbot(uid)  | wc -l

使用下面命令可以查看具体每个进程开启了多少线程

1
ps -o nlwp,pid,lwp,args -u xjjbot(uid)  | sort -n

根据 linux 一切都是文件的规则,首先想到的,是修改 ulimit 的参数,然而也不是,因为它已经足够大了。交叉回想一下 elasticsearch,在安装的时候,需要配置一个叫做 nproc 的东西,问题大概就出在这,是进程资源不够用啦。
相关的配置文件:

1
/etc/security/limits.conf

在不同的内核版本上,也有一些小差异。比如:/etc/security/limits.d/*
下的文件,会在某些时候覆盖 limits.conf 的配置。所以配置不生效的情况下,记得检查一下。
鉴于以上原因,可以将 limits.d 中的配置全部注释掉,统一在 limits.conf 中配置。
以下是原始配置

1
2
*          soft    nproc     4096
root soft nproc unlimited

将 4096 改为大点的数字,或者直接改成 unlimited 就可以了。

单机支持 100 万连接是可行的,但带宽问题会成为显著的瓶颈。启用压缩的二进制协议会节省部分带宽,但开发难度增加。

操作系统优化

更改进程最大文件句柄数

1
ulimit -n 1048576

复制代码修改单个进程可分配的最大文件数

1
echo 2097152 > /proc/sys/fs/nr_open

复制代码修改/etc/security/limits.conf 文件

1
2
3
4
*   soft nofile  1048576
* hard nofile 1048576
* soft nproc unlimited
root soft nproc unlimited

复制代码记得清理掉/etc/security/limits.d/*下的配置

网络优化

打开/etc/sysctl.conf,添加配置然后执行,使用 sysctl 生效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#单个进程可分配的最大文件数
fs.nr_open=2097152

#系统最大文件句柄数
fs.file-max = 1048576

#backlog 设置
net.core.somaxconn=32768
net.ipv4.tcp_max_syn_backlog=16384
net.core.netdev_max_backlog=16384

#可用知名端口范围配置
net.ipv4.ip_local_port_range='1000 65535'

#TCP Socket 读写 Buffer 设置
net.core.rmem_default=262144
net.core.wmem_default=262144
net.core.rmem_max=16777216
net.core.wmem_max=16777216
net.core.optmem_max=16777216
net.ipv4.tcp_rmem='1024 4096 16777216'
net.ipv4.tcp_wmem='1024 4096 16777216'

#TCP 连接追踪设置
net.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_tcp_timeout_time_wait=30

#TIME-WAIT Socket 最大数量、回收与重用设置
net.ipv4.tcp_max_tw_buckets=1048576

# FIN-WAIT-2 Socket 超时设置
net.ipv4.tcp_fin_timeout = 15

参考

  1. Linux Web 运维(Nginx)实战
  2. Nginx 开发从入门到精通
  3. nginx documentation

config

nginx.conf配置文件详解 http://www.ha97.com/5194.html
更多配置技巧 https://www.nginx.com/resources/wiki/start/

Nginx 原理

  1. Nginx 内部有使用多线程吗?
  2. 如果这篇文章说不清 epoll 的本质,那就过来掐死我吧! (1)

模块

  1. log
    Module ngx_http_log_module
    Module ngx_stream_log_module
    nginx 日志格式及自定义日志配置
  2. proxy
    Module ngx_http_proxy_module
  3. TCP / UDP
    How nginx processes a TCP/UDP session

Tengine

  1. Documentation