Nginx进程管理

2023-10-27

Nginx进程管理

1. Nginx进程管理之master进程

监控进程充当整个进程组与用户的交互接口,同时对进程进行监护。它不需要处理网络事件,不负责业务的执行,只会通过管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。
master进程全貌图(来自阿里集团数据平台博客):
master进程中for(::)无限循环内有一个关键的sigsuspend()函数调用,该函数调用是的master进程的大部分时间都处于挂起状态,直到master进程收到信号为止。
master进程通过检查一下7个标志位来决定ngx_master_process_cycle方法的运行:
sig_atomic_t ngx_reap;
sig_atomic_t ngx_terminate;
sig_atomic_t ngx_quit;
sig_atomic_t ngx_reconfigure;
sig_atomic_t ngx_reopen;
sig_atomic_t ngx_change_binary;
sig_atomic_t ngx_noaccept;
进程中接收到的信号对Nginx框架的意义:
信号 对应进程中的全局标志位变量 意义
QUIT ngx_quit 优雅地关闭整个服务
TERM或INT ngx_terminate 强制关闭整个服务
USR1 ngx_reopen 重新打开服务中的所有文件
WINCH ngx_noaccept 所有子进程不再接受处理新的连接,实际相当于对所有子进程发送QUIT信号
USR2 ngx_change_binary 平滑升级到新版本的Nginx程序
HUP ng_reconfigure 重读配置文件
CHLD ngx_reap 有子进程以外结束,需要监控所有子进程
还有一个标志位会用到:ngx_restart,它仅仅是在master工作流程中作为标志位使用,与信号无关。
我们知道在main函数中完成了Nginx启动初始化过程,启动初始化过程中的一个重要环节就是解析配置文件,回调各个配置指令的回调函数,因此完成了各个模块的配置及相互关联。在所有的这些重要及不重要的初始化完成后,main函数就开始为我们打开进程的“大门”——调用ngx_master_process_cycle(cycle); 接下来的文字里面,我们就重点来看看这个函数里做了一些什么事情。
    sigemptyset(&set);
    sigaddset(&set, SIGCHLD);
    sigaddset(&set, SIGALRM);
    sigaddset(&set, SIGIO);
    sigaddset(&set, SIGINT);
    sigaddset(&set, ngx_signal_value(NGX_RECONFIGURE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_REOPEN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_NOACCEPT_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_TERMINATE_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
    sigaddset(&set, ngx_signal_value(NGX_CHANGEBIN_SIGNAL));
    if (sigprocmask(SIG_BLOCK, &set, NULL) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "sigprocmask() failed");
    }
上面为了屏蔽一系列的信号。
    ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module);
    ngx_start_worker_processes(cycle, ccf->worker_processes,
                               NGX_PROCESS_RESPAWN);
    ngx_start_cache_manager_processes(cycle, 0);
这里好像要开始创建子进程了哦,没错,master进程就是通过依次调用这两个函数来创建子进程。第一个调用的函数创建的子进程我们称为worker进程,第二调用的函数创建的是有关cache的子进程。接收请求,完成响应的就是worker进程。光光是调用这个函数好像没什么看头,我们深入“虎穴”窥探一下究竟。
    for (i = 0; i < n; i++) {
        cpu_affinity = ngx_get_cpu_affinity(i);
        ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
                          "worker process", type);
        ch.pid = ngx_processes[ngx_process_slot].pid;
        ch.slot = ngx_process_slot;
        ch.fd = ngx_processes[ngx_process_slot].channel[0];
        ngx_pass_open_channel(cycle, &ch);
    }
