【Nginx 源码学习】Nginx 架构设计与主流程分析

upstream 的流程后面单出一篇。


启动流程


在这里插入图片描述

main 入口

int ngx_cdecl main(int argc, char *const *argv)

{

//1、声明一波对象,打印一些基本信息,初始化一些东西

//2、通过 ngx_save_argv(&init_cycle, argc, argv),将 argv 参数拷贝到一份内存中

if (ngx_save_argv(&init_cycle, argc, argv) != NGX_OK) {

return 1;

}

/*

argv 参数是什么?就是管理者想nginx传入的信号,如 nginx -s reload之类的

在后面还能找到信号捕捉函数,我已经把源码也铺开了,后面再看

*/

//然后继续init

//3、信号捕捉

if (ngx_signal) {

return ngx_signal_process(cycle, ngx_signal);

}

//继续配置

/…/

#if !(NGX_WIN32)

if (ngx_init_signals(cycle->log) != NGX_OK) {

return 1;

}

if (!ngx_inherited && ccf->daemon) {

// 4、创建守护进程

if (ngx_daemon(cycle->log) != NGX_OK) {

return 1;

}

ngx_daemonized = 1;

}

if (ngx_inherited) {

ngx_daemonized = 1;

}

#endif

/…/

//5、启动进程循环咯

if (ngx_process == NGX_PROCESS_SINGLE) {

ngx_single_process_cycle(cycle);

} else {

ngx_master_process_cycle(cycle);

}

return 0;

}


ngx_master_process_cycle 进入多进程模式

/**

  • Nginx的多进程运行模式

*/

void ngx_master_process_cycle(ngx_cycle_t *cycle) {

char *title;

u_char *p;

size_t size;

ngx_int_t i;

ngx_uint_t n, sigio;

sigset_t set;

struct itimerval itv;

ngx_uint_t live;

ngx_msec_t delay;

ngx_listening_t *ls;

ngx_core_conf_t *ccf;

/* 设置能接收到的信号 */

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”);

}

sigemptyset(&set);

size = sizeof(master_process);

for (i = 0; i < ngx_argc; i++) {

size += ngx_strlen(ngx_argv[i]) + 1;

}

/* 保存进程标题 */

title = ngx_pnalloc(cycle->pool, size);

if (title == NULL) {

/* fatal */

exit(2);

}

p = ngx_cpymem(title, master_process, sizeof(master_process) - 1);

for (i = 0; i < ngx_argc; i++) {

*p++ = ’ ';

p = ngx_cpystrn(p, (u_char *) ngx_argv[i], size);

}

ngx_setproctitle(title);

/* 获取核心配置 ngx_core_conf_t */

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);

ngx_new_binary = 0;

delay = 0;

sigio = 0;

live = 1;

/* 主线程循环 */

for (;😉 {

/* delay用来设置等待worker推出的时间,master接受了退出信号后,

  • 首先发送退出信号给worker,而worker退出需要一些时间*/

if (delay) {

if (ngx_sigalrm) {

sigio = 0;

delay *= 2;

ngx_sigalrm = 0;

}

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,

“termination cycle: %M”, delay);

itv.it_interval.tv_sec = 0;

itv.it_interval.tv_usec = 0;

itv.it_value.tv_sec = delay / 1000;

itv.it_value.tv_usec = (delay % 1000) * 1000;

if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {

ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,

“setitimer() failed”);

}

}

ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, “sigsuspend”);

/* 等待信号的到来,阻塞函数 */

sigsuspend(&set);

ngx_time_update();

ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,

“wake up, sigio %i”, sigio);

/* 收到了SIGCHLD信号,有worker退出(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);

}

if (!live && (ngx_terminate || ngx_quit)) {

ngx_master_process_exit(cycle);

}

/* 中止进程 */

if (ngx_terminate) {

if (delay == 0) {

delay = 50;

}

if (sigio) {

sigio–;

continue;

}

sigio = ccf->worker_processes + 2 /* cache processes */;

if (delay > 1000) {

ngx_signal_worker_processes(cycle, SIGKILL);

} else {

ngx_signal_worker_processes(cycle,

ngx_signal_value(NGX_TERMINATE_SIGNAL));

}

continue;

}

/* 退出进程 */

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;

}

/* 收到SIGHUP信号 重新初始化配置 */

if (ngx_reconfigure) {

ngx_reconfigure = 0;

if (ngx_new_binary) {

ngx_start_worker_processes(cycle, ccf->worker_processes,

NGX_PROCESS_RESPAWN);

ngx_start_cache_manager_processes(cycle, 0);

ngx_noaccepting = 0;

continue;

}

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reconfiguring”);

cycle = ngx_init_cycle(cycle);

if (cycle == NULL) {

cycle = (ngx_cycle_t *) ngx_cycle;

continue;

}

ngx_cycle = cycle;

ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx,

ngx_core_module);

ngx_start_worker_processes(cycle, ccf->worker_processes,

NGX_PROCESS_JUST_RESPAWN);

ngx_start_cache_manager_processes(cycle, 1);

