一、web服务基础介绍
Web 服务基础介绍
正常情况下的单次web服务访问流程:
Apache 经典的 Web 服务端
Apache起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发
目前经历了两大版本分别是1.X和2.X
其可以通过编译安装实现特定的功能
1.Apache prefork 模型 预派生模式,有一个主控制进程,然后生成多个子进程,使用select模型,最大并发1024 每个子进程有一个独立的线程响应用户请求 相对比较占用内存,但是比较稳定,可以设置最大和最小进程数 是最古老的一种模式,也是最稳定的模式,适用于访问量不是很大的场景 优点:稳定
缺点:每个用户请求需要对应开启一个进程,占用资源较多,并发性差,不适用于高并发场景
Apache worker 模型
一种多进程和多线程混合的模型
有一个控制进程,启动多个子进程
每个子进程里面包含固定的线程
使用线程程来处理请求
当线程不够使用的时候会再启动一个新的子进程,然后在进程里面再启动线程处理请求
由于其使用了线程处理请求,因此可以承受更高的并发
优点:相比prefork 占用的内存较少,可以同时处理更多的请求
缺点:使用keepalive的长连接方式,某个线程会一直被占据,即使没有传输数据,也需要一直等待到超 时才会被释放。如果过多的线程,被这样占据,也会导致在高并发场景下的无服务线程可用(该问题在 prefork模式下,同样会发生)
Apache event模型
Apache中最新的模式,2012年发布的apache 2.4.X系列正式支持event 模型,属于事件驱动模型(epoll)
每个进程响应多个请求,在现在版本里的已经是稳定可用的模式
它和worker模式很像,最大的区别在于,它解决了keepalive场景下长期被占用的线程的资源浪费问题 (某些线程因为被keepalive,空挂在哪里等待,中间几乎没有请求过来,甚至等到超时)
event MPM中,会有一个专门的线程来管理这些keepalive类型的线程
当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放。这样增强了高并发场 景下的请求处理能力
优点:单线程响应多请求,占据更少的内存,高并发下表现更优秀,会有一个专门的线程来管理keepalive类型的线程,当有真实请求过来的时候,将请求传递给服务线程,执行完毕后,又允许它释放
缺点:没有线程安全控制
Nginx-高性能的 Web 服务端
阻塞型 I/O 模型(blocking IO)
- 阻塞IO模型是最简单的I/O模型,用户线程在内核进行IO操作时被阻塞
- 用户线程通过系统调用read发起I/O读操作,由用户空间转到内核空间。内核等到数据包到达后,然 后将接收的数据拷贝到用户空间,完成read操作
- 用户需要等待read将数据读取到buffer后,才继续处理接收的数据。整个I/O请求的过程中,用户线 程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够
优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用 CPU 资源
缺点:每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销 较apache 的preforck使用的是这种模式。
同步阻塞:程序向内核发送I/O请求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回,则进 程将一直等待并不再接受新的请求,并由进程轮询查看I/O是否完成,完成后进程将I/O结果返回给 Client,在IO没有返回期间进程不能接受其他客户的请求,而且是有进程自己去查看I/O是否完成,这种 方式简单,但是比较慢,用的比较少。
非阻塞型 I/O 模型 (nonblocking IO
用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据 到达后,才真正读取到数据,继续执行。即 “轮询”机制存在两个问题:如果有大量文件描述符都要等, 那么就得一个一个的read。这会带来大量的Context Switch(read是系统调用,每调用一次就得在用户 态和核心态切换一次)。轮询的时间不好把握。这里是要猜多久之后数据才能到。等待时间设的太长, 程序响应延迟就过大;设的太短,就会造成过于频繁的重试,干耗CPU而已,是比较浪费CPU的方式,一 般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
非阻塞:程序向内核发送请I/O求后一直等待内核响应,如果内核处理请求的IO操作不能立即返回IO结 果,进程将不再等待,而且继续处理其他请求,但是仍然需要进程隔一段时间就要查看内核I/O是否完 成。
查看上图可知,在设置连接为非阻塞时,当应用进程系统调用 recvfrom 没有数据返回时,内核会立即返 回一个 EWOULDBLOCK 错误,而不会一直阻塞到数据准备好。如上图在第四次调用时有一个数据报准 备好了,所以这时数据会被复制到 应用进程缓冲区 ,于是 recvfrom 成功返回数据
当一个应用进程这样循环调用 recvfrom 时,称之为轮询 polling 。这么做往往会耗费大量CPU时间,实 际使用很少
多路复用 I/O 型(I/O multiplexing)
上面的模型中,每一个文件描述符对应的IO是由一个线程监控和处理 多路复用IO指一个线程可以同时(实际是交替实现,即并发完成)监控和处理多个文件描述符对应各自 的IO,即复用同一个线程 一个线程之所以能实现同时处理多个IO,是因为这个线程调用了内核中的SELECT,POLL或EPOLL等系统调 用,从而实现多路复用IO
I/O multiplexing 主要包括:select,poll,epoll三种系统调用,select/poll/epoll的好处就在于单个 process就可以同时处理多个网络连接的IO。
它的基本原理就是select/poll/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数 据到达了,就通知用户进程。
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket, 当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从 kernel拷贝到用户进程。
Apache prefork是此模式的select,worker是poll模式。
IO多路复用(IO Multiplexing) :是一种机制,程序注册一组socket文件描述符给操作系统,表示“我要 监视这些fd是否有IO事件发生,有了就告诉程序处理”IO多路复用一般和NIO一起使用的。NIO和IO多路 复用是相对独立的。NIO仅仅是指IO API总是能立刻返回,不会被Blocking;而IO多路复用仅仅是操作系统 提供的一种便利的通知机制。操作系统并不会强制这俩必须得一起用,可以只用IO多路复用 + BIO,这时 还是当前线程被卡住。IO多路复用和NIO是要配合一起使用才有
实际意义
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,就通知该进程多个连接共用一 个等待机制,本模型会阻塞进程,但是进程是阻塞在select或者poll这两个系统调用上,而不是阻塞在真 正的IO操作上用户首先将需要进行IO操作添加到select中,同时等待select系统调用返回。当数据到达 时,IO被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行从流程上来看,使用 select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视IO,以及调用select函数的 额外操作,效率更差。并且阻塞了两次,但是第一次阻塞在select上时,select可以监控多个IO上是否已 有IO操作准备就绪,即可达到在同一个线程内同时处理多个IO请求的目的。而不像阻塞IO那种,一次只 能监控一个IO虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select 函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只是注册自己需要的IO请求,然 后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率IO多路复用是最常使用的IO 模型,但是其异步程度还不够“彻底”,因它使用了会阻塞线程的select系统调用。因此IO多路复用只能称 为异步阻塞IO模型,而非真正的异步IO
优缺点
- 优点:可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述 符一个线程),这样可以大大节省系统资源
- 缺点:当连接数较少时效率相比多线程+阻塞 I/O 模型效率较低,可能延迟更大,因为单个连接处理 需要 2 次系统调用,占用时间会有增加
IO多路复用适用如下场合:
- 当客户端处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用
- 当一个客户端同时处理多个套接字时,此情况可能的但很少出现 当一个服务器既要处理监听套接字,又要处理已连接套接字,一般也要用到I/O复用
- 当一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用
- 当一个服务器要处理多个服务或多个协议,一般要使用I/O复用
信号驱动式 I/O 模型 (signal-driven IO)
信号驱动I/O的意思就是进程现在不用傻等着,也不用去轮询。而是让内核在数据就绪时,发送信号通知 进程。
调用的步骤是,通过系统调用 sigaction ,并注册一个信号处理的回调函数,该调用会立即返回,然后主 程序可以继续向下执行,当有I/O操作准备就绪,即内核数据就绪时,内核会为该进程产生一个 SIGIO信 号,并回调注册的信号回调函数,这样就可以在信号回调函数中系统调用 recvfrom 获取数据,将用户进 程所需要的数据从内核空间拷贝到用户空间
此模型的优势在于等待数据报到达期间进程不被阻塞。用户主程序可以继续执行,只要等待来自信号处 理函数的通知。
在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续 运行并不阻塞
在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续 运行并不阻塞 当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。
优 点:线程并没有在等待数据时被阻塞,内核直接返回调用接收信号,不影响进程继续处理其他请求因此 可以提高资源的利用率
缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知 异步阻塞:程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核收到进程 请求后 进行的IO如果不能立即返回,就由内核等待结果,直到IO完成后内核再通知进程
异步 I/O 模型 (asynchronous IO)
异步I/O 与 信号驱动I/O最大区别在于,信号驱动是内核通知用户进程何时开始一个I/O操作,而异步I/O 是由内核通知用户进程I/O操作何时完成,两者有本质区别,相当于不用去饭店场吃饭,直接点个外卖,把 等待上菜的时间也给省了
相对于同步I/O,异步I/O不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备 好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直 接复制数据给进程,然后从内核向进程发送通知。IO两个阶段,进程都是非阻塞的。
信号驱动IO当内核通知触发信号处理程序时,信号处理程序还需要阻塞在从内核空间缓冲区拷贝数据到 用户空间缓冲区这个阶段,而异步IO直接是在第二个阶段完成后,内核直接通知用户线程可以进行后续 操作了
优点:异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠
缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的 异步 I/O,在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编 程时以 IO 复用模型模式+多线程任务的架构基本可以满足需求
Linux提供了AIO库函数实现异步,但是用的很少。目前有很多开源的异步IO库,例如libevent、libev、 libuv。
异步非阻塞:程序进程向内核发送IO调用后,不用等待内核响应,可以继续接受其他请求,内核调用的 IO如果不能立即返回,内核会继续处理其他事物,直到IO完成后将结果通知给内核,内核在将IO完成的 结果返回给进程,期间进程可以接受新的请求,内核也可以处理新的事物,因此相互不影响,可以实现 较大的同时并实现较高的IO复用,因此异步非阻塞使用最多的一种通信方式。
五种io对比:
每种I/O模型都有其适用的场景和优缺点,在选择合适的I/O模型时需要考虑应用的需求和性能目标。在现代Web开发中,异步I/O模型越来越受欢迎,因为它能够在不牺牲性能的情况下支持高并发这五种 I/O 模型中,越往后,阻塞越少,理论上效率也是最优前四种属于同步 I/O,因为其中真正的 I/O 操作(recvfrom)将阻塞进程/线程,只有异步 I/O 模型才与 POSIX 定义的异步 I/O 相匹配
零拷贝
零拷贝(Zero-Copy)是一种优化技术,它通过减少数据在不同内存区域之间的复制次数来提高系统的性能。在传统的I/O模型中,数据通常需要经历多次拷贝才能从源头到达目的地。这些拷贝不仅消耗CPU资源,还会增加上下文切换的开销,影响整体的系统性能。
下面是如何通过零拷贝技术解决传统I/O模型中的问题:
### 传统I/O模型的问题
1. **多次数据拷贝**:
- 数据从磁盘读入时,通常先拷贝到内核空间的缓冲区;
- 然后从内核空间的缓冲区拷贝到用户空间的缓冲区;
- 如果数据还需要通过网络发送出去,那么它会再次从用户空间的缓冲区拷贝到内核空间的网络缓冲区。
2. **多次上下文切换**:
- 每个系统调用(如read和write)都会引发一次从用户态到内核态的上下文切换;
- 上下文切换会消耗CPU周期。
3. **额外的内存分配和管理**:
- 每次拷贝都需要分配新的内存缓冲区,并且在拷贝完成后释放旧的缓冲区。
### 零拷贝技术的解决方案
1. **减少数据拷贝次数**:
- 零拷贝技术尽量避免数据在用户空间和内核空间之间的拷贝;
- 例如,可以使用DMA(Direct Memory Access)直接将数据从硬件设备(如磁盘)读取到内核缓冲区,而无需再拷贝到用户空间。
2. **减少上下文切换次数**:
- 通过减少系统调用的数量来减少上下文切换;
- 例如,使用sendfile等系统调用可以在一次调用中完成从文件读取到网络发送的整个过程。
3. **简化内存管理**:
- 避免不必要的内存分配和释放操作;
- 利用直接内存映射或者共享内存区域来减少内存管理的开销。
### 具体实现方法
- **Sendfile**:通过sendfile系统调用可以直接从文件描述符读取数据并通过另一个文件描述符发送出去,无需中间的用户空间缓冲区。
- **Mmap/Memory Mapping**:通过内存映射文件的方式,使文件内容直接映射到进程的地址空间,从而避免了用户空间和内核空间之间的数据拷贝。
- **Direct I/O**:某些文件系统支持直接I/O,允许应用程序直接读写磁盘而绕过内核缓冲区,这样可以减少一次数据拷贝。
- **Ring Buffers**:使用环形缓冲区来存储数据,应用程序和内核都可以访问同一段内存,从而减少数据拷贝。
通过这些技术手段,零拷贝可以显著提高数据传输的效率,降低CPU和内存的使用率,进而提升应用程序的整体性能。
二、Nginx的架构与安装
nginx的架构和进程
Nginx 进程结构
web请求处理机制
- 多进程方式:服务器每接收到一个客户端请求就有服务器的主进程生成一个子进程响应客户端,直 到用户关闭连接,这样的优势是处理速度快,子进程之间相互独立,但是如果访问过大会导致服务 器资源耗尽而无法提供请求
- 多线程方式:与多进程方式类似,但是每收到一个客户端请求会有服务进程派生出一个线程和此客 户端进行交互,一个线程的开销远远小于一个进程,因此多线程方式在很大程度减轻了web服务器 对系统资源的要求,但是多线程也有自己的缺点,即当多个线程位于同一个进程内工作的时候,可 以相互访问同样的内存地址空间,所以他们相互影响,一旦主进程挂掉则所有子线程都不能工作 了,IIS服务器使用了多线程的方式,需要间隔一段时间就重启一次才能稳定。
nginx进程间通信
Nginx 是一款广泛使用的高性能 Web 和反向代理服务器,它采用了异步非阻塞的事件驱动架构来处理大量的并发连接。Nginx 的工作模式基于多进程或多线程模型,其中每个进程或线程独立运行并处理不同的客户端连接。为了协调这些进程之间的工作,Nginx 实现了多种进程间的通信机制。下面是 Nginx 进程间通信的主要方式及特点:
### 1. 主进程与工作进程之间的通信
- **主进程**负责监听端口并接收客户端连接请求。
- **工作进程**负责处理具体的客户端请求。
#### 特点:
- **配置更新**:当配置文件发生变化时,主进程负责重新加载配置,并通过信号通知工作进程。
- **平滑重启**:在重启过程中,主进程创建一个新的主进程和一组新的工作进程,然后优雅地关闭旧的工作进程,确保服务不中断。
### 2. 工作进程之间的通信
- **工作进程**之间通常是无状态的,它们各自独立处理请求。
- **共享内存**:Nginx 使用共享内存段来实现工作进程间的通信和同步。
#### 特点:
- **共享内存**:用于存储全局状态信息,如计数器、统计信息等。
- **锁机制**:使用原子操作或轻量级锁来保证共享内存中数据的一致性。
### 3. 使用 Unix 域套接字进行通信
- **Unix 域套接字**:一种用于进程间通信的方法,它允许在同一主机上的进程之间传递数据。
#### 特点:
- **模块化设计**:Nginx 支持通过模块扩展功能,这些模块可以通过 Unix 域套接字与 Nginx 的主进程或工作进程通信。
- **负载均衡模块**:如 Nginx Plus 中的动态负载均衡模块,可以使用 Unix 域套接字来调整后端服务器的权重。
- **监控和管理工具**:如 Nginx 的 Stream 模块,可以使用 Unix 域套接字进行监控和管理操作。
### 4. 信号机制
- **信号**:操作系统提供的机制,用于在进程之间传递简单的信息。
#### 特点:
- **配置重载**:通过发送 `SIGHUP` 信号来通知 Nginx 重新加载配置文件。
- **进程控制**:如发送 `SIGTERM` 信号来优雅地终止进程。
### 5. 文件共享
- **文件共享**:在某些情况下,Nginx 可能需要共享文件句柄或文件内容。
#### 特点:
- **共享文件句柄**:例如,在使用 sendfile 等系统调用时,文件描述符可以直接从一个进程传递给另一个进程。
- **缓存机制**:使用共享内存或文件系统缓存来存储静态内容,减少重复加载。
### 总结
Nginx 的进程间通信主要依赖于以下几种机制:
- **主进程负责配置更新和平滑重启**。
- **工作进程之间通过共享内存和锁机制进行通信**。
- **Unix 域套接字用于模块化扩展和高级功能**。
- **信号机制用于控制和管理进程**。
- **文件共享用于优化数据传输**。
通过这些机制,Nginx 能够高效地处理高并发请求,并保持稳定的服务质量。
Nginx 启动和 HTTP 连接建立
Nginx 是一款流行的高性能 Web 和反向代理服务器,它的启动流程和 HTTP 连接建立过程是其高效处理大量并发连接的关键。下面是这两个过程的总结:
### Nginx 启动流程
1. **主进程初始化**:
- 创建一个主进程(master process)。
- 主进程解析配置文件,并根据配置初始化服务器的各种参数和设置。
- 初始化信号处理机制,以便能够处理来自用户的信号(如 SIGHUP、SIGTERM 等)。
2. **打开监听套接字**:
- 主进程为每个监听的端口创建并绑定一个套接字。
- 设置套接字为监听模式,准备接受客户端连接请求。
3. **创建工作进程**:
- 根据配置文件中的指令创建指定数量的工作进程(worker processes)。
- 每个工作进程都会复制主进程的监听套接字,并开始监听客户端连接请求。
4. **配置重载**:
- 如果配置文件发生变化,主进程会在收到 SIGHUP 信号后重新加载配置文件。
- 平滑重启:创建新的主进程和工作进程,然后优雅地关闭旧的工作进程。
5. **进入运行状态**:
- 主进程负责管理所有工作进程,并监听来自用户的信号。
- 工作进程负责处理客户端的连接请求。
### HTTP 连接建立过程
1. **客户端发起连接**:
- 客户端(如浏览器)通过 TCP 协议向 Nginx 的监听端口发起连接请求。
- Nginx 的工作进程之一接收这个连接请求。
2. **建立 TCP 连接**:
- 通过三次握手完成 TCP 连接的建立。
- 第一步:客户端发送 SYN 包给服务器。
- 第二步:服务器回复 ACK+SYN 包给客户端。
- 第三步:客户端发送 ACK 包给服务器。
3. **HTTP 请求处理**:
- 客户端发送 HTTP 请求报文到已建立的 TCP 连接。
- Nginx 的工作进程读取请求报文并解析。
- 根据配置文件中的路由规则处理请求,可能包括转发到后端服务器或直接提供静态内容等。
4. **响应生成**:
- 根据请求的内容,Nginx 生成相应的 HTTP 响应。
- 如果请求需要从后端服务器获取数据,Nginx 会作为代理转发请求并等待后端服务器的响应。
5. **发送响应**:
- 将生成的 HTTP 响应通过已建立的 TCP 连接发送回客户端。
6. **关闭连接**:
- 根据 HTTP 协议版本的不同,连接可能会被关闭(HTTP/1.0)或保持打开(HTTP/1.1)以供后续请求使用。
- 对于 HTTP/2 和 HTTP/3,连接可以保持更长时间,以支持多路复用多个请求。
### 总结
- **启动流程**:Nginx 通过主进程初始化配置和创建工作进程来准备处理连接请求。
- **连接建立**:客户端通过 TCP 三次握手与 Nginx 建立连接,然后发送 HTTP 请求,Nginx 处理请求并生成响应,最后通过相同的 TCP 连接发送回客户端。
通过这种高效的启动流程和连接处理机制,Nginx 能够支持大量的并发连接,并保持高性能和稳定性。
Nginx 安装
Nginx版本
- Mainline version 主要开发版本,一般为奇数版本号,比如1.19
- Stable version 当前最新稳定版,一般为偶数版本,如:1.20
- Legacy versions 旧的稳定版,一般为偶数版本,如:1.18
Nginx安装可以使用yum或源码安装,但是推荐使用源码编译安装
yum的版本比较旧
编译安装可以更方便自定义相关路径
使用源码编译可以自定义相关功能,更方便业务的上的使用
在使用源码安装前,我们需要下载一些依赖
dnf install gcc pcre-devel zlib-devel openssl-devel -y
在这之后我们需要创建nginx用户,解压我们的nginx压缩包
useradd -s /sbin/nologin -M nginx
tar zxf nginx-1.24.0.tar.gz
./configure --prefix=/usr/local/nginx \
--user=nginx \ # 指定nginx运行用户
--group=nginx \ # 指定nginx运行组
--with-http_ssl_module \ # 支持https://
--with-http_v2_module \ # 支持http版本2
--with-http_realip_module \ # 支持ip透传
--with-http_stub_status_module \ # 支持状态页面
--with-http_gzip_static_module \ # 支持压缩
--with-pcre \ # 支持正则
--with-stream \ # 支持tcp反向代理
--with-stream_ssl_module \ # 支持tcp的ssl加密
--with-stream_realip_module # 支持tcp的透传ip
把nginx命令添加到环境变量里
vim ~/.bash_profile
export PATH=$PATH:/usr/local/nginx/sbin
平滑升级和回滚
有时候我们需要对Nginx版本进行升级以满足对其功能的需求,例如添加新模块,需要新功能,它允许管理员在不停止服务的前提下更新 Nginx 版本或更改配置。平滑升级的核心思想是在新旧两个主进程之间进行平滑过渡,以确保服务的连续性和可用性,而此时 Nginx又在跑着业务无法停掉,这时我们就可能选择平滑升级
创建新主进程:
- 当需要升级时,先启动一个新的 Nginx 主进程,并加载最新的配置文件。
- 新的主进程会创建相应数量的工作进程来处理新的连接请求。
优雅关闭旧进程:
- 一旦新的主进程及其工作进程准备好接收请求,就可以通过发送 SIGHUP 信号给旧的主进程来触发平滑升级。
- 接收到信号后,旧的主进程会停止接收新的连接请求,并等待正在处理的请求完成。
- 一旦所有旧的工作进程都完成了它们的任务,旧的主进程会终止这些工作进程,并最终退出。
切换监听套接字:
- 在整个过程中,新的主进程及其工作进程开始接管新连接,而旧的主进程则逐渐释放资源。
- 最终,新的主进程接管所有监听套接字,成为新的服务提供者。
验证升级:
升级完成后,应该验证新的 Nginx 实例是否正常运行,并确认所有的服务都没有受到干扰。
通过这种方式,Nginx 可以在不影响用户访问的情况下进行版本更新或配置变更,极大地提高了系统的可靠性和用户体验。平滑升级是 Nginx 高可用性的一个重要特性,使得 Nginx 成为了部署高流量网站和服务的理想选择。
tar zxf nginx-1.26.1.tar.gz
cd nginx-1.26.1/
开始编译
./configure --with-http_ssl_module --withhttp_v2_module
--with-http_realip_module --with-http_stub_status_module --withhttp_gzip_static_module
--with-pcre --with-stream --with-stream_ssl_module --
with-stream_realip_module
只要make无需要make install
make
查看两个版本
ll objs/nginx /usr/local/nginx/sbin/nginx
-rwxr-xr-x 1 root root 1239416 Jul 18 15:08 objs/nginx
-rwxr-xr-x 1 root root 5671488 Jul 18 11:41 /usr/local/nginx/sbin/nginx
把之前的旧版的nginx命令备份
cd /usr/local/nginx/sbin/
cp nginx nginx.24
把新版本的nginx命令复制过去
\cp -f /root/nginx/nginx-1.26.1/objs/nginx
/usr/local/nginx/sbin
检测一下有没有问题
[root@Nginx sbin]# nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
没问题的话,我们使用平滑升级的方法杀死进程:
[root@Nginx sbin]# kill -USR2 48732 #nginx worker ID
#USR2 平滑升级可执行程序,将存储有旧版本主进程PID的文件重命名为nginx.pid.oldbin,并启动新的
nginx
#此时两个master的进程都在运行,只是旧的master不在监听,由新的master监听80
#此时Nginx开启一个新的master进程,这个master进程会生成新的worker进程,这就是升级后的Nginx进
程,此时老的进程不会自动退出,但是当接收到新的请求不作处理而是交给新的进程处理。
过滤一下端口
[root@Nginx sbin]# ps aux | grep nginx
root 48732 0.0 0.1 9868 2436 ? Ss 14:17 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
nobody 48733 0.0 0.2 14200 4868 ? S 14:17 0:00 nginx: worker
process
root 52075 0.0 0.3 9876 6528 ? S 15:41 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
nobody 52076 0.0 0.2 14208 4868 ? S 15:41 0:00 nginx: worker
process
用curl -I 查看一下版本
[root@Nginx sbin]# curl -I localhost
HTTP/1.1 200 OK
Server: nginx/1.24.0 ##依旧是旧版本生生效
Date: Thu, 18 Jul 2024 07:45:58 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Thu, 18 Jul 2024 03:41:13 GMT
Connection: keep-alive
ETag: "66988ed9-267"
Accept-Ranges: bytes
这个时候我们就可以开始回收旧版本了
[root@Nginx sbin]# kill -WINCH 48732
[root@Nginx sbin]# ps aux | grep nginx
root 48732 0.0 0.1 9868 2436 ? Ss 14:17 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
root 52075 0.0 0.3 9876 6528 ? S 15:41 0:00 nginx: master
process /usr/local/nginx/sbin/nginxnobody 52076 0.0 0.2 14208 4868 ? S 15:41 0:00 nginx: worker
process
这个时候我们使用curl -I可以发现,已经变成新版本了
[root@Nginx sbin]# curl -I localhost
HTTP/1.1 200 OK
Server: nginx/1.26.1 #新版本生效
Date: Thu, 18 Jul 2024 07:59:45 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Thu, 18 Jul 2024 03:41:13 GMT
Connection: keep-alive
ETag: "66988ed9-267"
Accept-Ranges: bytes
如果升级的版本发现问题需要回滚,可以重新拉起旧版本的worker
cp nginx nginx.26
ls查看一下可以发现现在有三个版本
[root@Nginx sbin]# ls
nginx nginx.24 nginx.26
使用mv命令改名
mv nginx.24 nginx
接下来进行回滚
[root@Nginx sbin]# kill -HUP 48732
[root@Nginx sbin]# ps aux | grep nginx
root 48732 0.0 0.1 9868 2436 ? Ss 14:17 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
root 52075 0.0 0.3 9876 6528 ? S 15:41 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
nobody 52076 0.0 0.2 14208 5124 ? S 15:41 0:00 nginx: worker
process
nobody 52130 0.0 0.2 14200 4868 ? S 16:30 0:00 nginx: worker
process
[root@Nginx sbin]# kill -WINCH 52075
[root@Nginx sbin]# ps aux | grep nginx
root 48732 0.0 0.1 9868 2436 ? Ss 14:17 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
root 52075 0.0 0.3 9876 6528 ? S 15:41 0:00 nginx: master
process /usr/local/nginx/sbin/nginx
nobody 52130 0.0 0.2 14200 4868 ? S 16:30 0:00 nginx: worker
process
root 52137 0.0 0.1 221664 2176 pts/0 S+ 16:31 0:00 grep --
color=auto nginx
这个时候我们使用curl -I 命令查看,已经回滚成功了:
[root@Nginx sbin]# curl -I localhost
HTTP/1.1 200 OK
Server: nginx/1.24.0 ##版本回滚完成
Date: Thu, 18 Jul 2024 08:31:51 GMT
Content-Type: text/html
Content-Length: 615
Last-Modified: Thu, 18 Jul 2024 03:41:13 GMT
Connection: keep-alive
ETag: "66988ed9-267"
Accept-Ranges: bytes
三 、Nginx 核心配置详解
配置文件说明
如果我们遇到了什么无法解决的问题,去查询文档是一个不错的习惯,下面是nginx的官方帮助文档,需要的话可以去翻阅
- nginx 官方帮助文档:http://nginx.org/en/docs/
- 主配置文件:nginx.conf
- 子配置文件: include conf.d/*.conf
- fastcgi, uwsgi,scgi 等协议相关的配置文件
- mime.types:支持的mime类型,MIME(Multipurpose Internet Mail Extensions)多用途互联网邮 件扩展类型,MIME消息能包含文本、图像、音频、视频以及其他应用程序专用的数据,是设定某 种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动 使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。
主配置文件结构:四部分
main block:主配置段,即全局配置段,对http,mail都有效
#事件驱动相关的配置 event
{
...
}
#http/https 协议相关配置段 http {
...
}
#默认配置文件不包括下面两个块 #mail 协议相关配置段
mail {
...
}
#stream 服务器相关配置段 stream {
...
}
全局配置说明
user nginx nginx; #启动Nginx工作进程的用户和组
worker_processes [number | auto]; #启动Nginx工作进程的数量,一般设为和CPU核心数相同
worker_cpu_affinity 00000001 00000010 00000100 00001000 | auto ;
#将Nginx工作进程绑定到指定的CPU核心,默认Nginx是不进行进程绑定的,绑定并不是意味着当前nginx进 程独占以一核心CPU,但是可以保证此进程不运行在其他核心上,这就极大减少了nginx的工作进程在不同的 cpu核心上的来回跳转,减少了CPU对进程的资源分配与回收以及内存管理等,因此可以有效的提升nginx服务 器的性能。
CPU MASK:
00000001:0号CPU
00000010:1号CPU
10000000:7号CPU
#示例
worker_cpu_affinity 0001 0010 0100 1000;第0号---第3号CPU
worker_cpu_affinity 0101 1010;
#示例
worker_processes 4;
worker_cpu_affinity 00000010 00001000 00100000 10000000;
[root@centos8 ~]# ps axo pid,cmd,psr | grep nginx
31093 nginx: master process /apps 1
34474 nginx: worker process 1
34475 nginx: worker process 3
34476 nginx: worker process 5
34477 nginx: worker process 7
#错误日志记录配置,语法:error_log file [debug | info | notice | warn | error | crit | alert | emerg]
#error_log logs/error.log; #error_log logs/error.log notice; error_log /usr/local/nginx/logs/error.log error;
#pid文件保存路径 pid
/usr/local/nginx/logs/nginx.pid; worker_priority 0; #工作进程优先级,-20~20(19) worker_rlimit_nofile 65536; #所有worker进程能打开的文件数量上限,
#包括:Nginx的所有连接(例如与代理服务器的连接等
#而不仅仅是与客户端的连接 #另一个考虑因素是实际的并发连接数不能超过系统级别的最大打开文件 数的限制
#最好与ulimit -n 或者limits.conf的值保持一致,
#修改pam限制
[root@Nginx ~]# sudo -u nginx ulimit -n 1024
[root@Nginx ~]# vim /etc/security/limits.conf * - nofile 100000 [root@Nginx ~]# sudo -u nginx ulimit -n 100000 daemon off; #前台运行Nginx服务用于测试、docker等环境。
master_process off|on; #是否开启Nginx的master-worker工作模式,仅用于开发调试场景,默认为 on
events {
worker_connections 65535; #设置单个工作进程的最大并发连接数
use epoll; #使用epoll事件驱动,
#Nginx支持众多的事件驱动,
#比如:select、poll、epoll,只能设置在events模块中 设置
accept_mutex on;
#on为同一时刻一个请求轮流由work进程处理,
#而防止被同时唤醒所有worker
#避免多个睡眠进程被唤醒的设置,默认为off
#新请求会唤醒所有worker进程,此过程也称为"惊群"
#因此nginx刚安装完以后要进行适当的优化。建议设置为on multi_accept on;
#on时Nginx服务器的每个工作进程可以同时接受多个新的网 络连接 #此指令默认为off,
#即默认为一个工作进程只能一次接受一个新的网络连接
#打开后几个同接受多个。建议设置为on
}
http 配置块
#在响应报文中将指定的文件扩展名映射至MIME对应的类型
include /etc/nginx/mime.types;
default_type application/octet-stream; #除mime.types中的类型外
#指定其它文件的默认MIME类型,浏览 器一般会提示下载
types {
text/html html;
image/gif gif;
image/jpeg jpg;
}
示例:识别php文件为text/html
[root@Nginx ~]# vim /usr/local/nginx/html/lee.php
<?php
phpinfo();
?>
[root@Nginx ~]# curl -I 172.25.254.100/lee.php
HTTP/1.1 200 OK
Server: nginx/1.26.1
Content-Type: application/octet-stream
Content-Length: 24
Last-Modified: Fri, 19 Jul 2024 09:38:52 GMT
Connection: keep-alive
ETag: "669a342c-18"
Accept-Ranges: bytes
[root@Nginx ~]# vim /usr/local/nginx/conf/nginx.conf
default_type text/html;
[root@Nginx ~]# nginx -s reload
[root@Nginx ~]# curl -I 172.25.254.100/lee.php
HTTP/1.1 200 OK
Server: nginx/1.26.1
Date: Fri, 19 Jul 2024 09:49:49 GMT
Content-Type: text/html
Content-Length: 24
Last-Modified: Fri, 19 Jul 2024 09:38:52 GMT
Connection: keep-alive
ETag: "669a342c-18"
Accept-Ranges: bytes
核心配置示例
精确匹配
在server部分使用location配置一个web界面,例如:当访问nginx 服务器的/logo.jpg的时候要显示指定 html文件的内容,精确匹配一般用于匹配组织的logo等相对固定的URL,匹配优先级最高
mkdir /webdata/nginx/timinglee.org/lee/images -p
ls /webdata/nginx/timinglee.org/lee/images
vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location = /logo.png {
root /webdata/nginx/timinglee.org/lee/images;
}
}
上传logo.jpg图片到/webdata/nginx/timinglee.org/lee/images,重启Nginx并访问测试
访问测试:http://www.timinglee.org/logo.png
区分大小写
~ 实现区分大小写的模糊匹配. 以下范例中, 如果访问uri中包含大写字母的logo.PNG,则以下location匹配logo.png条件不成功 因为 ~ 区分大小写,当用户的请求被执行匹配时发现location中定义的是小写的png, 本次访问的uri匹配失败,后续要么继续往下匹配其他的location(如果有),要么报错给客户端
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location ~ /logo.PNG {
root /webdata/nginx/timinglee.org/lee/images;
}
}
重启Nginx并访问测试 http://www.timinglee.org/logo.PNG
访问失败,系统中没有logo.PNG文件
不区分大小写
~* 用来对用户请求的uri做模糊匹配,uri中无论都是大写、都是小写或者大小写混合,此模式也都会匹 配,通常使用此模式匹配用户request中的静态资源并继续做下一步操作,此方式使用较多
注意: 此方式中,对于Linux文件系统上的文件仍然是区分大小写的,如果磁盘文件不存在,仍会提示404
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location ~* /logo.PNG {
root /webdata/nginx/timinglee.org/lee/images;
}
}
重启Nginx并访问测试 #http://www.timinglee.org/logo.png
URI开始
首先我们先准备实验环境:
mkdir /webdata/nginx/timinglee.org/lee/images/images{1,2}
echo image1 > /webdata/nginx/timinglee.org/lee/images/images1/index.html
echo image1 > /webdata/nginx/timinglee.org/lee/images/images2/index.html
然后我们编写子配置文件
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location ^~ /images {
root /webdata/nginx/timinglee.org/lee/images;
index index.html;
}
location /images1 {
root /webdata/nginx/timinglee.org/lee/images;
}
}
重启Nginx并访问测试,实现效果是访问/images1和/images2返回内容一样
[root@node100 ~]# curl 172.25.254.200/images1/
image1
[root@node100 ~]# curl 172.25.254.200/images2/
image1
文件名后缀
上传一个图片到/webdata/nginx/timinglee.org/lee/image
mkdir /webdata/nginx/timinglee.org/lee/images
编写子配置文件
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location ~* \.(gif|jpg|jpeg|bmp|png|tiff|tif|ico|wmf|js|css)$ {
root /webdata/nginx/timinglee.org/lee/images;
index index.html;
}
}
重启Nginx并访问测试 172.25.254.200/logo.png
优先级
server {
listen 80;
server_name lee.timinglee.org;
location / {
root /webdata/nginx/timinglee.org/lee/html;
}
location ^~ /images {
root /webdata/nginx/timinglee.org/lee/images;
index index.html;
}
location /images1 {
root /webdata/nginx/timinglee.org/lee/images;
}
location ~* \.(gif|jpg|jpeg|bmp|png|tiff|tif|ico|wmf|js)$ {
root /data/nginx/static3;
index index.html;
}
}
匹配优先级:=, ^~, ~/~*,/ location优先级:(location =) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 完整路径) > (location 部分起始路径) > (/)
生产使用案例
有了前面的基础之后,我们来尝试做一个生产使用的案例,直接匹配网站根会加速Nginx访问处理
ocation = /index.html {
......;
}
location / {
......;
}
静态资源配置方法1
location ^~ /static/ {
......;
}
静态资源配置方法2,应用较多
location ~* \.(gif|jpg|jpeg|png|css|js|ico)$ {
......;
}
多应用配置
location ~* /app1 {
......;
}
location ~* /app2 {
......;
}
四、Nginx 高级配置
Nginx 状态页
- 基于nginx 模块 ngx_http_stub_status_module 实现
- 在编译安装nginx的时候需要添加编译参数 --with-http_stub_status_module 否则配置完成之后监测会是提示法错误
注意: 状态页显示的是整个服务器的状态,而非虚拟主机的状态
配置事例:
location /nginx_status {
stub_status;
auth_basic "auth login";
auth_basic_user_file /apps/nginx/conf/.htpasswd;
allow 192.168.0.0/16;
allow 127.0.0.1;
deny all;
}
状态页用于输出nginx的基本状态信息:
输出信息示例:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
上面三个数字分别对应accepts,handled,requests三个值
Reading: 6 Writing: 179 Waiting: 106
Active connections: #当前处于活动状态的客户端连接数
#包括连接等待空闲连接数=reading+writing+waiting
accepts: #统计总值,Nginx自启动后已经接受的客户端请求连接的总数。
handled: #统计总值,Nginx自启动后已经处理完成的客户端请求连接总数
#通常等于accepts,除非有因worker_connections限制等被拒绝的
连接
requests: #统计总值,Nginx自启动后客户端发来的总的请求数
Reading: #当前状态,正在读取客户端请求报文首部的连接的连接数
#数值越大,说明排队现象严重,性能不足
Writing: #当前状态,正在向客户端发送响应报文过程中的连接数,数值越大,说明
访问量很大
Waiting: #当前状态,正在等待客户端发出请求的空闲连接数
开启 keep-alive的情况下,这个值等于active –
(reading+writing)
Nginx 变量使用.
- nginx的变量可以在配置文件中引用,作为功能判断或者日志等场景使用 .
- 变量可以分为内置变量和自定义变量 .
- 内置变量是由nginx模块自带,通过变量可以获取到众多的与客户端访问相关的值
常用内置变量
$remote_addr;
#存放了客户端的地址,注意是客户端的公网IP
$args;
#变量中存放了URL中的所有参数
#例如:https://search.jd.com/Search?keyword=手机&enc=utf-8 #返回结果为: keyword=手机&enc=utf-8
$is_args
#如果有参数为? 否则为空 $document_root;
#保存了针对当前资源的请求的系统根目录,例如:/webdata/nginx/timinglee.org/lee。
$document_uri;
#保存了当前请求中不包含参数的URI,注意是不包含请求的指令
#比如:http://lee.timinglee.org/var?\id=11111会被定义为/var
#返回结果为:/var
$host;
#存放了请求的host名称 limit_rate 10240; echo $limit_rate;
#如果nginx服务器使用limit_rate配置了显示网络速率,则会显示,如果没有设置, 则显示0
$remote_port;
#客户端请求Nginx服务器时随机打开的端口,这是每个客户端自己的端口
$remote_user;
#已经经过Auth Basic Module验证的用户名
$request_body_file;
#做反向代理时发给后端服务器的本地资源的名称
$request_method;
#请求资源的方式,GET/PUT/DELETE等
$request_filename;
#当前请求的资源文件的磁盘路径,由root或alias指令与URI请求生成的文件绝对路径,
#如:webdata/nginx/timinglee.org/lee/var/index.html
$request_uri;
#包含请求参数的原始URI,不包含主机名,相当于:$document_uri?$args,
#例如:/main/index.do?id=20190221&partner=search $scheme; #请求的协议,例如:http,https,ftp等
$server_protocol;
#保存了客户端请求资源使用的协议的版本,例如:HTTP/1.0,HTTP/1.1,HTTP/2.0等
$server_addr;
#保存了服务器的IP地址 $server_name;
#虚拟主机的主机名
$server_port;
#虚拟主机的端口号
$http_user_agent;
#客户端浏览器的详细信息
$http_cookie;
#客户端的所有cookie信息
$cookie_
#name为任意请求报文首部字部cookie的key名
$http_
#name为任意请求报文首部字段,表示记录请求报文的首部字段,ame的对应的首部字段名需要为小写,如果有 横线需要替换为下划线
#示例: echo $http_user_agent; echo $http_host;
$sent_http_
#name为响应报文的首部字段,name的对应的首部字段名需要为小写,如果有横线需要替换为下划线,此变量有 问题 echo $sent_http_server;
$arg_
#此变量存放了URL中的指定参数,name为请求url中指定的参数 echo $arg_id;
示例:
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /var {
default_type text/html;
echo $remote_addr;
echo $args;
echo $document_root;
echo $document_uri;
echo $host;
echo $http_user_agent;
echo $request_filename;
echo $scheme;
echo $scheme://$host$document_uri?$args;
echo $http_cookie;
echo $cookie_key2;
echo $http_Accept;
}
}
[root@client ~]# curl -b "title=lee;key1=lee,key2=timinglee"
"lee.timinglee.org/var?search=lee&&id=666666"
172.25.254.20
search=lee&&id=666666
/webdata/nginx/timinglee.org/lee
/var
lee.timinglee.org
curl/7.29.0
/webdata/nginx/timinglee.org/lee/var
http
http://lee.timinglee.org/var?search=lee&&id=666666
title=lee;key1=lee,key2=timinglee
timinglee
*/*
自定义变量
假如需要自定义变量名称和值,使用指令set $variable value;
语法格式:
Syntax: set $variable value;
Default: —
Context: server, location, if
示例:
set $name timinglee;
echo $name;
set $my_port $server_port;
echo $my_port;
echo "$server_name:$server_port";
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /var {
default_type text/html;
set $name timinglee;
echo $name;
set $web_port $server_port;
echo $web_port;
}
}
测试输出
[root@client ~]# curl lee.timinglee.org/var
timinglee
80
五、Nginx Rewrite 相关功能
- Nginx服务器利用 ngx_http_rewrite_module
- 模块解析和处理rewrite请求 此功能依靠 PCRE(perl compatible regular expression),因此编译之前要安装PCRE库
- rewrite是nginx服务器的重要功能之一,用于实现URL的重写,URL的重写是非常有用的功能
- 比如它可以在我们改变网站结构之后,不需要客户端修改原来的书签,也无需其他网站修改我们的 链接,就可以设置为访问
- 另外还可以在一定程度上提高网站的安全性。
if 指令
官方文档:
Module ngx_http_rewrite_module
用于条件匹配判断,并根据条件判断结果选择不同的Nginx配置,可以配置在server或location块中进行 配置,Nginx的if语法仅能使用if做单次判断,不支持使用if else或者if elif这样的多重判断,用法如下:
if (条件匹配) {
action
}
使用正则表达式对变量进行匹配,匹配成功时if指令认为条件为true,否则认为false,变量与表达式之间 使用以下符号链接:
= #比较变量和字符串是否相等,相等时if指令认为该条件为true,反之为false
!= #比较变量和字符串是否不相等,不相等时if指令认为条件为true,反之为false
~ #区分大小写字符,可以通过正则表达式匹配,满足匹配条件为真,不满足匹配条件为假
!~ #区分大小写字符,判断是否匹配,不满足匹配条件为真,满足匹配条件为假
~* #不区分大小写字符,可以通过正则表达式匹配,满足匹配条件为真,不满足匹配条件为假
!~* #不区分大小字符,判断是否匹配,满足匹配条件为假,不满足匹配条件为真
-f 和 !-f #判断请求的文件是否存在和是否不存在
-d 和 !-d #判断请求的目录是否存在和是否不存在
-x 和 !-x #判断文件是否可执行和是否不可执行
-e 和 !-e #判断请求的文件或目录是否存在和是否不存在(包括文件,目录,软链接)
#注意:
#如果$变量的值为空字符串或0,则if指令认为该条件为false,其他条件为true。
#nginx 1.0.1之前$变量的值如果以0开头的任意字符串会返回false
示例:
location /test {
index index.html;
default_type text/html;
if ( $scheme = http ){
echo "if ---------> $scheme";
}
if ( $scheme = https ){
echo "if ---------> $scheme";
}
}
location /test2 {
if ( !-e $request_filename ){
echo "$request_filename is not exist";
return 409;
}
}
测试结果:
[root@client ~]# curl lee.timinglee.org/test/
if ---------> http
[root@client ~]# curl lee.timinglee.org/test2/test
/webdata/nginx/timinglee.org/lee/test2/test is not exist
set 指令
指定key并给其定义一个变量,变量可以调用Nginx内置变量赋值给key 另外set定义格式为set $key value,value可以是text, variables和两者的组合。
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /test3{
set $name lee;
echo $name;
}
}
测试:
[root@client ~]# curl lee.timinglee.org/test3
lee
break 指令
用于中断当前相同作用域(location)中的其他Nginx配置
与该指令处于同一作用域的Nginx配置中,位于它前面的配置生效
位于后面的 ngx_http_rewrite_module 模块中指令就不再执行 Nginx服务器在根据配置处理请求的过程中遇到该指令的时候,回到上一层作用域继续向下读取配置
该指令可以在server块和locationif块中使用
注意: 如果break指令在location块中后续指令还会继续执行,只是不执行 ngx_http_rewrite_module 模块的指令,其它指令还会执行
使用语法如下:
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /break{
default_type text/html;
set $name lee;
echo $name;
break;
set $port $server_port;
echo $port;
}
}
[root@client ~]# curl lee.timinglee.org/break #当未添加break时
lee
80
[root@client ~]# curl lee.timinglee.org/break #添加break后
lee
return 指令
return用于完成对请求的处理,并直接向客户端返回响应状态码,比如:可以指定重定向URL(对于特殊重 定向状态码,301/302等) 或者是指定提示文本内容(对于特殊状态码403/500等),处于此指令后的所有配 置都将不被执行,return可以在server、if 和 location块进行配置
语法格式:
return code; #返回给客户端指定的HTTP状态码
return code [text]; #返回给客户端的状态码及响应报文的实体内容
#可以调用变量,其中text如果有空格,需要用单或双引号
return code URL; #返回给客户端的URL地址
示例:
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /return {
default_type text/html;
if ( !-e $request_filename){
return 301 http://www.baidu.com;
#return 666 "$request_filename is not exist";
}
echo "$request_filename is exist";
}
}
测试:
[root@client ~]# curl lee.timinglee.org/return
/webdata/nginx/timinglee.org/lee/return is exist
[root@client ~]# curl lee.timinglee.org/return1
/webdata/nginx/timinglee.org/lee/return1 is not exist
#测试return 301 http://www.baidu.com;
可在浏览器直接访问lee.timinglee.org/return1
return示例:
location /test {
default_type application/json;
return 200 '{"status:"success"}';
}
Nginx 防盗链
防盗链基于客户端携带的referer实现,referer是记录打开一个页面之前记录是从哪个页面跳转过来的标 记信息,如果别人只链接了自己网站图片或某个单独的资源,而不是打开了网站的整个页面,这就是盗 链,referer就是之前的那个网站域名,正常的referer信息有以下几种:
none: #请求报文首部没有referer首部,
#比如用户直接在浏览器输入域名访问web网站,就没有referer信息。 blocked: #请求报文有referer首部,但无有效值,比如为空。
server_names: #referer首部中包含本主机名及即nginx 监听的server_name。 arbitrary_string: #自定义指定字符串,但可使用*作通配符。
示例:
*.timinglee.org www.timinglee.
* regular expression
#被指定的正则表达式模式匹配到的字符串,要使用~开头,例如: ~.*\.timinglee\.com
正常通过搜索引擎搜索web 网站并访问该网站的referer信息如下:
172.25.254.1 - - [22/Jul/2024:09:27:36 +0800] "GET /favicon.ico HTTP/1.1" 404 149
"http://lee.timinglee.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0)
Gecko/20100101 Firefox/115.0"
2024/07/22 09:27:36 [error] 34596#0: *205 open()
"/webdata/nginx/timinglee.org/lee/favicon.ico" failed (2: No such file or
directory), client: 172.25.254.1, server: lee.timinglee.org, request: "GET
/favicon.ico HTTP/1.1", host: "lee.timinglee.org", referrer:
"http://lee.timinglee.org/"
实现盗链:
在一个web 站点盗链另一个站点的资源信息,比如:图片、视频等
#新建一个主机172.25.254.20,盗取另一台主机lee.timinglee.org/images/logo.png的图片
[root@client ~]# yum install httpd -y
[root@client html]# vim /var/www/html/index.html
#准备盗链web页面:
<html>
<head>
<meta http-equiv=Content-Type content="text/html;charset=utf-8">
<title>盗链</title>
</head>
<body>
<img src="http://lee.timinglee.org/images/logo.png" >
<h1 style="color:red">欢迎大家</h1>
<p><a href=http://lee.timinglee.org>狂点老李</a>出门见喜</p>
</body>
</html>
#重启apache并访问http://172.25.254.20 测试
#验证两个域名的日志,是否会在被盗连的web站点的日志中出现以下盗链日志信息:
[root@Nginx ~]# cat /usr/local/nginx/logs/access.log
172.25.254.1 - - [22/Jul/2024:09:50:01 +0800] "GET /images/logo.png HTTP/1.1" 304
0 "http://172.25.254.20/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Edg/126.0.0.0"
172.25.254.1 - - [22/Jul/2024:09:50:18 +0800] "GET / HTTP/1.1" 304 0
"http://172.25.254.20/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
Edg/126.0.0.0"
实现防盗链
基于访问安全考虑,nginx支持通过ngx_http_referer_module模块,检查访问请求的referer信息是否有效 实现防盗链功能
官方文档:
https://nginx.org/en/docs/http/ngx_http_referer_module.html
示例: 定义防盗链:
[root@Nginx ~]# vim /usr/local/nginx/conf.d/vhosts.conf
server {
listen 80;
server_name lee.timinglee.org;
root /webdata/nginx/timinglee.org/lee;
location /images {
valid_referers none blocked server_names *.timinglee.org ~\.baidu\.;
if ($invalid_referer){
#return 403;
rewrite ^/ http://lee.timinglee.org/daolian.png permanent;
}
}
}
#重启Nginx并访问测试
http://172.25.254.20
六、Nginx 反向代理功能
指定 location 实现反向代理
server {
listen 80;
server_name www.timinglee.org;
location / {
proxy_pass http://172.25.254.30;
}
location ~ /static {
proxy_pass http://172.25.254.20:8080;
}
}
#后端web服务器必须要有相对于的访问URL
[root@apache20 ~]# mkdir /var/www/html/static
[root@apache20 ~]# echo static 172.25.254.20 > /var/www/html/static/index.html
[root@apache30 ~]# echo 172.25.254.30 > /var/www/html/index.html
#重启Nginx并访问测试:
[2024-07-25 17:09.35] ~
[Administrator.DESKTOP-P19CNDN] ➤ curl www.timinglee.org/static/
static 172.25.254.20
[2024-07-25 17:09.39] ~
[Administrator.DESKTOP-P19CNDN] ➤ curl www.timinglee.org
172.25.254.30
[root@Nginx ~]# vim /apps/nginx/conf.d/vhost.conf
server {
listen 80;
server_name www.timinglee.org;
location / {
proxy_pass http://172.25.254.30;
}
location ~ \.(png|jpg|gif) {
proxy_pass http://172.25.254.20:8080;
}
}
缓存功能
缓存功能默认关闭状态,需要先动配置才能启用
proxy_cache zone_name | off; 默认off
#指明调用的缓存,或关闭缓存机制;Context:http, server, location
#zone_name 表示缓存的名称.需要由proxy_cache_path事先定义
proxy_cache_key string;
#缓存中用于“键”的内容,默认值:proxy_cache_key $scheme$proxy_host$request_uri;
proxy_cache_valid [code ...] time;
#定义对特定响应码的响应内容的缓存时长,定义在http{...}中
示例:
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_path;
#定义可用于proxy功能的缓存;Context:http
proxy_cache_path path [levels=levels] [use_temp_path=on|off]
keys_zone=zone_name:size [inactive=time] [max_size=size] [manager_files=number]
[manager_sleep=time] [manager_threshold=time] [loader_files=number]
[loader_sleep=time] [loader_threshold=time] [purger=on|off]
[purger_files=number] [purger_sleep=time] [purger_threshold=time];
#示例:在http配置定义缓存信息
proxy_cache_path /var/cache/nginx/proxy_cache #定义缓存保存路径,proxy_cache会自动创建
levels=1:2:2 #定义缓存目录结构层次
#1:2:2可以生成
2^4x2^8x2^8=2^20=1048576个目录
keys_zone=proxycache:20m #指内存中缓存的大小,主要用于存放key和metadata
(如:使用次数)
#一般1M可存放8000个左右的key
inactive=120s #缓存有效时间
max_size=10g; #最大磁盘占用空间,磁盘存入文件内容的缓存空间最大值
#调用缓存功能,需要定义在相应的配置段,如server{...};或者location等
proxy_cache proxycache;
proxy_cache_key $request_uri; #对指定的数据进行MD5的运算做为缓存的key
proxy_cache_valid 200 302 301 10m; #指定的状态码返回的数据缓存多长时间
proxy_cache_valid any 1m; #除指定的状态码返回的数据以外的缓存多长时间,必须设置,
否则不会缓存
proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 |
http_502 | http_503 | http_504 | http_403 | http_404 | off ; #默认是off
#在被代理的后端服务器出现哪种情况下,可直接使用过期的缓存响应客户端
#示例
proxy_cache_use_stale error http_502 http_503;
proxy_cache_methods GET | HEAD | POST ...;
#对哪些客户端请求方法对应的响应进行缓存,GET和HEAD方法总是被缓存
非缓存场景压测
#准备后端httpd服务器
[root@apache20 app1]# pwd
/var/www/html/static
[root@apache20 static]# cat /var/log/messages > ./log.html #准备测试页面
[root@apache30 ~]# ab -n1000 -c100 http://www.timinglee.org/static/index.html
Concurrency Level: 100
Time taken for tests: 23.238 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 2011251000 bytes
HTML transferred: 2010991000 bytes
Requests per second: 43.03 [#/sec] (mean)
Time per request: 2323.789 [ms] (mean)
Time per request: 23.238 [ms] (mean, across all concurrent requests)
Transfer rate: 84521.97 [Kbytes/sec] received
准备缓存配置
[root@Nginx ~]# vim /apps/nginx/conf/nginx.conf
@@@@内容省略@@@@
#gzip on;
proxy_cache_path /apps/nginx/proxy_cache levels=1:2:2 keys_zone=proxycache:20m
inactive=120s max_size=1g; #配置在nginx.conf http配置段
[root@Nginx ~]# vim /apps/nginx/conf.d/vhost.conf
location ~ /static { #要缓存的URL 或者放在server配置项对所有URL都进行缓存
proxy_pass http://172.25.254.20:8080;
proxy_cache proxycache;
proxy_cache_key $request_uri;
proxy_cache_valid 200 302 301 10m;
proxy_cache_valid any 1m; #必须指定哪些响应码的缓存
}
#/data/nginx/proxycache/ 目录会自动生成
[root@Nginx ~]# ll /apps/nginx/proxy_cache/ -d
drwx------ 3 nginx root 4096 7月 25 20:07 /apps/nginx/proxy_cache/
[root@Nginx ~]# tree /apps/nginx/proxy_cache/
/data/nginx/proxycache/
0 directories, 0 files
访问并验证缓存文件
#访问web并验证缓存目录
[root@apache30 ~]# ab -n1000 -c100 http://www.timinglee.org/static/index.html
[root@centos8 ~]# ab -n 2000 -c200 http://www.magedu.org/static/log.html
Concurrency Level: 100
Time taken for tests: 10.535 seconds
Complete requests: 1000
Failed requests: 0
Total transferred: 2011251000 bytes
HTML transferred: 2010991000 bytes
Requests per second: 94.92 [#/sec] (mean)
Time per request: 1053.507 [ms] (mean)
Time per request: 10.535 [ms] (mean, across all concurrent requests)
Transfer rate: 186435.60 [Kbytes/sec] received
#验证缓存目录结构及文件大小
[root@Nginx ~]# tree /apps/nginx/proxy_cache/
/apps/nginx/proxy_cache/
└── e
└── 50
└── 99
└── 319432ef3663735a9d3cb4e0c1d9950e
3 directories, 0 files
七、nginx 二次开发版本
openresty
Nginx 是俄罗斯人发明的, Lua 是巴西几个教授发明的,中国人章亦春把 LuaJIT VM 嵌入到 Nginx 中,
实现了 OpenResty 这个高性能服务端解决方案
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方
模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服
务和动态网关。
OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将Nginx
有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言
调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高
性能 Web 应用系统。
OpenResty 由于有功能强大且方便的的API,可扩展性更强,如果需要实现定制功能,OpenResty是个不错的
选择
编译安装 openresty
[root@Nginx ~]#dnf -yq install gcc pcre-devel openssl-devel perl
[root@Nginx ~]#useradd -r -s /sbin/nologin nginx
[root@Nginx ~]#cd /usr/local/src
[root@Nginx src]#wget https://openresty.org/download/openresty-1.17.8.2.tar.gz
[root@Nginx src]#tar xf openresty-1.17.8.2.tar.gz
[root@Nginx src]#cd openresty-1.17.8.2/
[root@Nginx openresty-1.17.8.2]#./configure \
--prefix=/apps/openresty \
--user=nginx --group=nginx \
--with-http_ssl_module \
--with-http_v2_module \
--with_http_realip_module \
--with-http_stub_status_module \
--with-http_gzip_static_module
--with-pcre --with-stream \
--with-stream_ssl_module \
--with-stream_realip_module
[root@Nginx openresty-1.17.8.2]#make && make install
[root@Nginx openresty-1.17.8.2]#ln -s /apps/openresty/bin/* /usr/bin/
[root@Nginx openresty-1.17.8.2]#openresty -v
nginx version: openresty/1.17.8.2
[root@Nginx openresty-1.17.8.2]#openresty
[root@Nginx openresty-1.17.8.2]#ps -ef |grep nginx
测试:
[root@Nginx ~]#curl 10.0.0.18