weixueyuan-Nginx编译及部署1

https://www.weixueyuan.net/nginx/

Nginx是什么

Nginx(发音同“engine x”)是一个高性能的反向代理和 Web 服务器软件,最初是由俄罗斯人 Igor Sysoev 开发的。Nginx 的第一个版本发布于 2004 年,其源代码基于双条款 BSD 许可证发布,因其系统资源消耗低、运行稳定且具有高性能的并发处理能力等特性,Nginx 在互联网企业中得到广泛应用。

Nginx 是互联网上最受欢迎的开源 Web 服务器之一,它不仅提供了用于开发和交付的一整套应用技术,还是应用交付领域的开源领导者。Netcraft 公司 2019 年 7 月的统计数据表明,Nginx 为全球最繁忙网站中的 25.42% 提供了服务或代理,进一步扩大了其在主机域名领域的占有量,新增 5220 万个站点,总数达 4.4 亿个,市场占有率已经超过 Apache 4.89%。

得益于近几年云计算和微服务的快速发展,Nginx 因在其中发挥了自身优势而得到广泛应用,且有望在未来占有更多的市场份额。

2019 年 3 月,著名硬件负载均衡厂商 F5 宣布收购 Nginx,Nginx 成为 F5 的一部分。F5 表示,将加强对开源和 Nginx 应用平台的投资,致力于 Nginx 开源技术、开发人员和社区的发展,更大的投资将为开放源码计划注入新的活力,会主办更多的开放源码活动,并产生更多的开放源码内容。

1、Nginx 的不同版本

作为最受欢迎的 Web 服务器之一,Nginx 自 2004 年发布以来已经得到很多互联网企业的应用。官方目前有 Nginx 开源版和 Nginx Plus 商业版两个版本,开源版是目前使用最多的版本,商业版除了包含开源版本的全部功能外,还提供了一些独有的企业级功能。

Nginx 在国内互联网企业中也得到了广泛应用,企业在实际使用中会根据自身的需求进行相应的扩展和增强。目前国内流行的 Nginx 主要有两个开源版本,分别是由淘宝网技术团队维护的 Tengine 项目和由章亦春发起的 OpenResty 项目。

1) 开源版 Nginx

Nginx 开源版一直处于活跃开发状态,由 Nginx 公司负责开发与维护。截至本教程写作时,Nginx 开源版本已经更新到 1.17.2 版本。Nginx 自推出以来,一直专注于低资源消耗、高稳定、高性能的并发处理能力,除了提供 Web 服务器的功能外,还实现了访问代理、负载均衡、内容缓存、访问安全及带宽控制等功能。其基于模块化的代码架构及可与其他开发语言(如 Perl、JavaScript 和 Lua)有效集成的可编程特性,使其具有强大的扩展能力。

部署和优化具有高效率、高性能并发请求处理能力的应用架构是应用架构师一直追求的目标,在应用架构技术的迭代中,各种分离式思想成为主流,比如将访问入口和 Web 服务器分离、将 Web 服务器和动态脚本解析器分开、将 Web 功能不断拆分、微服务等。

Nginx 不仅提供了 Web 服务器的功能,还极大满足了这一主流架构的需求并提供了如下应用特性。

① 访问路由

现今大型网站的请求量早已不是单一 Web 服务器可以支撑的了。单一入口、访问请求被分配到不同的业务功能服务器集群,是目前大型网站的通用应用架构。Nginx 可以通过访问路径、URL 关键字、客户端 IP、灰度分流等多种手段实现访问路由分配。

② 反向代理

就反向代理功能而言,Nginx 本身并不产生响应数据,只是应用自身的异步非阻塞事件驱动架构,高效、稳定地将请求反向代理给后端的目标应用服务器,并把响应数据返回给客户端。其不仅可以代理 HTTP 协议,还支持 HTTPS、HTTP/2、FastCGI、uWSGI、SCGI、gRPC 及 TCP/UDP 等目前大部分协议的反向代理。

③ 负载均衡

Nginx 在反向代理的基础上集合自身的上游(upstream)模块支持多种负载均衡算法,使后端服务器可以非常方便地进行横向扩展,从而有效提升应用的处理能力,使整体应用架构可轻松应对高并发的应用场景。

④ 内容缓存

动态处理与静态内容分离是应用架构优化的主要手段之一,Nginx 的内容缓存技术不仅可以实现预置静态文件的高速缓存,还可以对应用响应的动态结果实现缓存,为响应结果变化不大的应用提供更高速的响应能力。

⑤ 可编程

Nginx 模块化的代码架构方式为其提供了高度可定制的特性,但可以用C语言开发 Nginx 模块以满足自身使用需求的用户只是少数。Nginx 在开发之初就具备了使用 Perl 脚本语言实现功能增强的能力。Nginx 对 JavaScript 语言及第三方模块对 Lua 语言的支持,使得其可编程能力更强。

Nginx 开源版本维护了两个版本分支,分别为主线(mainline)分支和稳定(stable)分支。主线分支是一个活跃分支,会添加一些最新的功能并进行错误修复,由版本号中的第二位奇数标识,截至本教程写作时的最新版本为 1.17.2。稳定分支会集成修复严重错误的代码,但不会增加新的功能,由版本号中的第二位偶数标识,截至本教程写作时的最新版本为 1.16.1。想了解更多内容的用户可参阅 Nginx 官方网站

2) 商业版 Nginx Plus

Nginx Plus 是 Nginx 于 2013 年推出的商业版本,在开源版本的基础上增加了使用户对 Nginx 的管理和监控更轻松的功能。其代码在单独的私有代码库中维护。它始终基于最新版本的 Nginx 开源版本主线分支,并包含一些封闭源代码特性和功能。因此,除了开源版本中提供的功能外,Nginx Plus 还具有独有的企业级功能,包括实时活动监视数据、通过 API 配置上游服务器负载平衡和主动健康检查等。

相对于开源版本,Nginx Plus 还提供了以下几个功能。

① 负载均衡

  • 基于 cookies 的会话保持功能。
  • 基于响应状态码和响应体的主动健康监测。
  • 支持 DNS 动态更新。

② 动态管理

  • 支持通过 API 清除内容缓存。
  • 可通过 API 动态管理上游的后端服务器列表。

③ 安全控制

  • 基于 API 和 OpenID 连接协议单点登录(SSO)的 JWT(JSON Web Token)认证支持。
  • Nginx WAF 动态模块。

④ 状态监控

  • 超过 90 个状态指标的扩展状态监控。
  • 内置实时图形监控面板。
  • 集成可用于自定义监控工具的 JSON 和 HTML 输出功能支持。

⑤ Kubernetes Ingress Controller

  • 支持 Kubernetes 集群 Pod 的会话保持和主动健康监测。
  • 支持 JWT 身份认证。

⑥ 流媒体

  • 支持自适性串流(Adaptive Bitrate Streaming,ABS)媒体技术 HLS(Apple HTTP Live Streaming)和 HDS(Adobe HTTP Dynamic Streaming)。
  • 支持对 MP4 媒体流进行带宽控制。

3) 分支版本 Tengine

Tengine 是由淘宝网技术团队发起的 Nginx 二次开发项目,是在开源版 Nginx 及诸多第三方模块的基础上,针对淘宝网的高并发需求进行的二次开发。其中添加了很多针对互联网网站中使用 Nginx 应对高并发负载、安全及维护等的功能和特性。

据 Tengine 官网介绍,Tengine 不仅在淘宝网上使用,搜狗、天猫、大众点评、携程、开源中国等也在使用,其性能和稳定性得到了有效检验。Tengine 从 2011 年 12 月开始成为开源项目,Tengine 团队的核心成员来自淘宝、搜狗等互联网企业。

Tengine 在继承 Nginx 的所有功能的同时,也保持了自有的对 Nginx 的优化和增强,其增强特性如下。
  • 继承 Nginx 1.17.3 版本的所有特性,兼容 Nginx 的配置;
  • 支持 HTTP 的 CONNECT 方法,可用于正向代理场景;
  • 支持异步 OpenSSL,可使用硬件(如 QAT)进行 HTTPS 的加速与卸载;
  • 增强相关运维、监控能力,如异步打印日志及回滚、本地 DNS 缓存、内存监控等;
  • Stream 模块支持 server_name 指令;
  • 支持输入过滤器机制。该机制的使用使得 Web 应用防火墙的编写更为方便;
  • 支持设置 Proxy、Memcached、FastCGI、SCGI、uWSGI 在后端失败时的重试次数;
  • 支持动态脚本语言 Lua,其扩展功能非常高效简单;
  • 支持按指定关键字(域名、URL 等)收集 Tengine 运行状态;
  • 更强大的防攻击(访问速度限制)模块。

Tengine 是基于 Nginx 开发的轻量级开源 Web 服务器,作为阿里巴巴七层流量入口的核心系统,支撑着阿里巴巴“双11”等大促活动的平稳度过,并提供了智能的流量转发策略、HTTPS 加速、安全防攻击、链路追踪等众多高级特性,同时秉着软硬件结合的性能优化思路,在高性能、高并发方面取得了重大突破。

