编者按:高可用架构分享及传播在架构领域具有典型意义的文章,本文由陈科在高可用架构群分享。转载请注明来自高可用架构公众号「ArchNotes」。 导读:很多工程师及架构师都希望了解及掌握高性能服务器开发,阅读优秀源代码是一种有效的方式,nginx 是业界知名的高性能 Web 服务器实现,如何有效的阅读及理解 nginx?本文用图解的方式帮助大家来更好的阅读及理解 nginx 关键环节的实现。
图一:nginx 启动及内存申请过程分析任何程序都离不开启动和配置解析。ngx 的代码离不开 ngx_cycle_s 和 ngx_pool_s 这两个核心数据结构,所以我们在启动之前先来分析下。 内存申请过程分为 3 步
内存分配过程图解如下 (图片来自网络) 为了更好理解上面的图,可以参看文末附 2 的几个数据结构:ngx_pool_s 及 ngx_cycle_s。 知道了这两个核心数据结构之后,我们正式进入 main 函数,main 函数执行过程如下
在 main 函数执行过程中,有一个非常重要的函数 ngx_init_cycle,这个阶段做了什么呢?下面分析 ngx_init_cycle,初始化过程:
图二:master 进程工作原理及工作工程以下过程都在ngx_master_process_cycle 函数中进行,启动过程:
master 进程工作过程
图三:worker 进程工作原理启动通过执行 ngx_start_worker_processes 函数:
接下来是 ngx_worker_process_cycle worker 进程逻辑
master 和 worker 通信原理为: Nginx 事件机制介绍先看几个主要方法
n = sendmsg(s, &msg, 0); Top of Form Bottom of Form
接下来分析事件模块工作流程 ngx_event模块结构 ngx_events_module 的数据结构如下: ngx_module_t ngx_events_module = { NGX_MODULE_V1, &ngx_events_module_ctx, /* module context */ ngx_events_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; ngx_event 模块初始化 static ngx_command_t ngx_events_commands[] = { { ngx_string('events') , NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS , ngx_events_block, 0, 0, NULL }, ngx_null_command }; 通过 ngx_events_commands 数组可以知道,event 模块初始化函数为 ngx_events_block,该函数工作内容如下:
ngx_core_event模块初始化 ngx_core_event_module 是在 ngx_cycle_init 的时候初始化的: for (i = 0; ngx_modules[i]; i++) { if (ngx_modules[i]->init_module) { if (ngx_modules[i]->init_module(cycle) != NGX_OK) { /* fatal */ exit(1); } } } 我们先来看下 ngx_core_event_module 的结构: ngx_module_t ngx_event_core_module = { NGX_MODULE_V1, &ngx_event_core_module_ctx, /* module context */ ngx_event_core_commands, /* module directives */ NGX_EVENT_MODULE, /* module type */ NULL, /* init master */ ngx_event_module_init, /* init module */ ngx_event_process_init, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; ngx_event_module_init 实现了初始化过程,该过程分以下几个步骤:
事件进程初始化 在工作线程初始化的时候,将会调用 ngx_event_process_init: 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); } } } ngx_event_process_init 该过程分以下几步:
ngx_process_events_and_timers 事件处理开始工作 工作流程如下:
ngx 定时器实现 ngx 的定时器利用了红黑树的实现 ngx 惊群处理 accept_mutex 解决了惊群问题,虽然linux的新内核已经解决了这个问题,但是ngx 是为了兼容。 整体原理图: Nginx 配置解析再补充一下配置解析,Nginx 配置解析最大的亮点是用一个三级指针和 ctx 关联了起来,然后每个模块关注各自的配置专注解析和初始化就行了。 配置文件解析 ngx 在 main 函数执行的时候会调用 ngx_init_cycle,在这个过程中,会进行初始化的几个步骤:
并且把根据模块号存入了 cycle→conf_ctx 中。这个过程主要是进行配置数据结构的初始化。以epoll模块为例:
这个函数一共有以下几个过程:
struct ngx_conf_s { char *name; ngx_array_t *args; ngx_cycle_t *cycle; ngx_pool_t *pool; ngx_pool_t *temp_pool; ngx_conf_file_t *conf_file; ngx_log_t *log; void *ctx; ngx_uint_t module_type; ngx_uint_t cmd_type; ngx_conf_handler_pt handler; char *handler_conf; };
rv = ngx_conf_parse(cf, NULL) ; 在初始化完 http 的上下文之后,继续进行内部的解析逻辑。这样就会调用到 ngx_conf_handler 的下面部分逻辑:
core 模块将会按照配置项的值在这个阶段进行初始化。ngx 的配置架构如下: 整体架构 serv_conf 结构 loc_conf 结构 附1:Nginx 主要数据结构我们可以参考 ngx_connection_s 结构体,在 ngx_connection_s 中保存了链表的指针:ngx_queue_t queue 6 . ngx_hash_t ngx 的 hash 表没有链表,如果找不到则往右继续查找空闲的 bucket。总的初始化 ngx_hash_init 流程即为:
ngx 对内存非常扣,假设了 hash 表不会占用太多的数据和空间,所以采用了这样的方式。 附2:内存分配的数据结构ngx_pool_s 是 ngx 的内存池,每个工作线程都会持有一个,我们来看它的结构: struct ngx_pool_s { ngx_pool_data_t d ; // 数据块 size_t max ; // 小块内存的最大值 ngx_pool_t *current ; // 指向当前内存池 ngx_chain_t *chain ; ngx_pool_large_t *large; // 分配大块内存用,即超过max的内存请求 ngx_pool_cleanup_t *cleanup ; // 挂载一些内存池释放的时候,同时释放的资源 ngx_log_t *log; } ; ngx_pool_data_t 数据结构: typedef struct { u_char *last ; // 当前数据块分配结束位置 u_char *end ; // 数据块结束位置 ngx_pool_t *next ; // 链接到下一个内存池 ngx_uint_t failed ; // 统计该内存池不能满足分配请求的次数 } ngx_pool_data_t ; 然后我们结合 ngx_palloc 方法来看一下内存池的分配原理: void * ngx_palloc (ngx_pool_t *pool, size_t size) { u_char *m; ngx_pool_t *p ; if (size <= pool-="">max) { p = pool->current ; do { m = ngx_align_ptr(p->d.last, NGX_ALIGNMENT) ; if ((size_t) (p->d.end - m) >= size) { p->d.last = m + size ; return m ; } p = p->d.next ; } while (p) ; return ngx_palloc_block(pool, size) ; } return ngx_palloc_large(pool, size) ; } ngx_cycle_s 每个工作进程都会维护一个: struct ngx_cycle_s { void ****conf_ctx ; // 配置上下文数组(含所有模块) ngx_pool_t *pool ; // 内存池 ngx_log_t *log ; // 日志 ngx_log_t new_log ; ngx_connection_t **files ; // 连接文件 ngx_connection_t *free_connections ; // 空闲连接 ngx_uint_t free_connection_n ; // 空闲连接个数 ngx_queue_t reusable_connections_queue ; // 再利用连接队列 ngx_array_t listening ; // 监听数组 ngx_array_t pathes ; // 路径数组 ngx_list_t open_files ; // 打开文件链表 ngx_list_t shared_memory ; // 共享内存链表 ngx_uint_t connection_n ; // 连接个数 ngx_uint_t iles_n ; // 打开文件个数 ngx_connection_t *connections ; // 连接 ngx_event_t *read_events ; // 读事件 ngx_event_t *write_events ; // 写事件 ngx_cycle_t *old_cycle; //old cycle指针 ngx_str_t conf_file; //配置文件 ngx_str_t conf_param; //配置参数 ngx_str_t conf_prefix; //配置前缀 ngx_str_t prefix; //前缀 ngx_str_t lock_file; //锁文件 ngx_str_t hostname; //主机名 }; 附3:Nginx 内存管理 & 内存对齐内存的申请最终调用的是 malloc 函数,ngx_calloc 则在调用 ngx_alloc 后,使用 memset 来填 0。假如自己开发NGX模块,不要直接使用 ngx_malloc/ngx_calloc,可以使用 ngx_palloc 否则还需要自己管理内存的释放。在 ngx_http_create_request 的时候会创建 request 级别的 pool: pool = ngx_create_pool(cscf->request_pool_size, c->log) ; if (pool == NULL) { return NULL; } r = ngx_pcalloc(pool, sizeof(ngx_http_request_t)); if (r == NULL) { ngx_destroy_pool(pool) ; return NULL ; } r->pool = pool ; 在 ngx_http_free_request 释放 request 的时候会调用 ngx_destroy_pool ( pool ) 释放连接。内存对齐,首先在创建 pool 的时候对齐:p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log) 。ngx_memalign(返回基于一个指定 alignment 的大小为 size 的内存空间,且其地址为 alignment 的整数倍,alignment 为 2 的幂。)最终通过:posix_memalign 或 memalign 来申请。
由于公众号文章篇幅关系,以上就是陈科分享的 nginx 源码分析前半部分,关注本公众号可收到后半部分内容。 本文策划邓启明,编辑王杰,审校 Tim Yang,如需第一时间获取高可用架构分享文章,请关注以下公众号。转载请注明来自高可用架构 「ArchNotes」微信公众号及包含以下二维码。 |
|