一. Nginx进程结构
Nginx有两种进程结构:单进程和多进程结构。编译安装之后默认是只有一个worker process进程,这个可以在nginx.conf中配置。
推荐 :worker
进程数应该设置为等于 CPU
的核数,高流量并发场合也可以考虑将进程数提高至 CPU
核数 * 2
单进程只能用于开发调试;多进程可以保证Nginx足够健壮并且可以利用多核特性。
为什么是多进程而不是多线程?
Nginx核心目的是为了保持高可用、高可靠,如果Nginx使用多线程结构,线程之间共享一个地址空间的,这时候如果某一个第三方模块引发了一个地址空间导致的段错误时或者地址越界出现时,将会导致Nginx进程全部挂掉。但是在使用多进程模型的时候,就不会出现这种情况!
调整工作进程数量?
在nginx编译安装之后默认会启动一个主进程一个工作进程,这样并不能满足nginx性能要求,我们可以修改nginx的配置文件来设置工作进程数量:
如果手动设置的话,可以执行lscpu,查看CPU核心数:
二. 信号量
2.1. 什么是信号量?
信号量(Semaphore)是一种用于实现计算机资源共享的IPC机制之一,其本质是一个计数器。信号量是在多进程环境下实现资源互斥访问或共享资源访问的方法,可以用来保证两个或多个关键代码段不被并发调用。
当进入一个关键代码段之前,进程/线程必须获取一个信号量;一旦该关键代码段完成了,那么该进程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个进程释放信号量。
应用形式:
- 临界资源的互斥访问,临界资源在同一时刻只允许一个进程使用
- 处理多个共享资源,信号量在其中起到记录空闲资源数目的作用
我们可以通过kill -l 查看Linux支持的信号量:
2.2. 常用的信号量:
代码 | 编号 | 备注 |
SIGCHLD | 17 | 子进程结束时, 父进程会收到这个信号。 |
SIGQUIT | 3 | 和SIGINT类似, 但由QUIT字符(通常是Ctrl-/)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。 |
SIGTERM | 15 | 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。(并不会立马结束) |
SIGKILL | 9 | 用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。 |
SIGHUP | 1 | 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。 |
SIGUSR1 | 10 | 留给用户使用 |
SIGUSR2 | 12 | 留给用户使用 |
SIGWINCH | 28 | 忽略信号, 窗口大小改变时发出。 |
更详细的信息可以查看这个博主的文章:https://blog.xujiuming.com/ming/8b477532.html
2.3. 信号量使用
我在日常中使用kill命令通过PID杀死进程,其实就是发送一个信号量给进程:
如图所示:
这里9是SIGKILL
信号量的编号,这个信号量是暴力杀死nginx的进程,如果当前正在读写数据或者进行运算的话,就会出现问题。推荐使用SIGTERM
这个信号量,相对来说比较温和。
我们也可以看到我们使用SIGKILL
信号只能杀死master
进程,worker
进程还存活。但是如果我们使用不带-9
参数的命令是可以的,kill命令默认发送的15
(SIGTERM
)信号量。
正常来说,关闭一个进程都是先发送SIGTERM
信号,系统等待一个短时间,如果这个进程仍然没有关闭,才会发送SIGKILL
信号。
三. Nginx和信号量
nginx是通过信号来控制,比如关闭,重启等去控制nginx进程。nginx信号是属于nginx进程间的通信的一种机制,master主进程控制多个worker子进程,也是通过信号控制的。
3.1. master进程作用
nginx的master主进程主要用来管理master进程,作用如下:
- 读取并校验配置信息
- 管理提供服务的worker进程,向各个worker进程发送信号;
- 监控worker进程的运行状态,当worker进程异常退出时,发送CHLD信号给master进程,master进程通过创建新的worker进程保持工作进程数量不变
这里需要注意master主进程不会响应用户请求,真正起作用的是worker进程响应用户请求。
3.2. 接受的信号量
接着看一下主进程和工作进程各自接口那些信号量。
信号量 | 作用 | master | worker |
TERM | 终止信号。这是kill命令默认发送的信号类型。 | 接收 | 接收 |
INT | 中断信号。执行效果和在终端按下Ctrl-C键的效果一样。直接关闭进程,不管进程是否正在被请求都讲关闭 | 接收 | 接收 |
QUIT | 优雅地关闭进程,即等当前进程中请求结束后再关闭, | 接收 | 接收 |
HUP | 加载新的配置,master 进程不变,逐步停止没有请求的work启动新的worker进程 | 接收 | |
USR1 | 重新打开日志文件,日志备份,切割日志时使用。 | 接收 | 接收 |
USR2 | 当需要把正在运行的nginx 进程平滑升级的时候配合使用 | 接收 | |
WINCH | 优雅从容的关闭worker进程 (一般配合USR2使用) | 接收 | 接收 |
3.3. 命令行和信号量
我们在执行nginx的相关命令,相当于发送的是一系列的信号量:
命令 | 信号量 | 作用 |
reload | HUP | 重新加载配置文件 |
reopen | USR1 | 重读日志文件 |
stop | TERM | 快速关闭,不管有没有正在处理的请求 |
quit | QUIT | 优雅的关闭方式,Nginx在退出前完成已经接受的连接请求 |
四. Reload重载配置文件流程
我们知道我们在执行 nginx -s reload的时候nginx是没有停机的,那么这个重载配置文件的流程是什么呢?下面介绍一下:
- 向master进程发送HUP信号(reload命令)
- master检查配置文件是否正确
- master进程打开监听端口
- master进程使用新的配置文件启动新的worker子进程
- master进程向老的worker子进程发送QUIT信号
- 旧的worker进程关闭监听句柄,处理完当前连接后关闭进程
五. 热部署流程
当从老版本替换为新版本的 nginx 的时候,会需要取消 nginx 服务并重启服务才能替换成功,如果当前有用户正在访问,此时访问的用户会断开连接,这样会影响用户的体验,这个时候需要使用热部署来平滑的更换版本。
5.1. 热部署流程
大体流程如下:
- 将旧的nginx文件替换为新的nginx文件
- 向master进程发送USER2信号
- master进程修改pid文件,加后缀.oldbin
- master进程用新nginx文件启动新的master进程
- 向旧的master进程发送WINCH信号,旧的worker子进程退出(此时master的进程是没有退出的,这样才能保证出错的时候可以回滚)
- 回滚情形:向旧master发送HUP(根据旧的配置文件,拉起子进程),向新的master发送QUIT(新的下线)
5.2. 热部署演示
注意 :新版本的nginx的需要和旧版本的目录、版本和configure参数一致。
第一步:备份,热部署主要是替换nginx的可执行文件,避免出问题,先备份旧的nginx二进制文件。
第二步:启动新的nginx进程,这里通过给正在运行的master进程(旧版本)发送SIGUSR2信号,使 nginx 的旧版本停止接收请求,用 nginx 新版本接替
执行结果如图:
并且此时在logs的会出现nginx.pid和nginx.pid.oldbin两个文件,分别记录新旧两个nginx的master进程PID。
第三步:停止旧nginx的worker进程,但是旧的nginx的master进程是不可以停止的,否则无法回滚
结果如图:
此时可以验证新的nginx是否满足我们的需求,不满足的话可以进程回滚,满足的话可以将旧的nginx的master进程结束掉。
5.3. 回滚演示
当发现新的nginx不能满足要求,这时就需要回滚了。此时可以给旧的nginx的master发送一个SIGHUP信号,旧的的nginx的master会拉起旧的配置文件,启动worker子进程。
结果:
接着退出问题的新的nginx。
结果:
扩展:段错误
什么是段错误?
一旦一个程序发生了越界访问,cpu 就会产生相应的保护,于是 segmentation fault 就出现了,通过上面的解释,段错误应该就是访问了不可访问的内存 。
这个内存区要么是不存在的,要么是受到系统保护的,还有可能是缺少文件或者文件损坏。
段错误产生的原因?
- 非关联化空指针——这是特殊情况由内存管理硬件
- 试图访问一个不存在的内存地址(在进程的地址空间)
- 试图访问内存的程序没有权利(如内核结构流程上下文)
- 试图写入只读存储器(如代码段)