目前,Tengine 正通过打通 Ingress Controller 和 Kubernetes 使 Tengine 具备动态感知某个服务整个生命周期的能力。未来,Tengine 将定期开源内部通用组件功能模块,并同步 Nginx 官方的最新代码,丰富开发者们的开源Web服务器选项。

4) 扩展版本 OpenResty

OpenResty 是基于 Nginx 开源版本的扩展版本,它利用 Nginx 的模块特性,使 Nginx 支持 Lua 语言的脚本编程,鉴于 Lua 本身嵌入应用程序中增强应用程序扩展和定制功能的设计初衷,开源版本 Nginx 的可编程性得到大大增强。

据 OpenResty 官网介绍,2017 年全球互联网中至少有 23 万台主机正在使用 Nginx 的 OpenResty 版本作为 Web 服务器或网关应用。OpenResty® 是一个基于 Nginx 与 Lua 的高性能Web平台,其内部集成了大量精良的 Lua库、第三方模块以及大多数依赖项,以便搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),将 Nginx 变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师就可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 模块及 Lua 模块,快速构造出足以胜任一万乃至百万以上单机并发连接的高性能Web应用系统。

OpenResty® 的目标是让 Web 服务直接运行在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅对 HTTP 客户端请求,还对远程后端如 MySQL、PostgreSQL、Memcached 及 Redis 等都进行一致的高性能响应。

OpenResty 构架在 Nginx 和 LuaJIT 的基础之上,利用 Nginx 的模块特性集成了大量 Lua 支持库,用户可以很方便地使用 Lua 编程语言对 Nginx 的功能进行扩展和增强。

OpenResty 通过基于 Nginx 优化的 ngx.location.capture_multi 功能,可以非阻塞地并行转发多个子请求给后端服务器,当后端服务器返回数据时进行相应的归类和排序处理,进而有效提升客户端的请求响应速度。在 OpenResty 代码架构中,其代码以 ngx_lua 模块的形式嵌入 Nginx 代码中,从而使用户编写的 Lua 代码与 Nginx 进程协同工作。OpenResty 为每个 Nginx 工作进程(Worker Process)创建了一个 Lua 虚拟机(LuaVM),如下图所示。并将 Nginx I/O 原语封装注入 Lua 虚拟机中供 Lua 代码访问,每个外部请求都由 Lua 虚拟机产生一个 Lua 协程(coroutine)进行处理,协程之间彼此数据隔离并共享对应的 Lua 虚拟机。

OpenResty Lua虚拟机
图:OpenResty Lua虚拟机

当 Lua 代码调用异步接口时,会挂起当前协程以不阻塞 Nginx 工作进程,等异步接口处理完成时再还原当前协程继续运行。

OpenResty 项目开始于 2007 年 10 月,最早是为雅虎中国搜索部门开发的项目,后由章亦春进行开发和维护,并得到了国内外诸多企业的应用,目前主要由 OpenResty 软件基金会和 OpenResty Inc. 公司提供支持。

Nginx源码架构分析

Nginx 低资源消耗、高稳定、高性能的并发处理能力,来源于其优秀的代码架构。它采用了多进程模型,使自身具有低资源消耗的特性。以事件驱动的异步非阻塞多进程请求处理模型,使 Nginx 的工作进程通过异步非阻塞的事件处理机制,实现了高性能的并发处理能力,让每个连接的请求均可在 Nginx 进程中以工作流的方式得到快速处理。

Nginx 代码架构充分利用操作系统的各种机制,发挥了软硬件的最大性能,使它在普通硬件上也可以处理数十万个并发连接。而且 Nginx 支持在多种操作系统下部署运行,为发挥 Nginx 的最大性能,需要对不同的平台进行细微的调整,为方便了解 Nginx 架构的特点,本教程仅以 Linux 系统平台为例进行介绍。

1、多进程模型

进程是操作系统资源分配的最小单位,由于 CPU 数量有限,多个进程间通过被分配的时间片来获得 CPU 的使用权,系统在进行内核管理和进程调度时,要执行保存当前进程上下文、更新控制信息、选择另一就绪进程、恢复就绪进程上下文等一系列操作,而频繁切换进程会造成资源消耗。

Nginx 采用的是固定数量的多进程模型(见下图),由一个主进程(Master Process)和数量与主机 CPU 核数相同的工作进程协同处理各种事件。主管理进程负责工作进程的配置加载、启停等操作,工作进程负责处理具体请求。

多进程模型
图:多进程模型

进程间的资源都是独立的,每个工作进程处理多个连接,每个连接由一个工作进程全权处理,不需要进行进程切换,也就不会产生由进程切换引起的资源消耗问题。默认配置下,工作进程的数量与主机 CPU 核数相同,充分利用 CPU 和进程的亲缘性(affinity)将工作进程与 CPU 绑定,从而最大限度地发挥多核 CPU 的处理能力。

Nginx 主进程负责监听外部控制信号,通过频道机制将相关信号操作传递给工作进程,多个工作进程间通过共享内存来共享数据和信息。

1) 信号

信号(signal)又称软中断信号,可通过调用系统命令kill来发送信号实现进程通信。在 Nginx 系统中,主进程负责监听外部信号,实现对进程的热加载、平滑重启及安全关闭等操作的响应。

Nginx 支持的信号如下表所示。

信号命令行参数功能
TERM 或 INT stop 快速关闭 Nginx 服务
QUIT quit 安全关闭 Nginx 服务
HUP reload 热加载配置文件
WINCH   安全关闭工作进程
USR1 reopen  重新创建日志文件
USR2   平滑更新 Nginx 执行文件

在 Linux 系统下可以通过 kill 命令向 Nginx 进程发送信号指令,代码如下:

kill -HUP 'cat nginx.pid'

在 Linux 系统下也可以通过 nginx -s 命令行参数实现信号指令的发送,代码如下:

nginx -s reload

2) 频道

频道(channel)是 Nginx 主进程向工作进程传递信号操作的通信方式,用于将控制工作进程的信号操作传递给工作进程。通信频道的原理是应用 socketpair 方法使用本机的 socket 方式实现进程间的通信。主进程发送频道消息,工作进程接收频道消息并执行相应操作,如工作进程的创建与停止等。

创建工作进程时会将接收频道消息的套接字注册到对应的事件引擎(如 epoll)中,当事件引擎监听到主进程发送的频道消息时,就会触发回调函数通知工作进程执行响应操作。

3) 共享内存

共享内存是 Linux 操作系统下进程间的一种简单、高效的通信方式,其允许多个进程访问同一个内存地址,一个进程改变了内存中的内容后,其他进程都可以使用变更后的内容。Nginx 的多个进程间就是通过共享内存的方式共享数据的,主进程启动时创建共享内存,工作进程创建(fork 方式)完成后,所有的进程都开始使用共享内存。

用户可以在配置文件中配置共享内存名称和大小,定义不同的共享内存块供 Nginx 不同的功能使用,Nginx 解析完配置文件后,会将定义的共享内存通过 slab 机制进行内部统一划分和管理。

4) 进程调度

当工作进程被创建时,每个工作进程都继承了主进程的监听套接字(socket),所以所有工作进程的事件监听列表中会共享相同的监听套接字。但是多个工作进程间同一时间内只能由一个工作进程接收网络连接,为使多个工作进程间能够协调工作,Nginx 的工作进程有如下几种调度方式。

① 无调度模式

所有工作进程都会在连接事件被触发时争相与客户端建立连接,建立连接成功则开始处理客户端请求。无调度模式下所有进程都会争抢资源,但最终只有一个进程可以与客户端建立连接,对于系统而言这将在瞬间产生大量的资源消耗,这就是所谓的惊群现象。

② 互斥锁模式(accept_mutex)

互斥锁是一种声明机制,每个工作进程都会周期性地争抢互斥锁,一旦某个工作进程抢到互斥锁,就表示其拥有接收 HTTP 建立连接事件的处理权,并将当前进程的 socket 监听注入事件引擎(如 epoll)中,接收外部的连接事件。

其他工作进程只能继续处理已经建立连接的读写事件,并周期性地轮询查看互斥锁的状态,只有互斥锁被释放后工作进程才可以抢占互斥锁,获取 HTTP 建立连接事件的处理权。当工作进程最大连接数的 1/8 与该进程可用连接(free_connection)的差大于或等于 1 时,则放弃本轮争抢互斥锁的机会,不再接收新的连接请求,只处理已建立连接的读写事件。

互斥锁模式有效地避免了惊群现象,对于大量 HTTP 的短连接,该机制有效避免了因工作进程争抢事件处理权而产生的资源消耗。但对于大量启用长连接方式的 HTTP 连接,互斥锁模式会将压力集中在少数工作进程上,进而因工作进程负载不均而导致 QPS 下降。

③ 套接字分片(Socket Sharding)

套接字分片是由内核提供的一种分配机制,该机制允许每个工作进程都有一组相同的监听套接字。当有外部连接请求时,由内核决定哪个工作进程的套接字监听可以接收连接。这有效避免了惊群现象的发生,相比互斥锁机制提高了多核系统的性能。该功能需要在配置 listen 指令时启用 reuseport 参数。