其实吧,ngx_start_worker_processes函数挺短小精干的,再截取主体就剩下这么一个for循环了。此处就是循环创建起n个worker进程,fork新进程的具体工作在ngx_spawn_process函数中完成。这里涉及到了一个全局数组ngx_processes(定义在src/os/unix/ngx_process.c文件中),这个数组的长度为NGX_MAX_PROCESSES(默认1024),存储的元素类型是ngx_process_t(定义在src/os/unix/ngx_process.h文件中)。全局数组ngx_processes就是用来存储每个子进程的相关信息,如:pid,channel,进程做具体事情的接口指针等等,这些信息就是用结构体ngx_process_t来描述的。在ngx_spawn_process创建好一个worker进程返回后,master进程就将worker进程的pid、worker进程在ngx_processes数组中的位置及channel[0]传递给前面已经创建好的worker进程,然后继续循环开始创建下一个worker进程。刚提到一个channel[0],这里简单说明一下:channel就是一个能够存储2个整型元素的数组而已,这个channel数组就是用于socketpair函数创建一个进程间通道之用的。master和worker进程以及worker进程之间都可以通过这样的一个通道进行通信,这个通道就是在ngx_spawn_process函数中fork之前调用socketpair创建的。有兴趣的自己读读ngx_spawn_process吧。
至于ngx_start_cache_manager_processes函数,和start_worker的工作相差无几,这里暂时就不纠结了。至此,master进程就完成了worker进程的创建工作了,此时此刻系统中就有一个master进程+N个worker进程在工作了哦,接下来master进程将“陷入”死循环中守护着worker进程,担当起伟大的幕后工作。在master cycle中调用了sigsuspend(),因而将master进程挂起,等待信号的产生。master cycle所做的事情虽然不算复杂,但却比较多;主要过程就是:【收到信号】,【调用信号处理函数(在初始化过程中注册了)】,【设置对应的全局变量】,【sigsuspend函数返回,判断各个全局变量的值并采取相应的动作】。在这里,我们不对每个信号的处理情况进行分析,随便看看两个信号就好了。
        if (ngx_quit) {
            ngx_signal_worker_processes(cycle,
                                        ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
            ls = cycle->listening.elts;
            for (n = 0; n < cycle->listening.nelts; n++) {
                if (ngx_close_socket(ls[n].fd) == -1) {
                    ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                                  ngx_close_socket_n " %V failed",
                                  &ls[n].addr_text);
                }
            }
            cycle->listening.nelts = 0;
            continue;
        }

这段位于master cycle中的代码是对SIGQUIT信号进行的处理动作。ngx_quit就那个全局变量之一,当master进程收到这个信号的时候,就调用ngx_signal_handler(定义在src/os/unix/ngx_process.c文件中)设置ngx_quit为1。因此master从sigsuspend返回后,检测到ngx_quit为1,就调用ngx_signal_worker_processes函数向每个worker进程递送SIGQUIT信号,通知worker进程们开始退出工作。然后就关闭所有的监听套接字。最后居然来了一个continue就又回到了cycle中,不是退出吗?为什么是continue而不是exit呢。前面已经提过了,master进程是幕后者,需要守护着worker进程们,既然是守护哪能worker进程没撤退,自己就先撤退了呢。由于,worker进程是master的子进程,所以worker退出后,将发送SIGCHLD信号给master进程,好让master进程为其善后(否则将出现“僵尸”进程)。在master进程收到SIGCHLD信号,就会设置全局变量ngx_reap为1了。

        if (ngx_reap) {
            ngx_reap = 0;
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
            live = ngx_reap_children(cycle);
        }
此时,ngx_reap为1了,master进程调用ngx_reap_children处理所有的worker子进程。这个ngx_reap_children函数不光担任起为worker进程善后的工作(子进程的收尸处理是在信号处理函数直接完成的),还担任了重启worker进程的任务。当然,这个重启worker进程是在一些异常情况下导致worker进程退出后的重启,并不是在“君要臣死、臣不得不死”的时候的顽强抵抗。Nginx具有高度的模块化优势,每个人都可以开发自己需要的模块程序,难免会出现一些bug引起worker进程的崩溃,因此master进程就肩负起了容错任务,这样才能够保证24小时的提供服务。

2. Nginx进程管理之worker进程

首先找到worker进程的入口地方——ngx_worker_process_cycle。这个函数不光是worker进程的入口函数,同时也是worker进程循环工作的主体函数,看函数名含有一个cycle嘛。进入这个cycle函数,第一件事就是调用ngx_worker_process_init(cycle, 1);对worker进程进行初始化操作。先看看这个worker进程的初始化过程。
    ngx_process = NGX_PROCESS_WORKER;
    if (ngx_set_environment(cycle, NULL) == NULL) {
        /* fatal */
        exit(2);
    }
进入初始化就将全局变量ngx_process设置为worker进程的标志,由于这个变量是从master进程复制过来的,所以没设置前就是master进程的标志。然后设置相应的环境变量。接下去就是设置了一些列的资源限制,id等玩意,这里就忽略代码了。
    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->init_process) {
            if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
                /* fatal */
                exit(2);
            }
        }
    }