/* allow new processes to start */

ngx_msleep(100);

live = 1;

ngx_signal_worker_processes(cycle,

ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

}

/* 当ngx_noaccepting==1时,会把ngx_restart设为1,重启worker */

if (ngx_restart) {

ngx_restart = 0;

ngx_start_worker_processes(cycle, ccf->worker_processes,

NGX_PROCESS_RESPAWN);

ngx_start_cache_manager_processes(cycle, 0);

live = 1;

}

/* 收到SIGUSR1信号,重新打开log文件 */

if (ngx_reopen) {

ngx_reopen = 0;

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “reopening logs”);

ngx_reopen_files(cycle, ccf->user);

ngx_signal_worker_processes(cycle,

ngx_signal_value(NGX_REOPEN_SIGNAL));

}

/* SIGUSER2,热代码替换 */

if (ngx_change_binary) {

ngx_change_binary = 0;

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “changing binary”);

ngx_new_binary = ngx_exec_new_binary(cycle, ngx_argv);

}

/* 收到SIGWINCH信号不在接受请求,worker退出,master不退出 */

if (ngx_noaccept) {

ngx_noaccept = 0;

ngx_noaccepting = 1;

ngx_signal_worker_processes(cycle,

ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

}

}

}

抽出主要流程看的比较清楚:

void ngx_master_process_cycle(ngx_cycle_t *cycle) {

···

// 启动各个worker进程

ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN);

···

for (;😉 {

···

if (···) {

// 这里主要是向各个进程发送命令

ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL));

}

}

}


ngx_start_worker_processes 创建工作进程

先了解一下 ngx_channel_t,有点重要哈:

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

typedef struct {

// 传递的 TCP 消息中的命令

ngx_uint_t command;

// 进程 ID,一般是发送命令方的进程 ID

ngx_pid_t pid;

// 表示发送命令方在 ngx_processes 进程数组间的序号

ngx_int_t slot;

// 通信的套接字句柄

ngx_fd_t fd;

}ngx_channel_t;

Nginx 针对 command 成员定义了如下命令:

// 打开频道,使用频道这种方式通信前必须发送的命令

#define NGX_CMD_OPEN_CHANNEL 1

// 关闭已经打开的频道,实际上也就是关闭套接字

#define NGX_CMD_CLOSE_CHANNEL 2

// 要求接收方正常地退出进程

#define NGX_CMD_QUIT 3

// 要求接收方强制地结束进程

#define NGX_CMD_TERMINATE 4

// 要求接收方重新打开进程已经打开过的文件

#define NGX_CMD_REOPEN 5

/**

  • 创建工作进程

*/

static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,

ngx_int_t type) {

ngx_int_t i;

ngx_channel_t ch;

ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, “start worker processes”);

ngx_memzero(&ch, sizeof(ngx_channel_t));

ch.command = NGX_CMD_OPEN_CHANNEL;

/* 循环创建工作进程 默认ccf->worker_processes=8个进程,根据CPU个数决定 */

for (i = 0; i < n; i++) {

/* 打开工作进程 (ngx_worker_process_cycle 回调函数,主要用于处理每个工作线程)*/

ngx_spawn_process(cycle, ngx_worker_process_cycle,

(void *) (intptr_t) i, “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);

}

}

简写一下:

static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) {

···

ngx_channel_t ch;

···

ch.command = NGX_CMD_OPEN_CHANNEL; //当前是新建了一个进程

for (i = 0; i < n; i++) {

//spawn,生成一个子进程

// ngx_worker_process_cycle:该子进程所进行的事件循环,这里先不管它什么循环

// worker进程在一个无限for循环中,不断的检查相应的事件模型中是否存在对应的事件,

// 然后将accept事件和read、write事件分开放入两个队列中,最后在事件循环中不断的处理事件

ngx_spawn_process(cycle, ngx_worker_process_cycle,

(void *) (intptr_t) i, “worker process”, type);

// 下面的这段代码的主要作用是将新建进程这个事件通知到其他的进程,

// 其就会向ngx_processes数组的每个进程的channel[0]上写入当前广播的事件,也即这里的ch,

// 因为子进程之间也需要通信

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_spawn_process fork工作进程

这里啊,需要关注一下:

typedef struct {

// socketpair 创建的套接字对

ngx_socket_t channel[2];

}ngx_processes_t;


ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) {

···

// 在ngx_processes数组中存储了当前创建的所有进程,而ngx_last_process理解为end,

//只不过ngx_processes中记录的进程有可能有部分已经失效了。

//当前循环就是从头开始查找是否有某个进程已经失效了,

//如果已经失效了,则复用该进程位置,否则直接使用ngx_last_process所指向的位置

for (s = 0; s < ngx_last_process; s++) {

if (ngx_processes[s].pid == -1) {

break;

}

}

// 这里说明所创建的进程数达到了最大限度

if (s == NGX_MAX_PROCESSES) {

···

return NGX_INVALID_PID;

}

// NGX_PROCESS_DETACHED标志表示当前fork出来的进程与原来的父进程没有任何关系,比如进行nginx升级时,

// 新生成的master进程就与原先的master进程没有关系

if (respawn != NGX_PROCESS_DETACHED) {

//这里为什么采用sockpair?

/* 这里的socketpair()方法的主要作用是生成一对套接字流,用于主进程和子进程的通信,

这一对套接字会存储在ngx_processes[s].channel中,本质上这个字段是一个长度为2的整型数组。

在主进程和子进程 进行通信的之前,主进程会关闭其中一个,而子进程会关闭另一个,

然后相互之间往未关闭的另一个文件描述符中写入或读取数据即可实现通信。

AF_UNIX表示当前使用的是UNIX文件形式的socket地址族SOCK_STREAM指定了当前套接字建立的通信方式是管道流,

并且这个管道流是双向的,即管道双方都可以进行读写操作第三个参数protocol必须为0。

*/

if (socketpair(AF_UNIX, SOCK_STREAM, 0, ngx_processes[s].channel) == -1) {

···

return NGX_INVALID_PID;

}

···

// 将ngx_processes[s].channel[0]设置为非阻塞模式

if (ngx_nonblocking(ngx_processes[s].channel[0]) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

// 将ngx_processes[s].channel[1]设置为非阻塞模式

if (ngx_nonblocking(ngx_processes[s].channel[1]) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

on = 1;

// 将ngx_processes[s].channel[0]套接字管道设置为异步模式

if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

// 当前还处于主进程中,这里的ngx_pid指向了主进程的进程id,当前方法的作用主要是将

// ngx_processes[s].channel[0]的操作权限设置给主进程,也就是说主进程通过向

// ngx_processes[s].channel[0]写入和读取数据来与子进程进行通信

if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

// FD_CLOEXEC表示当前指定的套接字管道在子进程中可以使用,但是在execl()执行的程序中不可使用

if (fcntl(ngx_processes[s].channel[0], F_SETFD, FD_CLOEXEC) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

// FD_CLOEXEC表示当前指定的套接字管道在子进程中可以使用,但是在execl()执行的程序中不可使用

if (fcntl(ngx_processes[s].channel[1], F_SETFD, FD_CLOEXEC) == -1) {

···

ngx_close_channel(ngx_processes[s].channel, cycle->log);

return NGX_INVALID_PID;

}

// ngx_processes[s].channel[1]是用于给子进程监听相关事件使用的,当父进程向

// ngx_processes[s].channel[0]发布事件之后,

// ngx_processes[s].channel[1]中就会接收到对应的事件,从而进行相应的处理

ngx_channel = ngx_processes[s].channel[1];

} else {

// 如果是NGX_PROCESS_DETACHED模式,则表示当前是另外新起的一个master进程,因而将其管道值都置为-1

ngx_processes[s].channel[0] = -1;

ngx_processes[s].channel[1] = -1;

}

ngx_process_slot = s;

// fork()产生一个新的进程

pid = fork();

switch (pid) {

case -1:

// fork出错

···

return NGX_INVALID_PID;

case 0:

// 子进程执行的分支,这里的proc()方法是外部传进来的,也就是说,当前方法只是创建一个新的进程,

// 具体的进程处理逻辑,将交由外部代码块进行定义ngx_getpid()方法获取的就是当前新创建的子进程的进程id

ngx_pid = ngx_getpid();

proc(cycle, data);

break;

default:

// 父进程会走到这里

break;

}

···

// 父进程会走到这里,当前的pid是fork()之后父进程得到的新创建的子进程的pid

ngx_processes[s].pid = pid;

ngx_processes[s].exited = 0;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

Java架构进阶面试及知识点文档笔记

这份文档共498页,其中包括Java集合,并发编程,JVM,Dubbo,Redis,Spring全家桶,MySQL,Kafka等面试解析及知识点整理

image

Java分布式高级面试问题解析文档

其中都是包括分布式的面试问题解析,内容有分布式消息队列,Redis缓存,分库分表,微服务架构,分布式高可用,读写分离等等!

image

互联网Java程序员面试必备问题解析及文档学习笔记

image

Java架构进阶视频解析合集
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-bHwvSDEa-1713402005366)]

[外链图片转存中…(img-osyuHpLR-1713402005366)]

[外链图片转存中…(img-W6gk8FlJ-1713402005366)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

Java架构进阶面试及知识点文档笔记

这份文档共498页,其中包括Java集合,并发编程,JVM,Dubbo,Redis,Spring全家桶,MySQL,Kafka等面试解析及知识点整理

[外链图片转存中…(img-QwiXZb8o-1713402005367)]

Java分布式高级面试问题解析文档

其中都是包括分布式的面试问题解析,内容有分布式消息队列,Redis缓存,分库分表,微服务架构,分布式高可用,读写分离等等!

[外链图片转存中…(img-lNlJRs6Z-1713402005367)]

互联网Java程序员面试必备问题解析及文档学习笔记

[外链图片转存中…(img-HBdib8i1-1713402005367)]

Java架构进阶视频解析合集
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值