Nginx 1.11.3 以后的版本中互斥锁模式默认是关闭的,由于 Nginx 的工作进程数量有限,且 Nginx 通常会在高并发场景下应用,很少有空闲的工作进程,所以惊群现象的影响不大。无调度模式因少了争抢互斥锁的处理,在高并发场景下可提高系统的响应能力。套接字分片模式则因为由 Linux 内核提供进程的调度机制,所以性能最好。

5) 事件驱动

事件驱动程序设计(Event-Driven Programming)是一种程序设计模型,这种模型的程序流程是由外部操作或消息交互事件触发的。其代码架构通常是预先设计一个事件循环方法,再由这个事件循环方法不断地检查当前要处理的信息,并根据相应的信息触发事件函数进行事件处理。通常未被处理的事件会放在事件队列中等待处理,而被事件函数处理的事件也会形成一个事件串,因此事件驱动模型的重点就在于事件处理的弹性和异步化。

为了确保操作系统运行的稳定性,Linux 系统将用于寻址操作的虚拟存储器分为内核空间和用户空间,所有硬件设备的操作都是在内核空间中实现的。当应用程序监听的网络接口接收到网络数据时,内核会先把数据保存在内核空间的缓冲区中,然后再由应用程序复制到用户空间进行处理。

Linux 操作系统下所有的设备都被看作文件来操作,所有的文件都通过文件描述符(File Descriptor,FD)集合进行映射管理。套接字是应用程序与 TCP/IP 协议通信的中间抽象层,也是一种特殊的文件,应用程序以文件描述符的方式对其进行读/写(I/O)、打开或关闭操作。每次对 socket 进行读操作都需要等待数据准备(数据被读取到内核缓冲区),然后再将数据从内核缓冲区复制到用户空间。

为了提高网络 I/O 操作的性能,操作系统设计了多种 I/O 网络模型。在 Linux 系统下,网络并发应用处理最常用的就是 I/O 多路复用模型,该模型是一种一个进程可以监视多个文件描述符的机制,一旦某个文件描述符就绪(数据准备就绪),进程就可以进行相应的读写操作。

epoll 模型是 Linux 系统下 I/O 多路复用模型里最高效的 I/O 事件处理模型,其最大并发连接数仅受内核的最大打开文件数限制,在 1GB 内存下可以监听 10 万个端口。epoll 模型监听的所有连接中,只有数据就绪的文件描述符才会调用应用进程、触发响应事件,从而提升数据处理效率。epoll 模型利用 mmap 映射内存加速与内核空间的消息传递,从而减少复制消耗。

作为 Web 服务器,Nginx 的基本功能是处理网络事件,快速从网络接口读写数据。Nginx 结合操作系统的特点,基于 I/O 多路复用模型的事件驱动程序设计,采用了异步非阻塞的事件循环方法响应处理套接字上的 accept 事件,使其在调用 accept 时不会长时间占用进程的 CPU 时间片,从而能够及时处理其他工作。通过事件驱动的异步非阻塞机制(见下图),使大量任务可以在工作进程中得到高效处理,以应对高并发的连接和请求。

异步非阻塞机制
图:异步非阻塞机制

2、工作流机制

Nginx 在处理客户端请求时,每个连接仅由一个进程进行处理,每个请求仅运行在一个工作流中,工作流被划分为多个阶段(见下图),请求在不同阶段由功能模块进行数据处理,处理结果异常或结束则将结果返回客户端,否则将进入下一阶段。工作进程维护工作流的执行,并通过工作流的状态推动工作流完成请求操作的闭环。

Nginx 工作流
图:Nginx 工作流

1) HTTP 请求处理阶段

HTTP 请求的处理过程可分为 11 个阶段,HTTP 请求处理阶段如下表所示。

阶段标识阶段说明
NGX_HTTP_POST_READ_PHASE 读取请求阶段,会进行 HTTP 请求头的读取和解析处理
NGX_HTTP_SERVER_REWRITE_PHASE server 重定向阶段,会在 URI进入location路由前修改 URI 的内容,进行重定向处理
NGX_HTTP_FIND_CONFIG_PHASE URI 匹配阶段,URI 进行 location 匹配处理,该阶段不支持外部模块引入
NGX_HTTP_REWRITE_PHASE rewrite 重写阶段,对 URI 执行 rewrite 规则修改处理
NGX_HTTP_POST_REWRITE_PHASE rewrite 重写结束阶段,对 rewrite 的结果执行跳转操作并进行次数验证,超过 10 次的则认为是死循环,返回 500 错误。该阶段不支持外部模块引入
NGX_HTTP_PREACCESS_PHASE 访问控制前阶段,进行连接数、单 IP 访问频率等的处理
NGX_HTTP_ACCESS_PHASE 访问控制阶段,进行用户认证、基于源 IP 的访问控制等处理
NGX_HTTP_POST_ACCESS_PHASE 访问控制结束阶段,对访问控制的结果进行处理,如向用户发送拒绝访问等响应。该阶段不支持外部模块引入
NGX_HTTP_PRECONTENT_PHASE 访问内容前阶段,对目标数据进行内容检验等操作。以前的版本称为 NGX_HTTP_TRY_FILES_PHASE,try_files 和 mirror 功能在这个阶段被执行
NGX_HTTP_CONTENT_PHASE 访问内容阶段,执行读取本地文件,返回响应内容等操作
NGX_HTTP_LOG_PHASE 日志记录阶段,处理完请求,进行日志记录

HTTP 请求处理阶段可以让每个模块仅在该阶段独立完成该阶段可实现的功能,而整个 HTTP 请求则是由多个功能模块共同处理完成的。

2) TCP/UDP 处理阶段

TCP/UDP 会话一共会经历 7 个处理阶段,每个 TCP/UDP 会话会自上而下地按照7个阶段进行流转处理,每个处理阶段的说明如下表所示。

阶段标识阶段说明
Post-accept 接收客户端连接请求后的第一阶段。模块 ngx_stream_realip_module 在这个阶段被调用
Pre-access 访问处理前阶段。模块 ngx_stream_limit_conn_module 在这个阶段被调用
Access  访问处理阶段。模块 ngx_stream_access_module 在这个阶段被调用
SSL TLS/SSL 处理阶段。模块 ngx_stream_ssl_module 在这个阶段被调用
Preread 数据预读阶段。将 TCP/UDP 会话数据的初始字节读入预读缓冲区,以允许 ngx_stream_ssl_preread_module 之类的模块在处理之前分析
Content  数据数据处理阶段。通常将 TCP/UDP 会话数据代理到上游服务器,或将模块 ngx_stream_return_module 指定的值返回给客户端
Log 记录客户端会话处理结果的最后阶段。模块 ngx_stream_log_module 在这个阶段被调用

Nginx 功能模块就是根据不同的功能目的,按照模块开发的加载约定嵌入不同的处理阶段的。

3、模块化

Nginx 一直秉持模块化的理念,其模块化的架构中,除了少量的主流程代码,都是模块。模块化的设计为 Nginx 提供了高度的可配置、可扩展、可定制特性。模块代码包括核心模块和功能模块两个部分:核心模块负责维护进程的运行、内存及事件的管理;功能模块则负责具体功能应用的实现,包括路由分配、内容过滤、网络及磁盘数据读写、代理转发、负载均衡等操作。Nginx 的高度抽象接口使用户很容易根据开发规范进行模块开发,有很多非常实用的第三方模块被广泛使用。

1) 模块分类

① 核心模块(core)

该模块提供了 Nginx 服务运行的基本功能,如 Nginx 的进程管理、CPU 亲缘性、内存管理、配置文件解析、日志等功能。

② 事件模块(event)

该模块负责进行连接处理,提供对不同操作系统的 I/O 网络模型支持和自动根据系统平台选择最有效 I/O 网络模型的方法。

③ HTTP模块(http)

该模块提供 HTTP 处理的核心功能和部分功能模块,HTTP 核心功能维护了 HTTP 多个阶段的工作流,并实现了对各种 HTTP 功能模块的管理和调用。

④ Mail模块(mail)

该模块实现邮件代理功能,代理 IMAP、POP3、SMTP 协议。

⑤ Stream模块(stream)

该模块提供 TCP/UDP 会话的代理和负载相关功能。

⑥ 第三方模块

第三方模块即非 Nginx 官方开发的功能模块,据统计,在开源社区发布的第三方模块已经达到 100 多个,其中 lua-resty、nginx-module-vts 等模块的使用度非常高。

2) 动态模块

Nginx 早期版本在进行模块编译时,通过编译配置(configure)选项--with_module--without-module决定要编译哪些模块,被选择的模块代码与 Nginx 核心代码被编译到同一个 Nginx 二进制文件中,Nginx 文件每次启动时都会加载所有的模块。这是一种静态加载模块的方式。随着第三方模块的增多和 Nginx Plus 的推出,模块在不重新编译 Nginx 的情况下被动态加载成为迫切的需求。