此处循环调用每个模块的init_process,完成每个模块自定义的进程初始化操作,一般在模块定义的时候设置这个回调指针的值,即注册一个函数给它。做模块开发的时候,貌似使用得挺少的,遇到的时候好好关注下。
    
    /*
    此处循环用于关闭其他worker进程的无用channel资源
    */
    for (n = 0; n < ngx_last_process; n++) {
	/*
	ngx_processes数组中n位置的进程不存在。
	*/	
        if (ngx_processes[n].pid == -1) {
            continue;
        }
	/*
	全局变量ngx_process_slot的值是创建worker进程的时候,从
	master进程复制过来的,所以此处ngx_process_slot就指本worker
	进程在ngx_process_slot数组中的索引位置。此处不处理本worker
	进程,所以跳过。
	*/
        if (n == ngx_process_slot) {
            continue;
        }
	/*
	channel不存在,继续跳过。
	*/
        if (ngx_processes[n].channel[1] == -1) {
            continue;
        }
	/*
	ngx_processes数组中存储的是每个worker进程的资源,是master进程负责创建的。
	因此创建一个worker进程的时候,就一同将这些资源复制过来了,所以此处就关闭
	无用的channel——其他worker进程的读端文件描述符,保留写端文件描述符做
	worker间的通信之用。
	*/
        if (close(ngx_processes[n].channel[1]) == -1) {
            ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                          "close() channel failed");
        }
    }
	/*
	关闭本worker进程channel的写端文件描述符,因为每个worker进程只从自己的channel
	上读,而不会写。写自己channel的是master和其他worker进程。这也是上面为什么要
	关闭其他worker进程channel的读端。
	*/
    if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) {
        ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
                      "close() channel failed");
    }
    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }

ngx_channel就是worker进程channel的读端,这里调用ngx_add_channel_event将channel放入Nginx关心的集合中,同时关注起这个channel上的读事件,也即这个channel上有数据到来后,就立马采取读channel操作。此处的添加一个channel的读事件是worker进程初始化的关键之处。到此,初始化过程就结束了,回到worker循环主体看看吧。
 	for ( ;; ) {
	/*
	ngx_exiting是在worker进程收到SIGQUIT信号后设置的,稍后就能看到庐山真面目了。
	*/
        if (ngx_exiting) {
            c = cycle->connections;
		/*
		worker进程退出前,先得处理完每个connection上已经发生的事件。
		*/
            for (i = 0; i < cycle->connection_n; i++) {
                /* THREAD: lock */
                if (c[i].fd != -1 && c[i].idle) {
                    c[i].close = 1;
                    c[i].read->handler(c[i].read);
                }
            }
			
		/*
		处理完所有事件后,调用ngx_worker_process_exit函数,worker进程退出。
		*/
            if (ngx_event_timer_rbtree.root == ngx_event_timer_rbtree.sentinel)
            {
                ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
                ngx_worker_process_exit(cycle);
            }
        }
        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
	/*
	这里是worker进程处理事件的核心开始。也即是,worker进程从里开始做一些特定的事情了,
	我们完全可以修改此处的代码,让Nginx为我们做一些其他的事情,呵呵。
	*/
        ngx_process_events_and_timers(cycle);
	/*
	worker进程收到了SIGINT信号,退出。
	*/
        if (ngx_terminate) {
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting");
            ngx_worker_process_exit(cycle);
        }
		
	/*
	worker进程收到了SIGQUIT信号,如果此时worker进程不是出于exiting状态,
	就将设置ngx_exiting为1,让其进入exiting状态;同时关闭监听套接口。
	*/
        if (ngx_quit) {
            ngx_quit = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
                          "gracefully shutting down");
            ngx_setproctitle("worker process is shutting down");
            if (!ngx_exiting) {
                ngx_close_listening_sockets(cycle);
                ngx_exiting = 1;
            }
        }
	/*
	worker进程收到了SIGUSR1信号
	*/
        if (ngx_reopen) {
            ngx_reopen = 0;
            ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs");
            ngx_reopen_files(cycle, -1);
        }
    }

3. master进程与worker进程间通信

这部分实现的源码主要分布于src/os/unix/channel.h和channel.c两个文件中。实现极其简单,没有什么复杂的逻辑。下面,我绘制了一个简单的master进程和worker进程间的关系,图中的箭头符号指出数据是由master进程传给worker进程,而没有从worker到master;这是因为channel不是一个普通的数据传输管道,在Nginx中它仅仅是用着master发送指令给worker的一个管道,master借此channel来告诉worker进程该做什么了,worker却不需要告诉master该做什么,所以是一个单向的通道。

