编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享。转载请注明来自高可用架构公众号「ArchNotes」。
导读:很多工程师及架构师都希望了解及掌握高性能服务器开发,阅读优秀源代码是一种有效的方式,nginx 是业界知名的高性能 Web 服务器实现,如何有效的阅读及理解 nginx?本文用图解的方式帮助大家来更好的阅读及理解 nginx 关键环节的实现。
陈科,十年行业从业经验,曾在浙江电信、阿里巴巴、华为、五八同城任开发工程及架构师等职,目前负责河狸家后端架构和运维。博客地址:http://www.dumpcache.com/wiki/doku.php
图一:nginx 启动及内存申请过程分析
任何程序都离不开启动和配置解析。ngx 的代码离不开 ngx_cycle_s 和 ngx_pool_s 这两个核心数据结构,所以我们在启动之前先来分析下。
内存申请过程分为 3 步
假如申请的内存小于当前块剩余的空间,则直接在当前块中分配。
假如当前块空间不足,则调用 ngx_palloc_block 分配一个新块然后把新块链接到 d.next 中,然后分配数据。
假如申请的大小大于当前块的最大值,则直接调用 ngx_palloc_large 分配一个大块,并且链接到 pool→large 链表中
内存分配过程图解如下
(图片来自网络)
为了更好理解上面的图,可以参看文末附 2 的几个数据结构:ngx_pool_s 及ngx_cycle_s。
知道了这两个核心数据结构之后,我们正式进入 main 函数,main 函数执行过程如下
调用 ngx_get_options() 解析命令参数;
调用 ngx_time_init() 初始化并更新时间,如全局变量ngx_cached_time;
调用 ngx_log_init() 初始化日志,如初始化全局变量 ngx_prefix,打开日志文件 ngx_log_file.fd;
清零全局变量 ngx_cycle,并为 ngx_cycle.pool 创建大小为 1024B 的内存池;
调用 ngx_save_argv() 保存命令行参数至全局变量 ngx_os_argv、ngx_argc、ngx_argv 中;
调用 ngx_process_options() 初始化 ngx_cycle 的 prefix, conf_prefix, conf_file, conf_param 等字段;
调用 ngx_os_init() 初始化系统相关变量,如内存页面大小 ngx_pagesize , ngx_cacheline_size , 最大连接数 ngx_max_sockets 等;
调用 ngx_crc32_table_init() 初始化 CRC 表 ( 后续的 CRC 校验通过查表进行,效率高 );
调用 ngx_add_inherited_sockets() 继承 sockets:
初始化每个 module 的 index,并计算 ngx_max_module;
调用 ngx_init_cycle() 进行初始化;
若有信号,则进入 ngx_signal_process() 处理;
调用 ngx_init_signals() 初始化信号;主要完成信号处理程序的注册;
若无继承 sockets,且设置了守护进程标识,则调用 ngx_daemon() 创建守护进程;
调用 ngx_create_pidfile() 创建进程记录文件;( 非 NGX_PROCESS_MASTER = 1 进程,不创建该文件 )
进入进程主循环;
在 main 函数执行过程中,有一个非常重要的函数 ngx_init_cycle,这个阶段做了什么呢?下面分析 ngx_init_cycle,初始化过程:
更新 timezone 和 time
创建内存池
给 cycle 指针分配内存
保存安装路径,配置文件,启动参数等
初始化打开文件句柄
初始化共享内存
初始化连接队列
保存 hostname
调用各 NGX_CORE_MODULE 的 create_conf 方法
解析配置文件
调用各NGX_CORE_MODULE的init_conf方法
打开新的文件句柄
创建共享内存
处理禁用词语socket
创建socket进行禁用词语
调用各模块的init_module
图二:master 进程工作原理及工作工程
以下过程都在ngx_master_process_cycle 函数中进行,启动过程:
暂时阻塞所有 ngx 需要处理的信号
设置进程名称
启动工作进程
启动cache管理进程
进入循环开始处理相关信号
master 进程工作过程
设置 work 进程退出等待时间
挂起,等待新的信号来临
更新时间
如果有 worker 进程因为 SIGCHLD 信号退出了,则重启 worker 进程
master 进程退出。如果所有 worker 进程都退出了,并且收到 SIGTERM 信号或 SIGINT 信号或 SIGQUIT 信号等,master 进程开始处理退出
处理SIGTERM信号
处理SIGQUIT信号,并且关闭socket
处理SIGHUP信号
平滑升级,重启worker进程
不是平滑升级,需要重新读取配置
-
处理重启 10处理SIGUSR1信号 重新打开所有文件 11处理SIGUSR2信号 热代码替换,执行新的程序 12处理SIGWINCH信号,不再处理任何请求
图三:worker 进程工作原理
启动通过执行 ngx_start_worker_processes 函数:
先在 ngx_processes 数组中找坑位if (ngx_processes[s].pid == -1) {break;}
进程相关结构初始化工作
创建管道 ( socketpair )
设置管道为非阻塞模式
设置管道为异步模式
设置异步 I/O 的所有者
如果 exec 执行的时候本 fd 不传递给 exec 创建的进程
fork 创建子进程。创建成功后,子进程执行相关逻辑:proc(cycle, data)。
设置 ngx_processes[s] 相关属性
通知子进程新进程创建完毕ngx_pass_open_channel(cycle, |