Nginx 从 1.9.11 版本开始支持动态加载模块的功能,该功能使 Nginx 可以在运行时有选择地加载 Nginx 官方或第三方模块。为使动态模块更易于使用,Nginx 官方还提供了 pkg-oss 工具,该工具可为任何动态模块创建可安装的动态模块包。

在 Nginx 开源版本的代码中,编译配置选项中含有=dynamic选项,表示支持动态模块加载。例如,模块 http_xslt_module 的动态模块编译配置选项示例如下。

./configure --with-http_xslt_module=dynamic

编译后,模块文件以 so 文件的形式独立存储于 Nginx 的 modules 文件夹中。动态模块编译如下图所示。

动态模块编译
图:动态模块编译

在不同编译配置选项下,Nginx 在编译时会因为某些结构字段未被使用而不会将其编译到代码中,因此就会出现不同编译配置选项的动态模块无法加载的问题。为解决这个问题,Nginx 在编译配置选项中提供了--with-compat选项,在进行 Nginx 及动态模块编译配置时如果使用了该选项,在相同版本的 Nginx 代码下,动态模块即使与 Nginx 执行文件的其他编译配置选项不同,也可以被 Nginx 执行文件加载。启用兼容参数编译的示例如下:

./configure --with-compat --with-http_xslt_module=dynamic

可以在配置文件中使用 load_module 指令加载动态模块,示例如下:

load_module "modules/ngx_http_xslt_filter_module.so";

 

Nginx编译安装

Nginx 是一款优秀的开源软件,支持在 FreeBSD、Linux、Windows、macOS 等多种操作系统平台下编译及运行。CentOS 拥有良好的系统结构和工具软件生态环境,是一款基于 Linux 的非常流行的发行版本。CentOS 源自 RedHat 企业版,按照 Linux 的开源协议编译而成,在稳定性及技术的可持续性方面完全可以代替 RedHat 企业版,因此我们选择将它 CentOS 作为本教程的操作系统环境。

1、编译环境准备

1) 操作系统的准备

Nginx 是一款优秀的开源软件,是运行在操作系统上的应用程序,因此 Nginx 的性能依赖于操作系统及其对底层硬件的管理机制,为了使 Nginx 在运行时发挥最大的性能,需要对操作系统的服务配置和参数做一些调整。系统服务配置可用如下方式实现。

① 系统服务安装

CentOS 可用最小化安装,安装完毕后,用如下命令补充工具。

yum -y install epel-release                           # 安装扩展工具包yum源
yum install net-tools wget nscd lsof            # 安装工具

② DNS 缓存

编辑 /etc/resolv.conf 配置 DNS 服务器,打开 NSCD 服务,缓存 DNS,提高域名解析响应速度。

systemctl start nscd.service                         # 启动NSCD服务
systemctl enable nscd.service

③ 修改文件打开数限制

操作系统默认单进程最大打开文件数为 1024,要想实现高并发,可以把单进程的文件打开数调整为 65536。

echo "* soft nofile 65536                             # *号表示所用用户
\* hard nofile 65536" >>/etc/security/limits.conf

2) Linux 内核参数

Linux 系统是通过 proc 文件系统实现访问内核内部数据结构及改变内核参数的,proc 文件系统是一个伪文件系统,通常挂载在 /proc 目录下,可以通过改变 /proc/sys 目录下文件中的值对内核参数进行修改。

/proc/sys 目录下的目录与内核参数类别如下表所示。

目录内核参数类别
fs 文件系统
kernel CPU、进程
net  网络
vm 内存

Linux 系统环境下,所有的设备都被看作文件来进行操作,建立的网络连接数同样受限于操作系统的最大打开文件数。最大打开文件数会是系统内存的 10%(以 KB 来计算),称为系统级限制,可以使用sysctl -a | grep fs.file-max命令查看系统级别的最大打开文件数。

同时,内核为了不让某个进程消耗掉所有的文件资源,也会对单个进程最大打开文件数做默认值处理,称之为用户级限制,默认值一般是 1024,使用ulimit -n命令可以查看用户级文件描述符的最大打开数。

Nginx 是一款 Web 服务器软件,通过系统层面的网络优化可以提升 HTTP 数据传输的效率。HTTP 协议是基于 TCP/IP 通信协议传递数据的,了解 TCP 建立连接(三次握手)及进行数据传输的机制是优化网络相关内核参数的基础。相关术语说明如下。
  • SYN:建立连接标识;
  • ACK:确认接收标识;
  • FIN:关闭连接标识;
  • seq:当前数据包编号,在实际传输过程中,数据会被拆成多个数据包传输给接收端,接收端再通过该编号将多个数据包拼接为完整的数据;
  • ack:确认号,为上一个数据包的编号 +1。

TCP 建立连接并进行数据传输的流程如下图所示,具体说明如下。
  • Client(下图中 ① 的位置)主动将请求报文(SYN=1,初始编号 seq=x)发送给 Server,将自己的状态更改为 SYN_SENT;
  • Server(下图中 ② 的位置)返回确认报文(SYN=1,ACK=1,确认号 ack=x+1,初始编号 seq=y),将自己的状态更改为 SYN_RCVD;
  • Client(下图中 ③ 的位置)返回确认报文(ACK=1,确认号 ack=y+1,编号 seq=x+1)给 Server,将自己的状态更改为 ESTABLISHED;
  • Server(下图中 ③ 的位置)收到确认报文后,将自己的状态更改为 ESTABLISHED,并与 Client 实现数据传输。

TCP建立连接
图:TCP建立连接

数据传输完毕后,TCP 关闭连接流程如下图所示,具体说明如下。
  • 发起端(下图中 ① 的位置)主动将连接关闭报文(FIN=1,编号 seq=u)发送给响应端,将自己的状态更改为 FIN_WAIT_1;
  • 响应端(下图中 ② 的位置)返回确认报文(ACK=1,确认号 ack=u+1,编号 seq=v)给发起端,将自己的状态更改为 CLOSE_WAIT;
  • 发起端(下图中 ② 的位置)收到确认报文后,将自己的状态更改为 FIN_WAIT_2,等待响应端发送连接释放报文;
  • 响应端(下图中 ③ 的位置)发送连接释放报文(FIN=1,ACK=1,编号 seq=w,确认号 ack=u+1)给发起端,将自己的状态更改为 LAST-ACK;
  • 发起端(下图中 ④ 的位置)收到连接释放报文后,发送确认报文(ACK=1,seq=u+1,ack=w+1)给响应端,将自己的状态更改为 TIME_WAIT,系统会在等待 2 倍MSL(Maximum Segment Lifetime)时间后关闭连接,释放资源;
  • 响应端(下图中 ④ 的位置)收到确认报文后,关闭连接,释放资源;
  • 关闭连接的动作不限于 Client 和 Server,不同角色都可作为发起端主动发起关闭连接的请求;
  • 有时发起端也可以在下图中 ① 发送 reset 报文给响应端,不经过 ②、③、④ 步骤立刻关闭连接。

TCP关闭连接
图:TCP关闭连接

CentOS 操作系统支持通过配置 sysctl.conf 文件中相关内核参数的方式实现对 proc/sys 目录下文件内容的调整。

2、Nginx源码编译

1) Nginx源码获取

Nginx 源码可通过官网直接下载,源码获取命令如下:

mkdir -p /opt/data/source
cd /opt/data/source
wget http://nginx.org/download/nginx-1.17.4.tar.gz
tar zxmf nginx-1.17.4.tar.gz

2) 编译配置参数

编译 Nginx 源码文件时,首先需要通过编译配置命令 configure 进行编译配置。编译配置命令 configure 的常用编译配置参数如下表所示。