master进程每次发送给worker进程的指令用如下一个结构来完成封装:

typedef struct {
     ngx_uint_t  command;
     ngx_pid_t   pid;
     ngx_int_t   slot;
     ngx_fd_t    fd;
} ngx_channel_t;

这个结构中的4个字段分别是发送的指令、worker进程的pid、worker进程的slot(在ngx_proecsses中的索引)及一个文件描述符。master进程可能会将一个打开的文件描述符发送给worker进程进行读写操作,那么此时就需要填写fd这个字段了。worker进程在收到一个这样的结构数据后,通过判断command的值来采取相应的动作;command就是master给worker下达的命令。

 


 

master进程用于处理SIGCHLD信号的函数ngx_reap_children中就有向worker进程发送关闭channel的指令,我们看看这个例子是怎么做的。

    ch.command = NGX_CMD_CLOSE_CHANNEL;
    ch.fd = -1;
    ch.pid = ngx_processes[i].pid;
    ch.slot = i;
    ngx_write_channel(ngx_processes[n].channel[0],
                                      &ch, sizeof(ngx_channel_t), cycle->log);

这几行代码是我从ngx_reap_children函数中拼凑起来的,所以看上去好像有点奇怪,不那么顺畅;但却清晰的给我们展现了master进程怎么给一个worker进程发送指令,此处发送的指令时NGX_CMD_CLOSE_CHANNEL。发送指令的函数ngx_write_channel是利用sendmsg来完成,《Unix网络编程》可以详细了解sendmsg。

 

worker进程在调用ngx_worker_process_init进行初始化的时候,使用了如下两行代码将channel放到epoll等事件处理模块中。

    if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT,
                              ngx_channel_handler)
        == NGX_ERROR)
    {
        /* fatal */
        exit(2);
    }
当master进程发来指令后,就调用ngx_channel_handler函数进行事件的响应。下面浓缩的代码给出了ngx_channel_handler所做的事情。

		/*
			读出master进程发送给过来的指令数据, ngx_read_channel
                        是利用recvmsg实现,详细介绍见《unix网络编程》
		 */
        n = ngx_read_channel(c->fd, &ch, sizeof(ngx_channel_t), ev->log);
		/*
		   	判断command的值,从而采取具体的动作,代码意图都写得很明显,
                           就不在这里多说了。
		*/
        switch (ch.command) {
        case NGX_CMD_QUIT:
            ngx_quit = 1;
            break;
        case NGX_CMD_TERMINATE:
            ngx_terminate = 1;
            break;
        case NGX_CMD_REOPEN:
            ngx_reopen = 1;
            break;
        case NGX_CMD_OPEN_CHANNEL:
            ngx_processes[ch.slot].pid = ch.pid;
            ngx_processes[ch.slot].channel[0] = ch.fd;
            break;
        case NGX_CMD_CLOSE_CHANNEL:
            if (close(ngx_processes[ch.slot].channel[0]) == -1) {
                ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
                              "close() channel failed");
            }
            ngx_processes[ch.slot].channel[0] = -1;
            break;
        }


参考: http://www.alidata.org/archives/1174
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nginx进程管理 的相关文章

