IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Nginx源码分析——worker进程源码与工作原理(一) -> 正文阅读

[系统运维]Nginx源码分析——worker进程源码与工作原理(一)

一、说明

一般说到nginx整体架构的话,会用这样的架构图进行概述。worker进程的运行模块是整个nginx最为核心的代码模块。还有下面都是基于Unix操作系统的,windows的可以了解下。
二、核心问题
由于worker的工作原理这个命题比较大,我们就列了几个核心问题去解决他,解决了这些核心问题,那么基本上面worker进程是如何工作的就知道了。
问题一:worker进程是如何启动的?
问题二:worker进程里面的数据结构是怎么样的?
问题三:worker进程是如何实现请求监听的?
问题四:worker进程是如何实现反向代理的?
如果大家有什么好的想法,或者想知道的也可以留言。
三、worker进程启动
./src/os/unix/ngx_process_cycle.c
ngx_start_worker_processes(cycle, ccf->worker_processes,
?? ??? ??? ??? ??? ??? ??? ?NGX_PROCESS_RESPAWN);
以Nginx首次启动为例子,看看nginx启动worker进程的时候做了什么。这里ccf->worker_processes先不考虑多个的情况,先看看只有一个进程的时候。
方法里面的内容很简单,一看就知道。
ngx_int_t??i;
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes");

for (i = 0; i < n; i++) {
????ngx_spawn_process(cycle, ngx_worker_process_cycle,
??????????????????????(void *) (intptr_t) i, "worker process", type);
????ngx_pass_open_channel(cycle);
}
为了以后我们可以了解更多的信息,我们调整一些error日志级别为debug,修改nginx.conf文件
error_log logs/error.log debug;
reload一下,结合上一章的知识,就可以理解,先看是一个进程然后结束一个进程。过程中都会有信号发出,由master进程来处理。
./src/os/unix/ngx_process.c
ngx_spawn_process(cycle, ngx_worker_process_cycle,
??????????????????????(void *) (intptr_t) i, "worker process", type);
方法cycle数据结构,ngx_worker_process_cycle方法,i为0,type为-3,这些作为入参。这个方法主要完成的就是生成子进程,并且进程创建完成之后执行ngx_worker_process_cycle。
这里就会涉及到ngx_process_t数据结构,我们先了解下。
./src/os/unix/ngx_process.h
typedef struct {
????ngx_pid_t???????????pid; //进程的pid
????int?????????????????status;//描述子进程的状态
????ngx_socket_t????????channel[2];//进程的channel,通过socketpair来创建,套接字所对应的句柄?? ?

????ngx_spawn_proc_pt???proc;//进程创建完成调用的函数
????void???????????????*data;//proc函数入参
????char???????????????*name;//进程名

????unsigned????????????respawn:1;?? ?? ? //是否需要重新由master启动
????unsigned????????????just_spawn:1;? ? //是否是一个新的进程
????unsigned????????????detached:1;? ? //热部署中
????unsigned????????????exiting:1;? ? //进程状态退出中
????unsigned????????????exited:1;?? ??//已退出
} ngx_process_t;
开始看看创建子进程的代码
?
    if (respawn >= 0) {
????????s = respawn;


????} else {
????????for (s = 0; s < ngx_last_process; s++) {
????????????if (ngx_processes[s].pid == -1) {
????????????????break;
????????????}
????????}


????????if (s == NGX_MAX_PROCESSES) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
??????????????????????????"no more than %d processes can be spawned",
??????????????????????????NGX_MAX_PROCESSES);
????????????return NGX_INVALID_PID;
????????}
????}
里面有两个全局变量,ngx_last_process记录当前进程的数量,ngx_processes保存所有子进程的数组。
如果ngx_processes数组中有进程失效了,那么就复用对应的进程的位置。
并且进程数量不能超过nginx最大进程数。
    if (respawn != NGX_PROCESS_DETACHED) {

????????/* Solaris 9 still has no AF_LOCAL */

????????if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1)
????????{
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????"socketpair() failed while spawning \"%s\"", name);
????????????return NGX_INVALID_PID;
????????}
这块的判断NGX_PROCESS_DETACHED表示是不是在进行nginx热升级,在上一章提到,nginx提供了热升级的能力,如果是热升级的新的worker进程与原来的master进程是没有关系的。这里我们先不去关注,主要是下面这块代码,生成套接字这块。
socketpair函数生成一个可以相互读写的套接字,生成成功之后,channel[0]与channel[1]可以进行双向的通信。
?        if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????ngx_nonblocking_n " failed while spawning \"%s\"",
??????????????????????????name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}
这是定义了对套接字的io方式—— ioctl(FIONBIO),为一个非阻塞io。如果设置失败,就关闭掉这个套接字。
        if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????ngx_nonblocking_n " failed while spawning \"%s\"",