编译配置参数默认值/默认编译状态参数说明
--prefix=PATH /usr/local 编译后代码的安装目录
--with-select_module 不编译 编译 select I/O 事件机制模块,在不支持 Nginx 默认 I/O 事件机制的操作系统下自动编译该模块
--without-select_module 编译 不编译 select I/O 事件机制模块
--with-poll_module 不编译 编译 poll/O 事件机制模块,在不支持 Nginx 默认 I/O 事件机制的操作系统下自动编译该模块
--without-poll_module 编译 不编译 poll I/O 事件机制模块
--with-threads 不编译  启用线程池支持
--with-file-aio 不编译 启用 AIO 支持
--with-http_ssl_module 不编译 编译 SSL 模块
--with-http_v2_module 不编译 编译 HTTP/2 模块
--with-http_realip_module 不编译 编译 HTTP 的真实 IP 模块
--with-http_addition_module 不编译 编译响应内容追加模块
--with-http_xslt_module 不编译 编译 XSLT 样式表转换模块
--with-http_xslt_module=dynamic -- 动态编译 XSLT 样式表转换 XML 响应模块
--with-http_image_filter_module 编译 编译图像转换模块
--with-http_image_filter_module=dynamic -- 动态编译图像转换模块
--with-http_geoip_module 编译 编译客户端 IP 解析城市地址模块
--with-http_geoip_module=dynamic -- 动态编译客户端 IP 解析城市地址模块
--with-http_sub_module 不编译 编译字符串替换模块
--with-http_dav_module 不编译 编译 WebDAV 协议支持模块
--with-http_flv_module 不编译  编译 FLV 文件伪流媒体服务器支持模块
--with-http_mp4_module 不编译 编译 MP4 文件伪流媒体服务器支持模块
--with-http_gunzip_module 不编译  编译 gzip 压缩兼容模块
--with-http_gzip_static_module 不编译    编译发送 gz 预压缩文件数据模块
--with-http_auth_request_module 不编译   编译请求认证模块
--with-http_random_index_module 不编译 编译随机首页模块
--with-http_secure_link_module 不编译 编译请求连接安全检查模块
--with-http_degradation_module  不编译  编译内存不足响应模块
--with-http_slice_module 不编译  编译文件切片模块
--with-http_stub_status_module 不编译  编译运行状态模块
--without-http_charset_module 编译 不编译字符集转换模块
--without-http_gzip__module 编译 不编译 gzip 方式压缩输出模块
--without-http_ssi_module 编译 不编译 SSI 支持模块
--without-http_userid_module 编译 不编译 cookie 操作模块
--without-http_access_module 编译 不编译基于 IP 的访问控制模块
--without-http_auth_basic_module 编译 不编译 HTTP 基本认证模块
--without-http_mirror_module 编译   不编译访问镜像模块
--without-http_autoindex_module 编译 不编译自动目录索引模块
--without-http_geo_module 编译 不编译根据客户 IP 创建变量模块
--without-http_map_module 编译 不编译变量映射模块
--without-http_split_clients_module 编译  不编译自定义客户请求分配模块
--without-http_referer_module 编译 不编译 referer 操作模块
--without-http_rewrite_module 编译 不编译 rewrite 规则模块
--without-http_proxy_module 编译 不编译代理功能模块
--without-http_fastcgi_module 编译 不编译 FastCGI 支持模块
--without-http_uwsgi_module  编译 不编译 uWSGI 支持模块
--without-http_scgi_module 编译 不编译 SCGI 支持模块
--without-http_grpc_module 编译 不编译 gRPC 支持模块
--without-http_memcached_module 编译 不编译 Memcached 服务访问模块
--without-http_limit_conn_module 编译 不编译并发连接数控制模块
--without-http_limit_req_module 编译 不编译单 IP 请求数限制模块
--without-http_empty_gif_module 编译 不编译空 GIF 图片模块
--without-http_browser_module 编译 不编译客户端浏览器识别模块
--without-http_upstream_hash_module 编译 不编译 hash 负载均衡算法模块
--without-http_upstream_ip_hash_module 编译 不编译 HTTP 协议 ip-hash 负载均衡模块
--without-http_upstream_least_conn_module 编译 不编译最少连接数算法负载均衡模块
--without-http_upstream_random_module 编译 不编译随机选择算法负载均衡模块
--without-http_upstream_keepalive_module 编译 不编译负载均衡后端长连接支持模块
--without-http_upstream_zone_module 编译 不编译负载均衡共享内存支持模块
--with-http_perl_module 不编译 编译 Perl 脚本支持模块
--with-http_perl_module=dynamic -- 动态编译 Perl 脚本支持模块
--with-stream 不编译 编译 TCP/UDP 代理模块
--with-stream=dynamic -- 动态编译 TCP/UDP 代理模块
--with-stream_ssl_module 不编译 编译 TCP/UDP 代理 SSL 支持模块
--with-stream_realip_module 不编译 编译 TCP/UDP 代理真实 IP 模块
--with-stream_geoip_module 不编译 编译地域信息解析模块
--with-stream_geoip_module=dynamic -- 动态编译地域信息解析模块
--with-stream_ssl_preread_module 不编译 编译 TCP/UDP 代理的 SSL 预处理模块

对于上表,有以下三点说明。
  • TCMalloc 是谷歌开源的一个内存管理分配器,优于 Glibc 的 malloc 内存管理分配器;
  • upstream 是被代理服务器组的 Nginx 内部标识,通常称为上游服务器;
  • 开启 pcre JIT 支持,可以提升处理正则表达式的速度。

如上表所示,具有带--with前缀的编译配置参数的模块都不会被默认编译,若要使用该功能模块,需要使用提供的编译配置参数进行编译配置。相反,具有带--without前缀的编译配置参数的模块都会被默认编译,如果不想使用某个功能模块,在进行编译配置时添加带有--without前缀的参数即可。此处只列出了常用功能的编译配置参数,也可以通过编译配置命令的帮助参数获得更多的编译配置参数。

./configure --help

3) 代码编译

安装编译工具及依赖库,脚本如下:

yum -y install gcc pcre-devel  zlib-devel openssl-devel libxml2-devel \
    libxslt-devel gd-devel GeoIP-devel jemalloc-devel libatomic_ops-devel \
    perl-devel  perl-ExtUtils-Embed

编译所有功能模块,脚本如下:

./configure \
    --with-threads \
    --with-file-aio \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_auth_request_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_slice_module \
    --with-http_stub_status_module \
    --with-stream=dynamic \
    --with-stream_ssl_module \
    --with-stream_realip_module \
    --with-stream_geoip_module=dynamic \
    --with-stream_ssl_preread_module \
    --with-compat  \
    --with-pcre-jit
    make && make install

此处只作为示例,可根据具体的需求灵活调整参数配置。编译后,默认安装目录为 /usr/local/nginx。

4) 添加第三方模块

Nginx 的功能是以模块方式存在的,同时也支持添加第三方开发的功能模块。执行 configure 时,通过--add-module=PATH参数指定第三方模块的代码路径,在 make 时就可以进行同步编译了。

添加第三方静态模块的方法如下:

./configure --add-module=../ngx_http_proxy_connect_module

添加第三方动态模块的方法如下:

./configure --add-dynamic-module=../ngx_http_proxy_connect_module \
    --with-compat

Tengine编译安装

Tengine 是由淘宝网发起的 Web 服务器项目。它在 Nginx 的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine 的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。

1、Tengine源码获取

Tengine 目前的版本是 Tengine 2.3.2,据其官网介绍,该版本继承了 Nginx 1.17.3 版本的所有特性,并兼容了 Nginx 的配置参数。Tengine 开发了很多自有模块,同时也集成了很多优秀的第三方模块,源代码可以通过 Tengine 的官方网站获得,获取命令如下:

mkdir -p /opt/data/source
cd /opt/data/source
wget http://tengine.taobao.org/download/tengine-2.3.2.tar.gz
tar zxmf tengine-2.3.2.tar.gz

2、编译配置参数

Tengine 比开源版 Nginx 增加了一些编译配置参数。Tengine 增加的编译配置参数如下表所示。

编译配置参数默认编译状态参数说明
--without-procs 编译 不编译 Procs 模块
--without-http_ssl_module 编译 不编译 HTTP SSL 支持模块
--without-http_stub_status_module 编译 不编译运行状态模块
--without-http-upstream-rbtree 编译 不使用红黑树(RBTree)方式进行上游服务器的查找
--with-http_lua_module 不编译 编译 Lua 脚本模块
--with-stream_sni 不编译 编译 TCP 代理时基于 SSL 的 SNI 支持
--with-jemalloc 不编译 启用 jemalloc 内存管理

对于上表,有以下两点说明。
  • jemalloc 是 Facebook 开源的一个内存管理分配器;
  • Nginx 原有编译配置参数参见《Nginx编译安装》一节。

3、代码编译

代码编译过程如下。

# 安装编译依赖
yum -y install gcc pcre-devel  zlib-devel openssl-devel libxml2-devel \
    libxslt-devel gd-devel GeoIP-devel yajl-devel jemalloc-devel \
    libatomic_ops-devel luajit luajit-devel perl-devel perl-ExtUtils-Embed

# 执行编译配置
./configure

# 代码编译及安装
make & make install

安装 Lua 或 LuaJIT 都可以,LuaJIT 是 Lua 的高效版本,推荐安装 LuaJIT。编译完成后,默认安装目录为 /usr/local/nginx。

4、Tengine 集成的模块

Tengine 自带的模块都存储在源码目录的 modules 文件中,用户可根据需要通过编译配置参数--add-module进行选择。模块说明如下表所示。