随机推荐

  • [carla]关于odometry坐标中的角度坐标系 以及 到地图的映射问题

    1 获取车辆的Odometry原始信息 在carla中 通过订阅 carla ego vecle odometry 可以查看车辆的全局位置信息 例如 gt header seq 118872 stamp secs 5946 nsecs 57
  • C++ 二维数组vector如何添加空行

    在制作BP神经网络时 需要给vector添加一个空行 自己根据直觉进行了以下试探 发现并没有问题 include
  • C++析构函数的自动调用问题

    首先要明确一点 系统只会自动释放栈内空间 而堆内空间需要用户自己维护 C 中 除了new来的空间存放在堆内 其他均存放在栈中 当单纯的创建对象的时候 对象存放在栈中 此时在程序块的 后面 系统会自动调用析构函数 释放掉栈空间 但是 如果创建
  • 简述浏览器渲染流程

    近期的项目涉及到了前端的一系列知识 所以就简单的总结一下 因为不是前端人员 相关的概念可能不会分析的很深 如果说法有问题 希望路过的大佬们多多指教 下面说的大多是自己的理解 尽可能简洁又通俗 说到浏览器渲染 一个重要前提应该就是dom do
  • Ubuntu启动ftp服务

    Ubuntu启动ftp服务 1 安装vsftpd sudo apt get install vsftpd 2 修改ftp配置文件 注意要加sudo 否则无权限更改 sudo vi etc vsftpd conf 将 local enable
  • MySQL数据库入门实战教程

    目录 前言 一 创建建数据库 创建建数据表 查看数据库 查看数据表 二 新增 修改 删除表记录 三 基础查询 where子句查询 1 基础查询 2 WHERE子句查询 3 Like模糊查询 四 分组查询 聚合函数 排序查询 4 排序查询 5
  • 子集和问题

    子集和问题 描述 Description 问题描述 子集和问题的一个实例为 S t 其中 S x1 x2 xn 是一个正整数的集合 c是一个正整数 子集和问题判定是否存在S的一个子集S1 使得子集S1和等于c 编程任务 对于给定的正整数的集
  • 【数据结构】堆的实现(简单易懂,超级详细!!!)

    目录 1 堆的概念及结构 概念 规律 2 堆的实现 2 1结构设计 2 2接口实现 2 3 初始化 2 4堆的向下调整算法 主要思想 涉及问题 代码实现 2 5建堆 思想 代码实现 建堆的时间复杂度 2 6 堆的向上调整算法 主要思想 涉及
  • PyQt5常用模块、类、控件

    一 常用模块 QtCore 包含非核心的GUI功能 此模块用于处理时间 文件和目录 各种数据类型 流 URL MIME类型 线程或进程 QtGui 包括窗口系统集成 事件处理 二维图形 基本成像 字体和文本 QtWidgets 基本控件都位
  • Yii Framework 开发教程(33) Zii组件-Accordion示例

    Zii组件中包含了一些基于JQuery的UI组件 这些UI组件定义在包zii widgets jui中 包括CJuiAccordion CJuiAutoComplete CJuiDatePicker等 本篇介绍CJuiAccordion 显
  • ARM的37个寄存器和异常处理机制详解

    1 ARM的37个寄存器 ARM的37个寄存器中 30个寄存器是 通用 1个固定用作PC 程序控制寄存器 一个固定用作CPSR 程序状态寄存器 5个固定用作5种异常模式下的SPSR 程序状态保存寄存器 特别注意user模式和sys模式共用寄
  • SPI总线的原理

    一 简介 SPI Serial Peripheral Interface 串行外围设备接口 是Motorola公司提出的一种同步串行接口技术 是一种高速 全双工 同步通信总线 在芯片中只占用四根管脚用来控制及数据传输 广泛用于EEPROM
  • Unity PackageManager一直加载不到信息的解决方法

    关闭unity 断开网络连接 打开unity 这时候会加载出来 连接网络开始install
  • 牛客网——两数之和

    题目描述 给出一个整数数组 请在数组中找出两个加起来等于目标值的数 你给出的函数twoSum 需要返回这两个数字的下标 index1 index2 需要满足 index1 小于index2 注意 下标是从1开始的 假设给出的数组中只存在唯一
  • 解决error: subprocess-exited-with-error

    运行 pip install upgrade setuptools 即可解决
  • Playwright解决永久保存下载文件

    Playwright默认在浏览器关闭的时候 所有的临时文件都将删除 无论你是自定义位置还是默认位置 那么如何正确下载对应的文件呢 废话不多说 大家直接看以下代码即可 这里还是告诫大家一下 多研究官网的API文档 别学我慌慌张张去搞了 啥都没
  • QTP的那些事--项目实践操作案例代码--查询操作

    1 一下的代码记录的是我对于一个查询操作的自动化的思路 遗憾的是预期的结果可能需要手动输入到datatable中 以后逐步完善 将所有的预期值都自动输入到excel中 登陆系统 2 3 RunAction login loginsystem
  • HDS 存储名词解释

    DKU 扩展柜 DKC 控制柜 DKA 后端端口 CHA 前端端口 CSW 交换卡 SVP 内置服务PC 另一个含义是服务程序 CM Cache Memory数据内存 SM Share Memory共享内存 HDU Hard Disk Un
  • npoi全国计算机编程,NPOI 导入Excel

    NPOI 导入Excel public static List GetExcelToList string excelfileName List list new List ISheet sheet null PropertyInfo pr
  • Nginx进程管理

    Nginx进程管理 1 Nginx进程管理之master进程 监控进程充当整个进程组与用户的交互接口 同时对进程进行监护 它不需要处理网络事件 不负责业务的执行 只会通过管理worker进程来实现重启服务 平滑升级 更换日志文件 配置文件实