nginx-源码带读-04-内存管理与进程模型
NGINX 源码带读 第4篇:内存管理与进程模型
本篇目标
理解NGINX的内存管理机制和多进程模型,包括内存池分配器、Slab分配器、共享内存和master-worker架构。
前置知识
- 了解fork()和Copy-on-Write
- 了解信号处理
- 阅读过第1-3篇
1. 内存池:ngx_palloc.c
1.1 设计理念
NGINX使用内存池(memory pool)管理内存。大部分内存分配从池中获取,池销毁时一次性释放所有内存。这避免了频繁的malloc/free和内存泄漏。
1.2 ngx_pool_t 结构
// src/core/ngx_palloc.h
typedef struct {
u_char *last; // 下次分配的起始位置
u_char *end; // 数据块末尾
ngx_pool_t *next; // 下一个数据块
ngx_uint_t failed; // 分配失败次数
} ngx_pool_data_t;
struct ngx_pool_s {
ngx_pool_data_t d; // 当前可分配数据
size_t max; // 小块分配的上限(默认4096)
ngx_pool_t *current; // 当前使用的数据块
ngx_chain_t *chain; // 空闲chain链表
ngx_pool_large_t *large; // 大块分配链表
ngx_pool_cleanup_t *cleanup; // 清理回调链表
ngx_log_t *log; // 日志
};
1.3 分配策略
| 分配大小 | 策略 | 时间复杂度 |
|---|---|---|
| <= max | 从当前数据块中分配(指针移动) | O(1) |
| > max | 直接malloc,加入large链表 | O(1) |
小块分配是O(1)的——只需移动指针。大块分配走系统malloc但注册到large链表。
1.4 三种池
| 池类型 | 生命周期 | 用途 | 默认大小 |
|---|---|---|---|
| 进程池 | 整个worker生命周期 | 连接池、配置 | 16KB |
| 请求池 | 单个请求 | 请求处理期间的所有分配 | 4KB |
| 连接池 | 单个连接 | 连接数据 | - |
1.5 ngx_create_pool()
// src/core/ngx_palloc.c
ngx_pool_t *ngx_create_pool(size_t size, ngx_log_t *log) {
ngx_pool_t *p;
// 分配内存(16字节对齐)
p = ngx_memalign(NGX_POOL_ALIGNMENT, size, log);
if (p == NULL) {
return NULL;
}
// 初始化:pool头部就在第一个数据块内部
p->d.last = (u_char *) p + sizeof(ngx_pool_t); // 跳过pool结构体
p->d.end = (u_char *) p + size;
p->d.next = NULL;
p->d.failed = 0;
// 计算max:min(size - sizeof(ngx_pool_t), NGX_MAX_ALLOC_FROM_POOL)
size -= sizeof(ngx_pool_t);
p->max = (size < NGX_MAX_ALLOC_FROM_POOL) ? size : NGX_MAX_ALLOC_FROM_POOL;
// 初始化其他字段
p->current = p;
p->chain = NULL;
p->large = NULL;
p->cleanup = NULL;
return p;
}
关键设计:pool头部与第一个数据块共存于同一块内存中,减少了分配次数。
1.6 ngx_palloc() —— 小块分配
void *ngx_palloc(ngx_pool_t *pool, size_t size) {
if (size <= pool->max) {
// 小块分配
return ngx_palloc_small(pool, size, pool->clean);
}
// 大块分配
return ngx_palloc_large(pool, size);
}
static void *ngx_palloc_small(ngx_pool_t *pool, size_t size,
ngx_uint_t align) {
u_char *m;
ngx_pool_t *p;
p = pool->current;
do {
m = p->d.last;
if (align) {
// 16字节对齐
m = ngx_align_ptr(m, NGX_POOL_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);
}
1.7 ngx_palloc_block() —— 创建新数据块
static void *ngx_palloc_block(ngx_pool_t *pool, size_t size) {
u_char *m;
size_t psize;
ngx_pool_t *p, *new;
// 新数据块大小与第一个数据块相同
psize = (size_t) (pool->d.end - (u_char *) pool);
m = ngx_memalign(NGX_POOL_ALIGNMENT, psize, pool->log);
if (m == NULL) {
return NULL;
}
new = (ngx_pool_t *) m;
new->d.last = m + sizeof(ngx_pool_data_t); // 跳过data结构体
new->d.end = m + psize;
new->d.next = NULL;
new->d.failed = 0;
// 在所有数据块中分配空间
for (p = pool->current; p->d.next; p = p->d.next) {
if (p->d.failed++ > 4) {
// 优化:跳过失败过多的数据块
pool->current = p->d.next;
}
}
// 链接到链表末尾
p->d.next = new;
// 在新数据块中分配
m = ngx_align_ptr(new->d.last, NGX_POOL_ALIGNMENT);
new->d.last = m + size;
return m;
}
关键优化:当数据块分配失败超过4次时,跳过该数据块,避免重复尝试。
1.8 ngx_palloc_large() —— 大块分配
static void *ngx_palloc_large(ngx_pool_t *pool, size_t size) {
void *p;
ngx_uint_t n;
ngx_pool_large_t *large;
// 直接malloc
p = ngx_alloc(size, pool->log);
if (p == NULL) {
return NULL;
}
// 查找空闲的large节点(最多查找4个)
n = 0;
for (large = pool->large; large; large = large->next) {
if (large->alloc == NULL) {
large->alloc = p;
return p;
}
if (n++ > 3) {
break; // 优化:避免遍历过多节点
}
}
// 创建新的large节点(从pool小块分配)
large = ngx_palloc_small(pool, sizeof(ngx_pool_large_t), 1);
if (large == NULL) {
ngx_free(p);
return NULL;
}
// 插入large链表头部
large->alloc = p;
large->next = pool->large;
pool->large = large;
return p;
}
1.9 ngx_pfree() —— 大块释放
ngx_int_t ngx_pfree(ngx_pool_t *pool, void *p) {
ngx_pool_large_t *l;
for (l = pool->large; l; l = l->next) {
if (p == l->alloc) {
ngx_free(l->alloc);
l->alloc = NULL; // 标记为空闲,节点保留
return NGX_OK;
}
}
return NGX_DECLINED; // 小块不能单独释放
}
设计决策:小块分配不能单独释放,只能在pool销毁时批量释放。这简化了内存管理,但可能导致内存浪费。
1.10 ngx_destroy_pool() —— 销毁内存池
void ngx_destroy_pool(ngx_pool_t *pool) {
ngx_pool_t *p, *n;
ngx_pool_large_t *l;
ngx_pool_cleanup_t *c;
// 1. 执行所有清理回调
for (c = pool->cleanup; c; c = c->next) {
if (c->handler) {
c->handler(c->data);
}
}
// 2. 释放所有大块
for (l = pool->large; l; l = l->next) {
if (l->alloc) {
ngx_free(l->alloc);
}
}
// 3. 释放所有数据块(从后向前)
for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {
// 在NGX_DEBUG模式下,先记录日志再释放
ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, pool->log, 0,
"free: %p", p);
ngx_free(p);
if (n == NULL) {
break;
}
}
}
1.11 ngx_pool_cleanup_add() —— 注册清理回调
ngx_pool_cleanup_t *ngx_pool_cleanup_add(ngx_pool_t *p, size_t size) {
ngx_pool_cleanup_t *c;
c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
}
if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
}
} else {
c->data = NULL;
}
c->handler = ngx_pool_cleanup_file; // 默认关闭文件
c->next = p->cleanup;
p->cleanup = c;
return c;
}
// 预定义的清理回调
static void ngx_pool_cleanup_file(void *data) {
ngx_pool_cleanup_file_t *c = data;
ngx_close_file(c->fd);
}
2. 共享内存:ngx_slab.c
2.1 用途
NGINX的多个worker进程需要共享数据(如连接计数、缓存数据、限流状态)。共享内存在多个进程间可见。
2.2 Slab分配器原理
Slab分配器将内存页面划分为固定大小的块(2的幂次),每次分配从对应大小的slot中取空闲块。
Slab分配器布局:
┌─────────────────────────────────────────────────┐
│ ngx_slab_pool_t (头部) │
├─────────────────────────────────────────────────┤
│ slots[0] (8B) │ slots[1] (16B) │ slots[2] (32B)│ ...
├─────────────────────────────────────────────────┤
│ stats[0] │ stats[1] │ stats[2] │ ...
├─────────────────────────────────────────────────┤
│ pages[] (页面元数据数组) │
├─────────────────────────────────────────────────┤
│ 数据区 (实际分配的页面) │
└─────────────────────────────────────────────────┘
2.3 ngx_slab_pool_t 结构
typedef struct {
ngx_shmtx_t lock; // 互斥锁(自旋锁)
size_t min_size; // 最小分配大小(8字节)
size_t min_shift; // 最小分配大小的log2(3)
ngx_buf_t *log; // 日志缓冲区
ngx_uint_t pfree; // 空闲页面数
ngx_slab_page_t *pages; // 页面数组
ngx_slab_page_t free; // 空闲页面链表头
ngx_slab_stats_t *stats; // 统计信息
ngx_uint_t start; // 数据区起始偏移
ngx_uint_t end; // 数据区结束偏移
ngx_shmtx_t mutex; // 互斥锁
u_char *log_ctx; // 日志上下文
u_char zero; // 零字节
void *addr; // 共享内存地址
} ngx_slab_pool_t;
2.4 页面类型
// 4种页面类型
#define NGX_SLAB_PAGE 0 // 整页(分配给大对象)
#define NGX_SLAB_BIG 1 // 大块(chunk > exact_size)
#define NGX_SLAB_EXACT 2 // 精确块(chunk = exact_size,bitmap在page->slab中)
#define NGX_SLAB_SMALL 3 // 小块(chunk < exact_size,bitmap在页面开头)
2.5 分配流程
void *ngx_slab_alloc(ngx_slab_pool_t *pool, size_t size) {
void *p;
// 加锁
ngx_shmtx_lock(&pool->mutex);
// 分配
p = ngx_slab_alloc_locked(pool, size);
// 解锁
ngx_shmtx_unlock(&pool->mutex);
return p;
}
static void *ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size) {
size_t s;
ngx_uint_t i, m, page, mask, shift;
// 大块分配(超过页面大小的一半)
if (size > ngx_slab_max_size) {
return ngx_slab_alloc_pages(pool, (size >> ngx_slab_page_shift) + 1);
}
// 计算slot索引
shift = ngx_slab_page_shift - ngx_slab_min_shift;
for (s = size - 1; shift; shift--) {
if (s < (size_t) 1 << (ngx_slab_min_shift + shift)) {
break;
}
}
m = (1 << ngx_slab_min_shift) - 1;
page = (size - m - 1) >> ngx_slab_page_shift;
mask = (ngx_slab_t) 1 << ngx_slab_page_shift;
// 查找空闲chunk
for ( ;; ) {
// 遍历该slot的所有页面
for (p = pool->slots[page]; p; p = p->next) {
// 检查bitmap,找到空闲chunk
for (i = 0; i < ngx_slab_pages; i++) {
if (ngx_slab_page_type(p) == NGX_SLAB_SMALL) {
// 小块:bitmap在页面开头
if (p->slab & mask) {
p->slab &= ~mask;
// 返回分配的chunk
return (u_char *) p->s + (i << ngx_slab_page_shift);
}
} else if (ngx_slab_page_type(p) == NGX_SLAB_EXACT) {
// 精确块:bitmap在page->slab中
if (p->slab != NGX_SLAB_BUSY) {
// 找到空闲位
for (i = 0; i < 32; i++) {
if (!(p->slab & (1 << i))) {
p->slab |= (1 << i);
return ...;
}
}
}
}
}
}
// 该slot没有空闲页面,创建新页面
p = ngx_slab_alloc_pages(pool, 1);
if (p == NULL) {
break;
}
// 初始化新页面
p->slab = ngx_slab_page_type(p);
p->next = NULL;
// 插入slot链表
}
return NULL; // 分配失败
}
2.6 释放流程
ngx_int_t ngx_slab_free(ngx_slab_pool_t *pool, void *p) {
ngx_shmtx_lock(&pool->mutex);
ngx_slab_free_locked(pool, p);
ngx_shmtx_unlock(&pool->mutex);
return NGX_OK;
}
static void ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p) {
ngx_uint_t type;
ngx_slab_page_t *page;
// 计算页面索引
page = (ngx_slab_page_t *) ((u_char *) p & ~(ngx_pagesize - 1));
type = ngx_slab_page_type(page);
// 清除bitmap中的对应位
if (type == NGX_SLAB_SMALL) {
// 小块:清除bitmap位
} else if (type == NGX_SLAB_EXACT) {
// 精确块:清除page->slab中的位
page->slab &= ~(1 << bit);
}
// 如果页面完全空闲,释放回空闲列表
if (page->slab == 0) {
ngx_slab_free_pages(pool, page, 1);
}
// 检测double-free(通过验证chunk是否在bitmap中标记为busy)
}
2.7 共享内存初始化
// src/core/ngx_cycle.c
static ngx_int_t ngx_init_zone_pool(ngx_cycle_t *cycle, ngx_shm_zone_t *zn) {
ngx_slab_pool_t *sp;
sp = (ngx_slab_pool_t *) zn->addr;
// 检查是否是重用的zone
if (sp->addr != NULL) {
// 重用:所有worker共享同一块内存
return NGX_OK;
}
// 初始化slab
ngx_slab_init(sp);
// 初始化自旋锁
ngx_shmtx_create(&sp->mutex, &sp->lock, cycle->conf.pid);
return NGX_OK;
}
3. Master-Worker进程模型
3.1 启动流程
nginx 命令行
-> ngx_master_process_cycle()
-> ngx_init_cycle() 初始化cycle
-> ngx_start_worker_processes(N) fork出worker进程
-> ngx_worker_process_cycle() 每个worker的主循环
-> while (!ngx_quit) {
ngx_process_events_and_timers(); // 事件循环
}
3.2 ngx_master_process_cycle()
// src/os/unix/ngx_process_cycle.c
void ngx_master_process_cycle(ngx_cycle_t *cycle) {
// 1. 设置进程名
ngx_setproctitle("master process");
// 2. 创建监听socket
ngx_open_listening_sockets(cycle);
// 3. 启动worker进程
ngx_start_worker_processes(cycle, ccf->worker_processes,
NGX_PROCESS_RESPAWN);
// 4. 启动缓存管理进程(可选)
ngx_start_cache_manager_processes(cycle, 0);
// 5. 主循环:等待信号
for ( ;; ) {
// 检查定时器
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"cycle:%V", &cycle->conf_file);
// 等待信号
sigsuspend(&set);
// 更新时间
ngx_time_update();
// 处理信号
if (ngx_quit) {
ngx_signal_worker_processes(cycle, NGX_SHUTDOWN_SIGNAL);
ngx_close_listening_sockets(cycle);
ngx_close_old_listening_sockets(cycle);
exit(0);
}
if (ngx_reopen) {
ngx_reopen_files(cycle, ccf->user);
ngx_reopen = 0;
}
if (ngx_restart) {
ngx_restart_worker_processes(cycle);
ngx_restart = 0;
}
}
}
3.3 Worker进程启动
static void ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n,
ngx_int_t type) {
ngx_int_t i;
for (i = 0; i < n; i++) {
ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
"worker process", type);
}
}
static void ngx_worker_process_cycle(ngx_cycle_t *cycle, char *me) {
// 1. 初始化
ngx_worker_process_init(cycle, me);
// 2. 设置进程名
ngx_setproctitle("worker process");
// 3. 主事件循环
for ( ;; ) {
// 检查退出标志
if (ngx_quit) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"shutting down");
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
break;
}
if (ngx_terminate) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"terminating");
break;
}
if (ngx_quit) {
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"gracefully shutting down");
ngx_close_listening_sockets(cycle);
ngx_close_idle_connections(cycle);
break;
}
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"worker cycle: %V", &cycle->conf_file);
// 核心:事件处理
ngx_process_events_and_timers(cycle);
}
// 4. 清理
ngx_worker_process_exit(cycle);
}
3.4 Worker进程初始化
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) {
ngx_connection_t *c;
ngx_int_t n;
// 1. 设置用户权限
if (setuid(ccf->user) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setuid() failed");
ngx_abort();
}
// 2. 设置进程优先级
if (ccf->priority && setpriority(PRIO_PROCESS, 0, ccf->priority) == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"setpriority() failed");
}
// 3. 设置资源限制
if (ccf->rlimit_nofile != NGX_CONF_UNSET) {
rl.rlim_cur = ccf->rlimit_nofile;
rl.rlim_max = ccf->rlimit_nofile;
setrlimit(RLIMIT_NOFILE, &rl);
}
// 4. 初始化事件处理
ngx_event_process_init(cycle);
// 5. 初始化日志
ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0,
"start worker processes");
// 6. 执行模块的init_process钩子
for (n = 0; n < ngx_modules_n; n++) {
if (ngx_modules[n]->init_process) {
if (ngx_modules[n]->init_process(cycle) == NGX_ERROR) {
ngx_abort();
}
}
}
}
3.5 Master进程职责
| 职责 | 说明 |
|---|---|
| 读取配置 | 验证配置合法性 |
| 创建worker | fork出N个worker进程 |
| 管理worker | 监控worker健康状态 |
| 处理信号 | 接收reload/stop/reopen信号 |
| 热升级 | 替换二进制文件 |
3.6 Worker进程职责
| 职责 | 说明 |
|---|---|
| 事件循环 | 处理所有客户端请求 |
| 接受连接 | 从accept mutex竞争获取 |
| 执行模块 | 运行HTTP/Stream等模块逻辑 |
| 回收连接 | 关闭空闲连接 |
4. 信号处理
4.1 关键信号
| 信号 | 作用 | 处理者 |
|---|---|---|
SIGQUIT |
优雅退出(等待请求完成) | master |
SIGTERM |
强制停止 | master |
SIGHUP |
重载配置 | master |
SIGUSR1 |
重新打开日志文件(日志轮转) | master |
SIGUSR2 |
热升级(exec新二进制) | master |
SIGCHLD |
子进程退出 | master |
SIGALRM |
定时器 | worker |
4.2 信号处理器注册
// src/os/unix/ngx_process.c
void ngx_init_signals(void) {
struct sigaction sa;
// SIGCHLD:非阻塞处理子进程退出
sa.sa_handler = ngx_process_get_status;
sa.sa_flags = SA_NOCLDSTOP|SA_NOCLDWR|SA_RESTART|SA_SIGINFO;
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, NULL);
// SIGHUP:重载配置
sa.sa_handler = ngx_signal_handler;
sigaction(SIGHUP, &sa, NULL);
// SIGUSR1:重新打开日志
sa.sa_handler = ngx_signal_handler;
sigaction(SIGUSR1, &sa, NULL);
// SIGUSR2:热升级
sa.sa_handler = ngx_signal_handler;
sigaction(SIGUSR2, &sa, NULL);
// SIGQUIT:优雅退出
sa.sa_handler = ngx_signal_handler;
sigaction(SIGQUIT, &sa, NULL);
// SIGTERM:强制停止
sa.sa_handler = ngx_signal_handler;
sigaction(SIGTERM, &sa, NULL);
}
4.3 优雅退出流程
master收到SIGQUIT
-> 设置 ngx_quit = 1
-> 发送SIGQUIT给所有worker
-> worker设置 ngx_quit = 1
-> 不再接受新连接
-> 等待现有连接处理完毕
-> 进程退出
4.4 子进程退出处理
static void ngx_process_get_status(void) {
int status;
ngx_pid_t pid;
ngx_int_t i;
// 非阻塞等待子进程
for ( ;; ) {
pid = waitpid(-1, &status, WNOHANG);
if (pid == 0) {
return; // 没有更多子进程退出
}
if (pid == -1) {
if (errno == EINTR) {
continue;
}
return;
}
// 记录退出状态
if (WIFEXITED(status)) {
ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
"%P %d exited with code %d",
pid, ngx_errno, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
"%P %d was killed by signal %d",
pid, ngx_errno, WTERMSIG(status));
}
// 重新启动worker进程
ngx_spawn_process(cycle, ngx_worker_process_cycle, NULL,
"worker process", NGX_PROCESS_JUST_RESPAWN);
}
}
5. 优雅退出
5.1 优雅退出流程
master收到SIGQUIT
-> 设置 ngx_quit = 1
-> 发送SIGQUIT给所有worker
-> worker检测到 ngx_quit = 1
-> ngx_close_listening_sockets(cycle) 关闭监听socket
-> ngx_close_idle_connections(cycle) 关闭空闲连接
-> 等待活跃连接处理完毕
-> ngx_worker_process_exit(cycle) 进程退出
5.2 关闭空闲连接
void ngx_close_idle_connections(ngx_cycle_t *cycle) {
ngx_connection_t *c;
ngx_uint_t i;
c = cycle->connections;
for (i = 0; i < cycle->connection_n; i++) {
// 如果连接空闲(没有活跃事件)
if (c[i].fd != -1 && c[i].read->active == 0) {
ngx_close_connection(&c[i]);
}
}
}
6. 内存管理最佳实践
1. 使用池分配器避免内存泄漏
2. 请求级别的分配使用请求池
3. 连接级别的分配使用连接池
4. 共享内存使用slab分配器
5. 避免在worker间直接共享堆内存
6. 使用ngx_pfree释放大块内存
7. 注册清理回调处理文件描述符等资源
7. 本篇小结
| 概念 | 要点 |
|---|---|
| 内存池 | 指针移动分配,池销毁时释放 |
| 小块分配 | O(1),指针移动 |
| 大块分配 | 直接malloc,加入large链表 |
| 共享内存 | slab分配器,锁保护 |
| slab | 4种页面类型,bitmap管理 |
| 进程模型 | master-fork-worker |
| accept锁 | 避免惊群效应 |
| 信号 | reload/stop/reopen/热升级 |
| 优雅退出 | 关闭监听socket,等待连接完成 |
思考题
- 内存池的最大块大小(max)为什么要设定上限?超过怎么办?
- 如果worker进程崩溃,NGINX如何恢复?
- 热升级过程中如何保证现有请求不中断?
- 为什么NGINX选择accept mutex而不是SO_REUSEPORT?
思考题解答
1. 内存池max上限为什么要设定?超过怎么办?
为什么设定上限:
-
防止大块分配淹没小块:如果所有分配都走内存池,大块分配(如读取整个请求体)会占用大量连续内存,导致小块分配(如配置字符串)无法找到合适的连续空间。
-
减少碎片:大块分配直接走系统malloc,使用完后立即释放,避免在内存池中留下碎片。
-
快速释放:内存池销毁时,大块通过free链表快速释放,小块通过指针移动批量释放。如果大块也走池,释放成本增加。
超过max时的处理:
// src/core/ngx_palloc.c
void *ngx_palloc(ngx_pool_t *pool, size_t size) {
if (size <= pool->max) {
// 小块:从当前pool数据块分配
return ngx_palloc_small(pool, size, pool->clean);
}
// 大块:直接malloc,加入large链表
return ngx_palloc_large(pool, size);
}
大块分配流程:
- 调用系统malloc分配内存
- 将分配的内存块加入
pool->large链表 - 内存块使用完后,在
ngx_destroy_pool()中释放
max的典型值:默认为页面大小(4096字节),可通过 pool->max 调整。
2. worker进程崩溃如何恢复?
自动恢复机制:
worker进程崩溃
-> master检测到子进程退出
-> 记录日志
-> 重新fork新的worker进程
-> 新worker继承监听socket
-> 继续处理请求
实现细节:
// src/os/unix/ngx_process.c
void ngx_process_get_status(void) {
pid = waitpid(-1, &status, WNOHANG);
if (WIFEXITED(status)) {
// worker正常退出
ngx_log_error(..., "worker %d exited with code %d", pid, WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// worker被信号杀死(崩溃)
ngx_log_error(..., "worker %d was killed by signal %d", pid, WTERMSIG(status));
}
// 重新启动worker
ngx_start_worker_processes(cycle, ngx_process_slot, NGX_PROCESS_JUST_RESPAWN);
}
恢复策略:
- master进程永不退出(除非手动停止)
- worker崩溃只影响该worker处理的连接,其他worker不受影响
- 新worker立即继承监听socket,可以接受新连接
- 旧worker的连接在超时后被清理
配置:
worker_rlimit_nofile 65535; # 增加文件描述符限制
worker_shutdown_timeout 10s; # 优雅退出超时
3. 热升级如何保证请求不中断?
热升级流程:
1. 下载新二进制文件到临时路径
2. master接收SIGUSR2信号
-> fork新master(使用新二进制)
-> 新master fork新worker
3. 旧worker继续处理现有请求
4. 旧worker优雅退出
-> 等待现有连接处理完毕
-> 退出
5. 新master完全接管
无缝切换的关键:
- 监听socket继承:新master从旧master继承监听socket,新worker可以立即接受连接
- 旧连接不中断:旧worker继续处理已建立的连接,直到连接关闭或超时
- 配置文件共享:新master使用新二进制但可以加载旧配置文件
- 回滚机制:如果新版本有问题,可以发送
SIGQUIT给新master,旧master仍可恢复
回滚步骤(如果新版本有问题):
# 向旧master发送信号恢复
kill -QUIT old_master_pid
# 旧master会重新启动worker
4. 为什么选择accept mutex而不是SO_REUSEPORT?
SO_REUSEPORT:
- 内核层面的负载均衡
- 多个进程可以bind同一个端口
- 内核自动分发连接到不同进程
NGINX选择accept mutex的原因:
-
更细粒度的控制:
- 可以根据worker负载动态调整连接分配
- 支持不同的调度策略(如权重)
-
兼容性:
- SO_REUSEPORT需要Linux 3.9+
- accept mutex在所有平台都支持
- NGINX需要支持FreeBSD、Linux、Windows等
-
惊群效应的处理:
- SO_REUSEPORT在某些内核版本仍有惊群问题
- accept mutex完全避免惊群
-
代码复杂度:
- accept mutex实现简单,不需要额外的内核支持
- SO_REUSEPORT需要处理内核版本差异
SO_REUSEPORT的优势(可选启用):
# NGINX 1.9.1+ 支持reuseport
listen 80 reuseport;
启用后,每个worker有独立的accept队列,内核直接分发连接,无需accept mutex,性能更好。
建议:Linux 3.9+环境推荐启用 reuseport,其他平台使用accept mutex。

浙公网安备 33010602011771号