模块文件夹名模块说明
mod_dubbo 提供对与后端 Dubbo 服务体系对接的支持
ngx_backtrace_module 该模块可用于在工作进程异常退出时转储 Nginx 的回溯信息,如在接收到某些信号(sigabr、sigbus、sigfpe、sigill、sigiot、sigsegv)时。它非常便于调试
ngx_debug_pool 该模块可以提供 Nginx/Tengine 内存池占用内存的状态信息
ngx_debug_timer 该模块可以提供 Nginx/Tengine 定时器的状态信息
ngx_http_concat_module 类似于 Apache 中的 mod_concat 模块,用于将多个文件合并在一个响应报文中
ngx_http_footer_filter_module 在请求的响应末尾输出一段内容
ngx_http_lua_module Lua 脚本集成模块
ngx_http_proxy_connect_module 提供对 HTTP 的 CONNECT 方法的支持
ngx_http_reqstat_module 监控模块
ngx_http_slice_module 文件切片模块
ngx_http_sysguard_module 该模块监控内存(含 SWAP 分区)、CPU 和请求的响应时间,当某些监控指标达到设定的阈值时,跳转到指定的 URL
ngx_http_tfs_module 该模块实现了 TFS 的客户端,为 TFS 提供了 RESTful API。TFS 的全称是 Taobao File System,是淘宝的一个开源分布式文件系统
ngx_http_trim_filter_module 该模块用于删除 HTML、内嵌在 JavaScript 和 CSS 中的注释以及重复的空白符
ngx_http_upstream_check_module 该模块可以为 Tengine 提供主动式后端服务器健康检查功能
ngx_http_upstream_consistent_hash_module 该模块提供一致性 hash 作为负载均衡算法
ngx_http_upstream_dynamic_module 此模块可在运行时动态解析 upstream 中 Server 域名
ngx_http_upstream_dyups_module upstream 动态修改模块
ngx_http_upstream_session_sticky_module 该模块是一个负载均衡模块,通过 cookie 实现客户端与后端服务器的会话保持,在一定条件下可以保证同一个客户端访问的是同一个后端服务器
ngx_http_upstream_vnswrr_module 该模块是一个高效的负载均衡算法,同 Nginx 官方的加权轮询算法 SWRR 相比,VNSWRR 具备平滑、散列和高性能特征
ngx_http_user_agent_module 该模块可以分析 HTTP 消息头属性字段“User-Agent”中的内容
ngx_multi_upstream_module Dubbo 服务的多路复用连接支持模块
ngx_slab_stat 该模块可以提供 Nginx/Tengine 共享内存的状态信息

上述模块功能说明来源于源码中的说明文档,具体使用方法可参照源码中的说明文档。

Tengine 编译完成后,可使用nginx -m命令查看所有已经加载的模块,static 标识是静态编译的,shared 标识是动态编译的。

OpenResty编译安装

OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

OpenResty 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

1、OpenResty源码获取

OpenResty 当前源码版本是 1.15.8.2,集成的 Nginx 版本是 Nginx 1.15.8 版本。其源码可通过官网直接获取,获取命令如下:

mkdir -p /opt/data/source
cd /opt/data/source
wget https://openresty.org/download/openresty-1.15.8.2.tar.gz
tar zxmf openresty-1.15.8.2.tar.gz

2、编译配置参数

OpenResty 是 Nginx 的扩展版,其在编译配置参数上也进行了清晰的区分,分为 Nginx 原有编译配置参数和扩展编译配置参数两部分,扩展编译配置参数如下表所示。

编译参数参数说明
--with-debug 启用调试日志
--with-dtrace-probes 启用 DTrace USDT 探针
--without-http_echo_module 不编译可以在 Nginx 的 URL 访问中通过 echo 命令输出字符到用户的浏览器,一般用来调试输出信息,检测 Nginx 的可访问性、配置正确性
--without-http_xss_module 不编译跨站点脚本支持
--without-http_coolkit_module 不编译 Nginx 插件模块集合
--without-http_set_misc_module 不编译 rewrite 指令的扩展
--without-http_form_input_module 不编译 HTTP 请求扩展
--without-http_encrypted_session_module 不编译加密解密 Nginx 变量值的模块
--without-http_srcache_module 不编译缓存增强模块
--without-http_lua_module 不编译 Lua 脚本支持模块
--without-http_lua_upstream_module 不编译提供操作 upstream 服务器 Lua API 功能的模块
--without-http_headers_more_module 不编译 header 返回信息编辑模块
--without-http_array_var_module 不编译数组类型的 Nginx 变量模块
--without-http_memc_module 不编译 Memcache 扩展模块
--without-http_redis2_module 不编译 Redis 2.0 协议支持模块
--without-http_redis_module 不编译 Redis 缓存的模块
--without-http_rds_ison_module 不编译 DBD-Stream 格式转 JSON 格式模块
--without-http_rds_csv_module 不编译 DBD-Stream 格式转 cvs 格式模块
--without-stream_lua_module 不编译 TCP/UDP 负载 Lua 脚本支持模块
--without-ngx_devel_kit_module 不编译模块开发库
--with-http_iconv_module 编译编码字符转换模块
--without-http_ssl_module 不编译 HTTP SSL 支持模块
--without-stream_ssl_module 不编译 Stream SSL 支持模块
--with-http_drizzle_module 编译 MySQL 或 Drizzle 数据库访问模块
--with-http_postgres_module 编译 PostgreSQL 数据库访问模块
--without-lua_cjson 不编译快速解析 JSON 的一个 Lua C 模块
--without-lua_tablepool 不编译基于 LuaJIT 的 Lua 表格资源回收池支持库
--without-lua_redis_parser 不编译 Redis 数据解析为 Lua 数据结构模块
--without-lua_rds_parser 不编译 DBD-Stream 格式的数据解析为 Lua 数据结构模块
--without-lua_resty_dns 不编译 DNS(域名系统)解析器的 Lua 模块
--without-lua_resty_memcached 不编译 Memcached 客户端模块
--without-lua_resty_redis 不编译 Redis 客户端模块
--without-lua_resty_mysql 不编译 MySQL 客户端模块
--without-lua_resty_upload 不编译上传文件支持模块
--without-lua_resty_upstream_healthcheck 不编译基于 Lua 的服务器组健康监测模块
--without-lua_resty_string 不编译字符串实用程序和通用哈希函数的 Lua 库
--without-lua_resty_websocket 不编译基于 Lua 的非阻塞 WebSocket 服务/客户端
--without-lua_resty_limit_traffic 不编译 lua-resty-limit-traffic 支持库
--without-lua_resty_lock 不编译基于 Lua Nginx 模块的共享内存字典的简单非阻塞互斥锁 API
--without-lua_resty_lrucache 不编译 Lua-land LRU 缓存
--without-lua_resty_signal 不编译 lua-resty-signal 支持库,该支持库用于向 UNIX 进程发送信号
--without-lua_resty_shell 不编译 lua-resty-shell 支持库,该支持库可以实现通过 Lua 调用 shell 脚本的支持
--without-lua_resty_core 不编译使用 LuaJIT FFI 实现 Lua Nginx 模块提供的 Lua API
--with-luajit 编译 LuaJIT 2.1 解释器
--without-luajit-lua52 不编译基于 Lua 5.2 的 LuaJIT 扩展解释器

对于下表,有以下几点说明。
  • 扩展的功能模块都是被默认编译的,可以通过参数设置选择不编译;
  • DTrace 是基于系统底层的性能监控技术,可监测函数级别的内存、CPU 性能数据;
  • Nginx 原有编译配置参数参见《Nginx编译安装》一节。

3、代码编译

OpenResty 代码编译如下所示:

yum -y install gcc pcre-devel make zlib-devel openssl-devel libxml2-devel \
    libxslt-devel gd-devel GeoIP-devel libatomic_ops-devel luajit \
    luajit-devel perl-devel perl-ExtUtils-Embed

./configure \
    --with-threads \
    --with-file-aio \
    --with-http_ssl_module \
    --with-http_v2_module \
    --with-http_realip_module \
    --with-http_addition_module \
    --with-http_xslt_module=dynamic \
    --with-http_image_filter_module=dynamic \
    --with-http_geoip_module=dynamic \
    --with-http_sub_module \
    --with-http_dav_module \
    --with-http_flv_module \
    --with-http_mp4_module \
    --with-http_gunzip_module \
    --with-http_gzip_static_module \
    --with-http_auth_request_module \
    --with-http_random_index_module \
    --with-http_secure_link_module \
    --with-http_degradation_module \
    --with-http_slice_module \
    --with-http_stub_status_module \
    --with-stream=dynamic \
    --with-stream_ssl_module \
    --with-stream_realip_module \
    --with-stream_geoip_module=dynamic \
    --with-stream_ssl_preread_module

gmake && gmake install

编译完成后,默认安装目录为 /usr/local/openresty。Nginx 安装在 /usr/local/openresty/nginx 目录下。

4、OpenResty集成的模块

在 OpenResty 中使用 Lua 是非常方便的,既可以在配置文件中通过 OpenResty 定义的指令区域直接编写 Lua 语法命令,也可以通过引用方式调用外部 Lua 脚本文件。

OpenResty 提供了很多非常实用的 Nginx 模块和 Lua 支持库,模块说明如下表所示。