??????????????????????????name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}

对于ngx_processes[s].channel[1]这个套接字也进行相同的操作。
????????if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????"ioctl(FIOASYNC) failed while spawning \"%s\"", name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}
ioctl(ngx_processes[s].channel[0]设置 ioctl(FIOASYNC)设置为信号驱动异步模式——收到SIGIO和SIGPOLL信号才能进行IO。
????????if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????"fcntl(F_SETOWN) failed while spawning \"%s\"", name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}
通过fcntl(F_SETOWN)让ngx_processes[s].channel[0]属于ngx_pid这个进程。既channel[0]这个套接字是属于master进程d的。
????????if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
???????????????????????????name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}

????????if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {
????????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????????"fcntl(FD_CLOEXEC) failed while spawning \"%s\"",
???????????????????????????name);
????????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????????return NGX_INVALID_PID;
????????}
当fork子进程后,仍然可以使用channel[0],channel[1]的fd。但执行exec后系统就会字段关闭子进程中的fd了。
master进程可以向channel[0]写入事件之后,channel[1]就可以接收到这个事件并且是在子进程中进行。
?? ?ngx_channel = ngx_processes[s].channel[1];
}

ngx_process_slot = s;
将ngx_channel指向channel[1]。
ngx_process_slot中保存的是现在子进程的数量。
下面的代码开始创建子进程
pid = fork();
在fork之后,就是生成一个新的进程,子进程里面的数据结构完全复制了父进程的数据结构。
在fork之后分成了两个进程同时在执行,也就是说下面代码的逻辑父进程与子进程都会在跑。
能够区分他们的就是父进程里面pid返回的是子进程的进程号,子进程返回的pid为0。看下面的代码:
???switch (pid) {

????case -1:
????????ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
??????????????????????"fork() failed while spawning \"%s\"", name);
????????ngx_close_channel(ngx_processes[s].channel, cycle->log);
????????return NGX_INVALID_PID;

????case 0:
????????ngx_parent = ngx_pid;
????????ngx_pid = ngx_getpid();
????????proc(cycle, data);
????????break;

????default:
????????break;
????}

?

就是说如果是父进程就走的是default,什么都不做,如果是子进程走的就是case 0。
在启动的时候,由于fork出来的内存数据是与主进程完全相同的,所有ngx_pid为主进程的pid,所以用ngx_parent保存ppid,在去获取子进程的pid。
proc是外部传入的函数,即是proc(cycle, data)执行的时候就是执行ngx_worker_process_cycle(cycle, data)函数。
./src/os/unix/ngx_process_cycle.c
ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
这个方法里面有一个无条件循环,这样这个子进程就在运行了。具体这块逻辑,在我们解决问题二、三、四的时候需要去分析的。
剩下的代码就简单了,都是由父进程执行的
????ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start %s %P", name, pid);

????ngx_processes[s].pid = pid;
????ngx_processes[s].exited = 0;

????if (respawn >= 0) {
????????return pid;
????}

????ngx_processes[s].proc = proc;
????ngx_processes[s].data = data;
????ngx_processes[s].name = name;
????ngx_processes[s].exiting = 0;

????switch (respawn) {

????case NGX_PROCESS_NORESPAWN:
????????ngx_processes[s].respawn = 0;
????????ngx_processes[s].just_spawn = 0;
????????ngx_processes[s].detached = 0;
????????break;

????case NGX_PROCESS_JUST_SPAWN:
????????ngx_processes[s].respawn = 0;
????????ngx_processes[s].just_spawn = 1;
????????ngx_processes[s].detached = 0;
????????break;
????case NGX_PROCESS_RESPAWN:
????????ngx_processes[s].respawn = 1;
????????ngx_processes[s].just_spawn = 0;
????????ngx_processes[s].detached = 0;
????????break;

????case NGX_PROCESS_JUST_RESPAWN:
????????ngx_processes[s].respawn = 1;
????????ngx_processes[s].just_spawn = 1;
????????ngx_processes[s].detached = 0;
????????break;

????case NGX_PROCESS_DETACHED:
????????ngx_processes[s].respawn = 0;
????????ngx_processes[s].just_spawn = 0;
????????ngx_processes[s].detached = 1;
????????break;
????}

????if (s == ngx_last_process) {
????????ngx_last_process++;
????}

????return pid;
主要还是完善ngx_processes[s],就是对应进程的数据结构,代码不具体讲了。
四、总结
子进程创建的过程中,通过套接字的方式实现了master进程与worker进程之间的通信,ngx_processes中的channel[0]交给了master进程,后续worker进程会监听channel[1]管道。
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-09-10 11:14:59  更:2021-09-10 11:17:09 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 14:09:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码