AIGC标识 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队列?

  1. 避免事件表修改冲突:回调可能添加/删除epoll事件,在遍历events数组时修改底层数据结构可能导致问题
  2. 公平调度:先收集所有就绪事件,再统一处理,避免某个事件的回调耗时过长阻塞其他事件
  3. accept优先:accept事件放入单独的队列,优先处理,减少新连接等待时间
  4. 重入保护:避免事件处理过程中触发新的事件干扰遍历

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的高并发基础

核心优势:

  1. 单线程事件驱动:避免锁竞争
  2. 非阻塞I/O + epoll:单线程处理数万连接
  3. 连接池:O(1)分配和回收连接
  4. 红黑树定时器:O(logN)插入/删除,O(1)获取最近超时
  5. posted队列:避免事件处理过程中修改事件表
  6. instance位检测:正确处理过期事件

8. 本篇小结

概念 要点
事件循环 ngx_process_events_and_timers()
epoll ngx_epoll_module.c封装,边缘触发
定时器 红黑树,key=过期时间
连接池 预分配数组,空闲链表管理
posted 三种队列:accept/next/events
instance位 检测过期事件,防止错误处理
负载检测 空闲连接<总连接数/8时减少accept

思考题

  1. 为什么NGINX用红黑树而不用最小堆实现定时器?
  2. epoll_wait返回后,如果立即处理事件回调会有什么问题?
  3. 连接池的大小应该如何配置?过大或过小各有什么后果?
  4. 与Redis的ae事件循环相比,NGINX的事件模型有什么异同?

思考题解答

1. 为什么NGINX用红黑树而不用最小堆实现定时器?

红黑树 vs 最小堆

特性 红黑树 最小堆
查找最近超时 O(1)(最左节点) O(1)(堆顶)
插入定时事件 O(logN) O(logN)
删除定时事件 O(logN) O(logN)(需要查找)
修改定时事件 O(logN) O(logN)(需要查找)
内存局部性 较差 较好

NGINX选择红黑树的原因

  1. 删除操作频繁:NGINX中大量定时事件会被提前取消(如连接提前关闭),需要高效删除。红黑树的删除是O(logN),而最小堆删除任意元素需要先找到它(O(N))或维护额外的索引。

  2. 查找最近超时:两者都是O(1),但红黑树通过 ngx_rbtree_min() 直接获取最左节点,实现简单。

  3. 已有实现:NGINX源码中已经有完善的红黑树实现(ngx_rbtree.c),不需要额外实现堆。

  4. 通用性:红黑树是通用数据结构,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的多进程模型有本质区别。

posted @ 2026-09-04 17:26  IcarusLee  阅读(5)  评论(0)    收藏  举报