模块文件夹名模块说明
array-var-nginx-module 一个 Nginx 模块,向 Nginx 配置文件添加对数组类型变量的支持
drizzle-nginx-module 实现与 MySQL 或 Drizzle 数据库的代理连接支持
echo-nginx-module 该模块封装了大量 Nginx 内部 API,用于流式输入和输出、并行/顺序子请求、定时器和休眠,以及各种元数据访问
encrypted-session-nginx-module 该模块为基于 Mac 系统的 AES-256,为 Nginx 变量提供加密和解密支持
form-input-nginx-module 该模块读取编码为 application/x-www-form-urlencoded 的 HTTP POST 和 PUT 请求体,并将请求体中的参数解析为 Nginx 变量
headers-more-nginx-module 该模块允许用户添加、设置或清除指定响应或请求头
iconv-nginx-module 该模块使用 libiconv 转换不同编码的字符,它既可以处理 Nginx 变量,也可以作为输出过滤器处理响应体
lua-cjson 可为 Lua 提供快速的 JSON 解析和编码支持
lua-rds-parser 该 Lua 库可以用于将由 Drizzle Nginx 模块和 Postgres Nginx 模块生成的 Resty-DBD-Stream 流格式的数据解析为 Lua 数据结构
lua-redis-parser 该 Lua 库实现了一个精简而快速的将 Redis Raw 格式解析为 Lua 数据结构的解析器,同时还构造了一个 Redis Raw 格式请求函数
lua-resty-core 使用 LuaJIT FFI 实现的 Lua Nginx 模块 Lua API
lua-resty-dns 基于 Lua Nginx 模块 cosocket API 的非阻塞域名解析支持库
lua-resty-limit-traffic 用于限制和控制流量的 Lua 支持库
lua-resty-lock 该 Lua 库基于 Lua Nginx 模块的共享内存字典实现了一个简单的非阻塞互斥锁 API,主要用于消除 dog-pile effects
lua-resty-lrucache Lua-land LRU 缓存支持库
lua-resty-memcached 基于 Lua Nginx 模块 cosocket API 的 Lua Memcached 客户端驱动支持库
lua-resty-mysql 基于 Lua Nginx 模块 cosocket API 的 Lua MySQL 客户端驱动支持库
lua-resty-redis 基于 Lua Nginx 模块 cosocket API 的 Lua Redis 客户端驱动支持库
lua-resty-shell 该支持库可以实现通过 Lua 调用 shell 脚本的支持
lua-resty-signal 该支持库用于向 UNIX 进程发送信号
lua-resty-string 为 Lua Nginx 模块提供字符串实用程序和公共哈希函数的 Lua 库
lua-resty-upload 基于 Lua Nginx 模块 cosocket API 的 HTTP 文件上传流式阅读器和解析器支持库
lua-resty-upstream-healthcheck Nginx 上游服务器运行状况的主动检查支持库
lua-resty-websocket 基于 Lua Nginx 模块 cosocket API 的非阻塞 WebSocket 服务器和非阻塞 WebSocket 客户端支持库
lua-tablepool 基于 LuaJIT 的 Lua 表格资源回收池支持库
memc-nginx-module 该模块扩展了标准的 Memcached 模块,几乎支持全部 Memcached ASCII 协议,它允许用户为 Memcached 服务器定义一个 Rest 接口,
通过子请求或独立的伪请求在 Nginx 服务器中以非常高效的方式访问 Memcached 服务器
ngx_coolkit 该模块是一些小的、有用的 Nginx 附加组件集合
ngx_devel_kit 该模块旨在以其为基础为其他模块提供扩展 Nginx Web 服务器的核心功能支持
ngx_lua_upstream 该模块向 Lua Nginx 模块公开了一个 Lua API,用于 Nginx 上游服务器的操作
ngx_stream_lua Nginx Stream 代理的 Lua 脚本支持模块
opm OpenResty 的包管理工具
rds-csv-nginx-module 该 Lua 库可以用于将由 Drizzle Nginx 模块和 Postgres Nginx 模块生成的 Resty-DBD-Stream 流格式的数据解析为 CSV 数据结构
rds-json-nginx-module 该 Lua 库可以用于将由 Drizzle Nginx 模块生成的 Resty-DBD-Stream 流格式的数据解析为 JSON 数据结构
redis2-nginx-module 该模块使 Nginx 以非阻塞的方式与 Redis 2.x 服务器通信,已经完整地实现 Redis 2.0 协议支持,包括 Redis pipelining 支持
redis-nginx-module 该模块使 Nginx 以非阻塞的方式与 Redis 2.x 服务器通信,且与标准的 ngx_memcached 模块有相似的接口,但是只支持 Redis 的 GET 和 SELECT 命令
resty-cli OpenResty 的命令行实用程序的集合
set-misc-nginx-module 该模块是 Nginx 的 rewrite 模块的功能增强模块,提供了更多的指令
srcache-nginx-module 该模块为 Nginx 提供了一个透明的缓存层,可以用在 Nginx 的任意位置
xss-nginx-module 该模块提供了跨站点 AJAX 支持,目前只支持跨站点的 GET 方法

各模块的详细功能和使用方法请参见官方网站的相关介绍。

Nginx配置简述

前面我们已经成功的将 Nginx 安装到了我们的系统中,接下来还需要将 Nginx 简单的配置一下。

1、环境配置

Nginx 编译安装成功后,为了便于操作维护,建议把 Nginx 执行文件的路径添加到环境变量中,可以通过如下命令完成。

cat >/etc/profile.d/nginx.sh << EOF
PATH=$PATH:/usr/local/nginx/sbin
EOF
source /etc/profile

对于 OpenResty,为了保持与 Nginx 的维护一致性,可以将 Nginx 目录软连接到 /usr/local 目录下。

ln -s /usr/local/openresty/nginx /usr/local/nginx

在 CentOS 操作系统中,配置文件通常放在 /etc 目录下,建议将 Nginx 的 conf 目录软连接到 /etc 目录下。

ln -s /usr/local/nginx/conf /etc/nginx

2、命令行参数

Nginx 执行文件的命令行参数可以通过 -h 参数获取,Nginx 命令行参数如下:

Usage: nginx [-?hvVtTq] [-s signal] [-c filename] [-p prefix] [-g directives]

Options:
-?,-h         : this help
-v            : show version and exit
-V            : show version and configure options then exit
-t            : test configuration and exit
-T            : test configuration, dump it and exit
-q            : suppress non-error messages during configuration testing
-s signal     : send signal to a master process: stop, quit, reopen, reload
-p prefix     : set prefix path (default: /usr/local/openresty/nginx/)
-c filename   : set configuration file (default: conf/nginx.conf)
-g directives : set global directives out of configuration file

上述代码中的主要参数解释说明如下。
  • -v 参数:显示 Nginx 执行文件的版本信息;
  • -V 参数:显示 Nginx 执行文件的版本信息和编译配置参数;
  • -t 参数:进行配置文件语法检查,测试配置文件的有效性;
  • -T 参数:进行配置文件语法检查,测试配置文件的有效性,同时输出所有有效配置内容;
  • -q 参数:在测试配置文件有效性时,不输出非错误信息;
  • -s 参数:发送信号给 Nginx 主进程,信号可以为以下 4 个;
  • stop:快速关闭;
  • quit:正常关闭;
  • reopen:重新打开日志文件;
  • reload:重新加载配置文件,启动一个加载新配置文件的 Worker Process,正常关闭一个加载旧配置文件的 Worker Process;
  • -p 参数:指定 Nginx 的执行目录,默认为 configure 时的安装目录,通常为 /usr/local/nginx;
  • -c 参数:指定 nginx.conf 文件的位置,默认为 conf/nginx.conf;
  • -g 参数:外部指定配置文件中的全局指令。

应用示例如下:

nginx -t                                               # 执行配置文件检测
nginx -t -q                                          # 执行配置文件检测,且只输出错误信息
nginx -s stop                                      # 快速停止Nginx
nginx -s quit                                       # 正常关闭Nginx
nginx -s reopen                                  # 重新打开日志文件
nginx -s reload                                   # 重新加载配置文件
nginx -p /usr/local/newnginx            # 指定Nginx的执行目录
nginx -c /etc/nginx/nginx.conf          # 指定nginx.conf文件的位置
# 外部指定pid和worker_processes配置指令参数
nginx -g "pid /var/run/nginx.pid; worker_processes 'sysctl -n hw.ncpu';"

Tengine 的扩展命令如下:

nginx -m                                 # 列出所有的编译模块
nginx -l                                   # 列出支持的所有指令

3、注册系统服务

CentOS 系统环境中使用 systemd 进行系统和服务管理,可以按需守护进程,并通过 systemctl 命令进行 systemd 的监测和控制。为了方便 Nginx 应用进程的维护和管理,此处把 Nginx 注册成系统服务,由 systemd 进行服务管理,命令如下。

cat >/usr/lib/systemd/system/nginx.service <<EOF
[Unit]                                                                                     # 记录service文件的通用信息
Description=The Nginx HTTP and reverse proxy server      # Nginx服务描述信息
After=network.target remote-fs.target nss-lookup.target  # Nginx服务启动依赖,在指定服务之后启动

[Service]                                                                                        # 记录service文件的service信息
Type=forking                                                                              # 标准UNIX Daemon使用的启动方式
PIDFile=/run/nginx.pid                                                                 # Nginx服务的pid文件位置
ExecStartPre=/usr/bin/rm -f /run/nginx.pid                                 # Nginx服务启动前删除旧的pid文件
ExecStartPre=/usr/local/nginx/sbin/nginx -t -q                           # Nginx服务启动前执行配置文件检测
ExecStart=/usr/local/nginx/sbin/nginx -g "pid /run/nginx.pid;"  # 启动Nginx服务
ExecReload=/usr/local/nginx/sbin/nginx -t -q                             # Nginx服务重启前执行配置文件检测
ExecReload=/usr/local/nginx/sbin/nginx -s reload -g "pid /run/nginx.pid;" 
                                                                                                      # 重启Nginx服务
