nginx-源码带读-02-事件驱动模型
NGINX 源码带读 第2篇:事件驱动模型
本篇目标
理解NGINX高性能的基石:事件驱动模型。深入分析epoll实现、红黑树定时器、连接池管理和posted事件队列。
前置知识
- 了解epoll的原理(epoll_create/ctl/wait)
- 了解非阻塞I/O
- 阅读过第1篇
1. 事件循环入口
1.1 ngx_process_events_and_timers()
这是事件循环的核心函数,在worker进程的主循环中反复调用:
// src/event/ngx_event.c
void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
ngx_uint_t flags;
ngx_msec_t timer, delta;
// 1. 计算最近定时器的超时时间
timer = ngx_event_find_timer();
// 2. 选择事件处理标志
flags = NGX_POST_EVENTS;
if (ngx_use_accept_mutex) {
// 3. 如果使用accept mutex,尝试获取锁
if (ngx_accept_disabled > 0) {
// 负载过高,减少accept尝试
ngx_accept_disabled--;
} else {
// 尝试获取accept锁
if (ngx_trylock(&ngx_accept_mutex) == NGX_OK) {
// 获得锁:注册accept事件,延迟处理其他事件
ngx_use_accept_mutex = 1;
ngx_enable_accept_events(cycle);
flags |= NGX_POST_EVENTS; // 所有事件都放入posted队列
} else {
// 未获得锁:限制定时器等待时间
timer = ngx_min(timer, ngx_accept_mutex_delay);
}
}
}
// 4. 检查是否有待处理的accept事件
if (ngx_posted_next_events) {
ngx_posted_next_events = 0;
timer = 0; // 立即处理
}
// 5. 记录开始时间
delta = ngx_current_msec;
// 6. 调用平台相关的事件处理(epoll_wait等)
ngx_process_events(cycle, timer, flags);
// 7. 计算处理耗时
delta = ngx_current_msec - delta;
ngx_event_process_posted(cycle, &ngx_posted_accept_events);
// 8. 释放accept锁(尽快释放)
if (ngx_accept_mutex_held) {
ngx_shmtx_unlock(&ngx_accept_mutex);
}
// 9. 处理定时器事件
ngx_event_expire_timers();
// 10. 处理普通posted事件
ngx_event_process_posted(cycle, &ngx_posted_events);
}
1.2 关键变量
| 变量 | 类型 | 作用 |
|---|---|---|
ngx_accept_disabled |
ngx_int_t |
负载阈值,大于0时减少accept |
ngx_accept_mutex |
ngx_shmtx_t |
accept互斥锁(共享内存中的自旋锁) |
ngx_accept_mutex_delay |
ngx_msec_t |
未获锁时的重试间隔(默认50ms) |
ngx_use_accept_mutex |
ngx_flag_t |
是否使用accept mutex |
ngx_posted_next_events |
ngx_uint_t |
是否有紧急待处理事件 |
ngx_current_msec |
ngx_msec_t |
当前时间(毫秒) |
1.3 事件处理宏
ngx_process_events 是一个宏,根据编译选项指向不同的平台实现:
// 在ngx_epoll_module中:
#define ngx_process_events ngx_epoll_process_events
// 在ngx_kqueue_module中:
#define ngx_process_events ngx_kqueue_process_events
2. epoll实现
2.1 ngx_epoll_module.c 关键函数
| 函数 | 行数 | epoll系统调用 | 作用 |
|---|---|---|---|
ngx_epoll_init |
323 | epoll_create(connection_n/2) |
创建epoll实例 |
ngx_epoll_add_event |
579 | epoll_ctl(ADD) |
注册事件 |
ngx_epoll_del_event |
643 | epoll_ctl(DEL) |
删除事件 |
ngx_epoll_add_connection |
701 | epoll_ctl(ADD) |
同时注册读写事件 |
ngx_epoll_process_events |
784 | epoll_wait |
等待并收集就绪事件 |
2.2 ngx_epoll_init()
static ngx_int_t ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer) {
ngx_epoll_conf_t *epcf;
// 获取event模块的配置
epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module);
// 如果epfd已存在,先关闭(重载配置时)
if (ep == -1) {
ep = epoll_create(cycle->connection_n / 2);
if (ep == -1) {
ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno,
"epoll_create() failed");
return NGX_ERROR;
}
// 分配事件列表
event_list = ngx_alloc(sizeof(struct epoll_event) * epcf->events,
cycle->log);
epoll_n = epcf->events; // 默认512
}
// 设置模块标志
module->actions.add = ngx_epoll_add_event;
module->actions.del = ngx_epoll_del_event;
module->actions.enable = ngx_epoll_add_event;
module->actions.disable = ngx_epoll_del_event;
module->actions.add_conn = ngx_epoll_add_connection;
module->actions.del_conn = ngx_epoll_del_connection;
module->actions.process = ngx_epoll_process_events;
module->actions.init = ngx_epoll_init;
module->actions.done = ngx_epoll_done;
// 检查EPOLLRDHUP支持
ngx_epoll_test_rdhup(cycle);
return NGX_OK;
}
2.3 ngx_epoll_add_event() —— 注册事件
static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event,
ngx_uint_t flags) {
struct epoll_event ee;
ngx_connection_t *c;
c = ev->data;
// 构建epoll事件
ee.events = 0;
if (event == NGX_READ_EVENT) {
ee.events |= EPOLLIN;
} else {
ee.events |= EPOLLOUT;
}
if (ev->oneshot) {
ee.events |= EPOLLONESHOT;
}
if (flags & NGX_EXCLUSIVE_EVENT) {
// 不使用边缘触发(某些平台不支持)
} else {
ee.events |= EPOLLET; // 边缘触发
}
// 加入EPOLLRDHUP(对端关闭)
if (flags & NGX_LOWAT_EVENT) {
ee.events |= EPOLLRDHUP;
}
// 关键:将connection指针和instance位打包到data.ptr
ee.data.ptr = (void *)((uintptr_t)c | ev->instance);
// 注册到epoll
if (epoll_ctl(ep, op, c->fd, &ee) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
return NGX_OK;
}
2.4 ngx_epoll_process_events() —— 收集就绪事件
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle,
ngx_msec_t timer, ngx_uint_t flags) {
int events;
ngx_int_t instance;
ngx_uint_t i, found;
ngx_event_t *rev, *wev;
ngx_connection_t *c;
// 调用epoll_wait
events = epoll_wait(ep, event_list, (int)epoll_n, timer);
if (events == -1) {
if (errno == EINTR) {
return NGX_OK; // 被信号中断,正常
}
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"epoll_wait() failed");
return NGX_ERROR;
}
if (events == 0) {
if (timer != NGX_TIMER_INFINITE) {
return NGX_OK; // 超时
}
ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
"epoll_wait() returned no events without timeout");
return NGX_ERROR;
}
// 处理就绪事件
found = 0;
for (i = 0; i < (ngx_uint_t)events; i++) {
// 从data.ptr解包connection和instance
c = event_list[i].data.ptr;
// instance位检查:检测过期事件
instance = (uintptr_t)c & 1;
c = (ngx_connection_t *)((uintptr_t)c & ~(uintptr_t)1);
// 检查连接是否有效
if (c->fd == -1 || c->read->instance != instance) {
// 连接已被关闭或复用,忽略此事件
ngx_log_debug(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
"epoll: stale event %p", c);
continue;
}
rev = c->read;
// 处理读事件
if (rev->active && (event_list[i].events & (EPOLLIN|EPOLLERR|EPOLLHUP))) {
found = 1;
rev->ready = 1;
if (flags & NGX_POST_EVENTS) {
// 延迟处理:放入posted队列
ngx_post_event(rev, &ngx_posted_events);
} else {
// 立即调用回调
rev->handler(rev);
}
}
wev = c->write;
// 处理写事件
if (wev->active && (event_list[i].events & (EPOLLOUT|EPOLLERR|EPOLLHUP))) {
found = 1;
wev->ready = 1;
if (flags & NGX_POST_EVENTS) {
ngx_post_event(wev, &ngx_posted_events);
} else {
wev->handler(wev);
}
}
}
return NGX_OK;
}
2.5 instance位的工作原理
注册事件时:
ee.data.ptr = (c | instance) // 将connection和instance打包
事件触发时:
instance = (uintptr_t)c & 1; // 取出instance位
c = (ngx_connection_t *)((uintptr_t)c & ~1); // 取出connection
if (c->fd == -1 || rev->instance != instance) {
// 过期事件,忽略
}
为什么需要instance位?
场景:连接A关闭后,连接B复用了同一个connection结构体。如果连接A的旧事件仍在epoll中,事件触发时会错误地处理连接B。instance位可以检测这种情况。
2.6 边缘触发(ET)模式
NGINX使用边缘触发(EPOLLET)模式:
- 水平触发(LT):只要fd可读/可写,epoll_wait就不断返回
- 边缘触发(ET):只有fd状态从不可读/不可写变为可读/可写时才返回
ET模式的优势:
- 减少epoll_wait返回次数,提高性能
- 必须一次性读完所有数据(直到EAGAIN),否则可能丢失事件
NGINX的处理:
// 事件数组大小可配置
events = 512; // 默认值
// 每次epoll_wait返回时,遍历所有就绪事件
// 由于ET模式,需要确保处理所有事件
3. 红黑树定时器
3.1 定时器红黑树
NGINX使用红黑树管理所有定时事件。每个事件节点的key是过期时间的毫秒数。
// src/event/ngx_event_timer.c
ngx_rbtree_t ngx_event_timer_rbtree;
static ngx_rbtree_node_t ngx_event_timer_sentinel;
// 初始化
void ngx_event_timer_init(ngx_log_t *log) {
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
ngx_rbtree_insert_timer_value);
}
3.2 核心操作
| 函数 | 行数 | 作用 |
|---|---|---|
ngx_add_timer(ev, timer) |
~40 | 插入定时事件 |
ngx_del_timer(ev) |
~90 | 删除定时事件 |
ngx_event_find_timer() |
~120 | 查找最近的超时时间 |
ngx_event_expire_timers() |
~150 | 处理所有已超时的事件 |
3.3 插入定时事件
void ngx_add_timer(ngx_event_t *ev, ngx_msec_t timer) {
ngx_msec_t key;
ngx_event_t *next;
ngx_rbtree_node_t *sentinel, *root;
sentinel = ngx_event_timer_rbtree.sentinel;
// 计算过期时间 = 当前时间 + 超时时间
key = ngx_current_msec + timer;
ev->key = key;
// 如果已有定时器,删除旧的
if (ev->timer_left) {
ngx_del_timer(ev);
}
// 查找插入位置
root = ngx_event_timer_rbtree.root;
if (root->left == sentinel) {
// 空树,直接插入到根
root->left = &ev->timer;
ev->timer.parent = root;
ngx_rbt_insert(&ngx_event_timer_rbtree, &ev->timer);
} else {
// 查找合适的父节点
next = root->left;
for (;;) {
if (next->timer.key <= key) {
if (next->next) {
next = next->next;
continue;
}
next->next = ev;
ev->prev = next;
break;
}
if (next->left == sentinel) {
next->left = &ev->timer;
ev->timer.parent = next;
ngx_rbt_insert(&ngx_event_timer_rbtree, &ev->timer);
break;
}
next = next->left;
}
}
}
3.4 超时时间计算
ngx_msec_t ngx_event_find_timer(void) {
ngx_msec_t timer;
ngx_rbtree_node_t *node, *root;
if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
return NGX_TIMER_INFINITE; // 无定时事件
}
root = ngx_event_timer_rbtree.root;
node = ngx_rbtree_min(root, &ngx_event_timer_sentinel);
// 最近超时时间 = 节点key - 当前时间
timer = (ngx_msec_t) node->key;
timer -= ngx_current_msec;
return timer; // 作为epoll_wait的超时参数
}
3.5 处理超时事件
void ngx_event_expire_timers(void) {
ngx_event_t *ev;
ngx_rbtree_node_t *node, *sentinel;
sentinel = ngx_event_timer_rbtree.sentinel;
// 从红黑树最左节点开始(最小key = 最近超时)
for (;;) {
node = ngx_rbtree_min(ngx_event_timer_rbtree.root, sentinel);
// 检查是否超时
if ((ngx_msec_t) node->key <= ngx_current_msec) {
// 获取包含此node的event结构体
ev = ngx_rbtree_data(node);
ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"event timer del: %d: %M",
ngx_event_ident(ev->data), ev->key);
// 从红黑树删除
ngx_rbtree_delete(&ngx_event_timer_rbtree, node);
ev->timer_left = 0;
// 调用超时回调
ev->handler(ev);
} else {
break; // 没有更多超时事件
}
}
}
4. 连接池管理
4.1 预分配连接池
NGINX启动时预分配固定数量的连接对象:
// src/event/ngx_event.c
static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle) {
// 分配连接数组
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n,
cycle->log);
if (cycle->connections == NULL) {
return NGX_ERROR;
}
// 分配读写事件数组
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,
cycle->log);
// 初始化空闲链表(反向构建)
c = cycle->connections;
i = cycle->connection_n;
next = NULL;
do {
i--;
c[i].data = next; // 指向下一个空闲连接
c[i].read = &cycle->read_events[i];
c[i].write = &cycle->write_events[i];
c[i].fd = -1; // 标记为空闲
next = &c[i]; // 下一个空闲连接
} while (i);
cycle->free_connections = next; // 链表头
cycle->free_connection_n = cycle->connection_n;
// 为每个监听socket注册读事件
ls = cycle->listening.elts;
for (i = 0; i < cycle->listening.nelts; i++) {
c = ngx_get_connection(ls[i].fd, cycle->log);
if (c == NULL) {
return NGX_ERROR;
}
c->listening = &ls[i];
rev = c->read;
rev->handler = ngx_event_accept; // 读事件回调
if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
return NGX_ERROR;
}
}
}
4.2 连接池数据结构
cycle->connections 数组:
┌─────────┬─────────┬─────────┬─────────┬─────────┐
│ conn[0] │ conn[1] │ conn[2] │ ... │ conn[N] │
└────┬────┴────┬────┴────┬────┴─────────┴─────────┘
│ │ │
v v v
cycle->read_events[0] write_events[0]
cycle->read_events[1] write_events[1]
cycle->read_events[2] write_events[2]
空闲链表(通过data指针链接):
conn[N] -> conn[N-1] -> ... -> conn[0] -> NULL
^
cycle->free_connections -------------------+
cycle->free_connection_n = N+1
4.3 接受新连接
// src/event/ngx_event_accept.c
void ngx_event_accept(ngx_event_t *ev) {
ngx_connection_t *c, *lc;
ngx_event_t *rev;
struct sockaddr_in sin;
socklen_t socklen;
lc = ev->data; // 监听socket的连接
log = lc->log;
// 循环accept直到EAGAIN
for ( ;; ) {
socklen = sizeof(struct sockaddr_in);
// accept新连接
fd = accept(lc->fd, (struct sockaddr *)&sin, &socklen);
if (fd == -1) {
if (errno == EAGAIN) {
break; // 没有更多连接
}
// 其他错误
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, "accept() failed");
break;
}
// 设置非阻塞
ngx_nonblocking(fd);
// 获取空闲连接
c = ngx_get_connection(fd, log);
if (c == NULL) {
close(fd);
break;
}
// 初始化连接
c->sockaddr = &sin;
c->local_sockaddr = lc->sockaddr;
c->listening = lc->listening;
// 设置读事件回调
rev = c->read;
rev->handler = ngx_http_process_request; // 或其他handler
// 注册读事件
ngx_add_event(rev, NGX_READ_EVENT, 0);
// 更新统计
if (ngx_accept_disabled > 0) {
ngx_accept_disabled--;
} else {
// 重置accept超时
ngx_add_timer(rev, c->listening->accept_timeout);
}
}
}
4.4 负载检测
// src/event/ngx_event_accept.c
// 当空闲连接 < 总连接数/8 时,认为负载过高
ngx_accept_disabled = ngx_cycle->connection_n / 8
- ngx_cycle->free_connection_n;
当 ngx_accept_disabled > 0 时,worker不再主动accept,让其他空闲worker处理新连接。
5. posted事件队列
5.1 概念
某些事件不适合在epoll_wait返回时立即处理(可能修改事件表),而是放入 ngx_posted_events 队列,延迟到下一轮循环处理。
5.2 三种posted队列
| 队列 | 用途 |
|---|---|
ngx_posted_accept_events |
accept事件(优先处理) |
ngx_posted_next_events |
紧急事件(下次循环立即处理) |
ngx_posted_events |
普通事件 |
5.3 处理流程
// 处理posted事件
void ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *q) {
ngx_event_t *ev;
while (!ngx_queue_empty(q)) {
// 从队列头取出事件
ev = ngx_queue_data(q->next, ngx_event_t, queue);
ngx_queue_remove(q->next);
// 调用事件回调
ev->handler(ev);
}
}
5.4 为什么要用posted队列?
- 避免事件表修改冲突:回调可能添加/删除epoll事件,在遍历events数组时修改底层数据结构可能导致问题
- 公平调度:先收集所有就绪事件,再统一处理,避免某个事件的回调耗时过长阻塞其他事件
- accept优先:accept事件放入单独的队列,优先处理,减少新连接等待时间
- 重入保护:避免事件处理过程中触发新的事件干扰遍历
6. 事件循环的完整流程
worker主循环
|
+-> ngx_process_events_and_timers()
|
+-> 计算timer(最近定时器超时时间)
|
+-> 尝试获取accept mutex
| +-> 获得锁:注册accept事件,设置POST_EVENTS标志
| +-> 未获得锁:设置较短的timer
|
+-> ngx_process_events() [宏 -> ngx_epoll_process_events]
| +-> epoll_wait(ep, event_list, epoll_n, timer)
| +-> 遍历就绪事件
| +-> 检查instance位(过期事件检测)
| +-> 设置rev->ready/wev->ready
| +-> ngx_post_event(rev) 或 rev->handler(rev)
|
+-> 处理posted_accept_events
| +-> 优先处理accept事件
|
+-> 释放accept mutex
|
+-> ngx_event_expire_timers()
| +-> 遍历红黑树最左节点
| +-> 调用超时回调
|
+-> 处理posted_events
+-> 处理所有普通事件
7. 高效设计总结
事件驱动 + 非阻塞I/O + 连接池 + 红黑树定时器 + posted延迟队列
= NGINX的高并发基础
核心优势:
- 单线程事件驱动:避免锁竞争
- 非阻塞I/O + epoll:单线程处理数万连接
- 连接池:O(1)分配和回收连接
- 红黑树定时器:O(logN)插入/删除,O(1)获取最近超时
- posted队列:避免事件处理过程中修改事件表
- instance位检测:正确处理过期事件
8. 本篇小结
| 概念 | 要点 |
|---|---|
| 事件循环 | ngx_process_events_and_timers() |
| epoll | ngx_epoll_module.c封装,边缘触发 |
| 定时器 | 红黑树,key=过期时间 |
| 连接池 | 预分配数组,空闲链表管理 |
| posted | 三种队列:accept/next/events |
| instance位 | 检测过期事件,防止错误处理 |
| 负载检测 | 空闲连接<总连接数/8时减少accept |
思考题
- 为什么NGINX用红黑树而不用最小堆实现定时器?
- epoll_wait返回后,如果立即处理事件回调会有什么问题?
- 连接池的大小应该如何配置?过大或过小各有什么后果?
- 与Redis的ae事件循环相比,NGINX的事件模型有什么异同?
思考题解答
1. 为什么NGINX用红黑树而不用最小堆实现定时器?
红黑树 vs 最小堆:
| 特性 | 红黑树 | 最小堆 |
|---|---|---|
| 查找最近超时 | O(1)(最左节点) | O(1)(堆顶) |
| 插入定时事件 | O(logN) | O(logN) |
| 删除定时事件 | O(logN) | O(logN)(需要查找) |
| 修改定时事件 | O(logN) | O(logN)(需要查找) |
| 内存局部性 | 较差 | 较好 |
NGINX选择红黑树的原因:
-
删除操作频繁:NGINX中大量定时事件会被提前取消(如连接提前关闭),需要高效删除。红黑树的删除是O(logN),而最小堆删除任意元素需要先找到它(O(N))或维护额外的索引。
-
查找最近超时:两者都是O(1),但红黑树通过
ngx_rbtree_min()直接获取最左节点,实现简单。 -
已有实现:NGINX源码中已经有完善的红黑树实现(
ngx_rbtree.c),不需要额外实现堆。 -
通用性:红黑树是通用数据结构,NGINX还在其他地方使用(如定时器、连接管理),代码复用率高。
最小堆的劣势:删除任意节点需要先定位(线性扫描),或者维护从节点到堆位置的映射(增加复杂度和内存)。
2. epoll_wait返回后立即处理事件回调有什么问题?
如果在epoll_wait返回的循环中直接处理事件回调,会遇到以下问题:
问题1:事件表修改:
- 回调函数可能修改epoll的事件表(添加/删除事件)
- 在遍历events数组时修改底层数据结构可能导致未定义行为
- 解决:先收集所有就绪事件到posted队列,再统一处理
问题2:处理不均衡:
- 如果某个事件的回调函数耗时较长,会阻塞其他事件的处理
- 例如:一个慢速连接的读取可能占用整个事件循环
- 解决:posted队列允许在事件收集完成后公平处理
问题3:重入问题:
- 某些事件处理可能触发新的事件(如写操作触发epollout)
- 在遍历events时添加新事件可能干扰遍历
NGINX的解决方案:
static ngx_int_t ngx_epoll_process_events(...) {
events = epoll_wait(...);
for (i = 0; i < events; i++) {
if (flags & NGX_POST_EVENTS) {
// 收集到posted队列,稍后处理
ngx_post_event(rev, &ngx_posted_events);
} else {
// 立即处理(较少使用)
rev->handler(rev);
}
}
}
只有在特殊情况下(如超时紧迫)才立即处理,通常都通过posted队列延迟处理。
3. 连接池大小如何配置?过大或过小的后果?
配置方法:
events {
worker_connections 10240; # 每个worker的最大连接数
}
过大的后果:
- 内存浪费:每个
ngx_connection_t占用约200字节,10240个连接约2MB/worker - 进程内存膨胀:fork后COW页增多,可能增加物理内存使用
- 上下文切换可能增加:更多连接意味着更多事件需要处理
过小的后果:
- 连接拒绝:当连接数达到上限时,新连接无法建立
- 性能下降:连接数不足可能导致请求排队
- 可用性问题:在高并发场景下无法处理突发流量
最佳实践:
# 根据服务器内存和业务需求配置
events {
worker_connections 4096; # 中等规模服务器
}
# 计算总连接数
# 总连接数 = worker_connections * worker_processes
# 例如:4096 * 4 = 16384 个并发连接
考虑因素:
- 每个连接的实际内存使用(包括请求池、缓冲区)
- 系统文件描述符限制(
ulimit -n) - 后端服务器的连接限制
- 预期的并发用户数
4. 与Redis的ae事件循环相比,NGINX的事件模型有什么异同?
相同点:
- 都是单线程事件驱动模型
- 都使用epoll(Linux)作为事件多路复用
- 都有定时器机制(红黑树 vs 最小堆)
- 都支持非阻塞I/O
不同点:
| 特性 | NGINX | Redis ae |
|---|---|---|
| 进程模型 | 多进程(master-worker) | 单进程 |
| 并发模型 | 每个worker一个epoll | 一个epoll |
| 连接管理 | 连接池(预分配) | 动态分配 |
| 定时器 | 红黑树 | 最小堆 |
| 事件类型 | 读/写/定时 | 读/写/定时/文件事件 |
| 内存管理 | 内存池 | jemalloc/tcmalloc |
| 模块化 | 高度模块化 | 单一程序 |
设计差异的原因:
- NGINX面向网络I/O密集型场景,需要处理大量并发连接
- Redis面向内存操作场景,需要低延迟的数据操作
- NGINX需要多进程模型利用多核CPU(避免单线程瓶颈)
- Redis选择单进程简化实现,通过单线程避免锁竞争
Redis后续发展:Redis 6.0引入了多线程I/O(io-threads),但核心执行仍然是单线程。这与NGINX的多进程模型有本质区别。

浙公网安备 33010602011771号