ExecStop=/bin/kill -s HUP $MAINPID                                          # 关闭Nginx服务
KillSignal=SIGQUIT
TimeoutStopSec=5
KillMode=process
PrivateTmp=true

[Install]                                                           # 记录service文件的安装信息
WantedBy=multi-user.target                        # 多用户环境下启用
EOF

systemctl enable nginx                            # 将Nginx服务注册为系统启动后自动启动
systemctl start nginx                               # 启动Nginx服务命令
systemctl reload nginx                            # reload Nginx服务命令
systemctl stop nginx                               # stop Nginx服务命令
systemctl status nginx                            # 查看Nginx服务运行状态命令

Nginx的Docker容器化配置

Docker 是一款基于Go语言开发的开源应用容器引擎,Docker 可以让用户将需要运行的应用服务和依赖环境打包在一个小体积的应用容器中,被打包的容器可以移植到任意可运行 Docker 环境的操作系统中,极大地缩短了应用服务编译和部署所需的时间。Docker 的虚拟化机制也使得在不同操作系统环境下编译的应用服务都可运行在同一Docker 宿主机中。

Docker 中有两个基本概念:镜像(Image)和容器(Container)。Docker 使用 AUFS 文件系统进行文件管理,这种文件系统的文件是分层叠加存储的,镜像是存储在只读层的文件,而运行的容器则是镜像运行的实例,它的实例文件存储在可写层中,所以通常需要先通过 Docker 命令制作镜像,然后再通过 Docker 编排命令将镜像运行成容器。

1、Docker环境安装

Docker 的虚拟化机制是基于操作系统的进程级别虚拟化技术,所以 Docker 也可以安装在其他虚拟机中。在物理机或云环境的 CentOS 7 环境下均可通过 yum 命令实现快速安装,安装命令如下。

# 安装yum工具
yum install -y yum-utils

# 安装Docker官方yum源
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# 安装Docker及docker-compose应用
yum install -y docker-ce docker-compose

# 设置Docker服务开机自启动
systemctl enable docker

# 启动Docker服务
systemctl start docker

2、Dockerfile常用命令及编写

Dockerfile 是按照 Docker Build 语法约定的顺序结构规则脚本文件。通过 Dockerfile 的编写可以实现 Docker 镜像的自动化制作,本节所介绍的编译过程均可被编写在 Dockerfile 中,使用 Docker 命令打包为 Nginx 的 Docker 镜像。

Dockerfile 常用命令如下。

1) FROM 用于指定构建当前镜像的基础镜像名,使用方法如下。

FROM centos

2) MAINTAINER 用于填写作者声明的描述信息,使用方法如下。

MAINTAINER Nginx Dockerfile Write by John.Wang

3) ADD 命令会向 Image 中添加文件,支持文件、目录、URL 的源,使用方法如下。

ADD /tmp/init_nginx.sh /usr/local/nginx/sbin/

4) COPY 用于向镜像内复制文件夹,使用方法如下。

COPY . /tmp

5) ENV 设置 Container 启动后的环境变量,使用方法如下。

ENV PATH $PATH:/usr/local/nginx/sbin

6) EXPOSE 设置 Container 启动后对外开放的端口,它只相当于一个防火墙开放端口的概念,与实际运行的服务无关,使用方法如下。

EXPOSE 8080

7) RUN 用于在制作 Image 时执行指定的脚本或 shell 命令,使用方法如下。

RUM yum -y install net-tools

8) USER 设置运行 Image 或 Container 的系统用户,使用方法如下。

USER nginx:nginx

9) VOLUME 定义 Image 挂载点,该挂载点可被其他 Container 使用,且目录中的内容是共享的,将会同步更新,使用方法如下。

VOLUME ["/data1","/data2"]

10) WORKDIR 设置 CMD 参数指定命令的运行目录,使用方法如下。

WORKDIR ~/

11) CMD 命令是设定于 Container 启动后执行的命令,可被外部docker run命令参数覆盖,使用方法如下。

CMD "Hello Nginx"

12) ENTRYPOINT 命令是设定于 Container 启动后执行的命令,不可被外部docker run命令参数覆盖。

ENTRYPOINT /usr/local/nginx/sbin/init_nginx.sh

现在,可以按照 Dockerfile 的命令格式编写 Dockerfile 了,基础镜像选用 CentOS 7,Nginx 选用 Nginx 的扩展版本 OpenResty 1.15.8.2。

Nginx 镜像 Dockerfile 脚本如下:

FROM centos:centos7
MAINTAINER Nginx Dockerfile Write by John.Wang
RUN yum -y install epel-release && yum -y install wget gcc make pcre-devel \
    zlib-devel openssl-devel libxml2-devel libxslt-devel luajit GeoIP-devel \
    gd-devel libatomic_ops-devel luajit-devel perl-devel perl-ExtUtils-Embed

RUN cd /tmp && wget https://openresty.org/download/openresty-1.15.8.2.tar.gz  && \
    tar zxmf openresty-1.15.8.2.tar.gz && \
    cd openresty-1.15.8.2 && \
    ./configure \
        --with-threads \
        --with-file-aio \
        --with-http_ssl_module \
        --with-http_v2_module \
        --with-http_realip_module \
        --with-http_addition_module \
        --with-http_xslt_module=dynamic \
        --with-http_image_filter_module=dynamic \
        --with-http_geoip_module=dynamic \
        --with-http_sub_module \
        --with-http_dav_module \
        --with-http_flv_module \
        --with-http_mp4_module \
        --with-http_gunzip_module \
        --with-http_gzip_static_module \
        --with-http_auth_request_module \
        --with-http_random_index_module \
        --with-http_secure_link_module \
        --with-http_degradation_module \
        --with-http_slice_module \
        --with-http_stub_status_module \
        --with-stream=dynamic \
        --with-stream_ssl_module \
        --with-stream_realip_module \
        --with-stream_geoip_module=dynamic \
        --with-libatomic \
        --with-pcre-jit \
        --with-stream_ssl_preread_module && \
    gmake && gmake install
ENV PATH $PATH:/usr/local/nginx/sbin
RUN ln -s /usr/local/openresty/nginx /usr/local/nginx
RUN ln -sf /dev/stdout /usr/local/nginx/logs/access.log &&\
    ln -sf /dev/stderr /usr/local/nginx/logs/error.log
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]

在 Dockerfile 文件的同一目录下,执行如下命令构建 Nginx 的 Dokcer 镜像。

docker build -t nginx:v1.0 .

在脚本执行结束后,当尾行出现“Successfully tagged nginx:v1.0”时表示 Dokcer 镜像已经构建成功,可以通过 Docker 命令 docker images 查看镜像是否已经存在于本地的镜像仓库中,查询结果如下图所示。

本地镜像仓库中的所有 Docker 镜像
图:本地镜像仓库中的所有 Docker 镜像

3、Nginx Docker运行

Docker 镜像在 AUFS 文件系统中是只读的,需要通过docker run命令以容器方式运行,脚本如下:

docker run --name nginx -p 80:80 -d nginx:v1.0
docker ps -a
CONTAINER ID        IMAGE               COMMAND                  CREATED
STATUS                    PORTS               NAMES
26ffd54950e8          nginx:v1.0          "nginx -g 'daemon of…"  7 seconds ago
Up 7 seconds          0.0.0.0:80->80/tcp  nginx

通过 curl 命令访问本地 80 端口,可以返回 OpenResty 的提示信息。

Docker 容器如果被移除,所有的修改文件同样会被删除,为了把变更的配置保存下来,需要把配置文件目录复制出来进行持久化,所以需要通过卷挂载的方式实现配置的使用和维护,脚本如下:

mkdir -p /opt/data/apps/nginx/
docker cp nginx:/usr/local/nginx/conf /opt/data/apps/nginx/
docker stop nginx
docker rm nginx
docker run --name nginx -h nginx -p 80:80 -v
/opt/data/apps/nginx/conf:/usr/local/nginx/conf -d nginx:v1.0

如下图所示,Docker 容器已经把本地目录挂载到容器中。

目录挂载
图:目录挂载

在使用 docker run 命令时,每次都需要使用很多参数,为了便于维护,可以用 Docker-Compose 工具进行容器编排,Docker-Compose 是使用基于 YAML 语法的脚本配置文件来实现容器的运行管理的。Nginx 的 docker-compose.yaml 脚本文件如下:

nginx:
    image: nginx:v1.0
    restart: always
    container_name: nginx
    hostname: 'nginx'
    ports:
        - 80:80
    volumes:
        - '/opt/data/apps/nginx/conf:/usr/local/nginx/conf'

posted @ 2022-04-25 21:23  hanease  阅读(190)  评论(0编辑  收藏  举报