Nginx 使用进程级锁和延迟 accept() 等逻辑,以解决 epoll/kqueue 存在的 thundering herd 问题,并在各 worker 进程间尽量平衡负载 (正常连接)。

directives

  • accept_mutex - nginx uses accept mutex to serialize accept() syscalls.

  • accept_mutex_delay - If a worker process does not have accept mutex it will try to acquire it at least after this delay. By default delay is 500ms.

  • multi_accept - tries to accept() as many connections as possible after nginx gets notification about a new connection.

variables and functions

  • ngx_event_accept() - 实际完成 accept() 工作的函数。

    --------event/ngx_event.c:822--------------
    rev->handler = ngx_event_accept;
    
    if (ngx_use_accept_mutex) {
        continue;
    }
    ...
    if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
        return NGX_ERROR;
    }
    
  • ngx_use_accept_mutex - 基本上和配置项 accept_mutex 相对应。

    -------event/ngx_event.c:593---------------
    if (ccf->master && ccf->worker_processes > 1 && ecf->accept_mutex) {
        ngx_use_accept_mutex = 1;
        ngx_accept_mutex_held = 0;
        ngx_accept_mutex_delay = ecf->accept_mutex_delay;
    
    } else {
        ngx_use_accept_mutex = 0;
    }
    
  • ngx_accept_disabled - worker 进程是否可以开始竞争 accept_mutex 锁。

    --------event/ngx_event_accept.c:81---------
    ngx_accept_disabled = ngx_cycle->connection_n / 8
                            - ngx_cycle->free_connection_n;
    
    --------event/ngx_event.c:200---------------
    void
    ngx_process_events_and_timers(ngx_cycle_t *cycle)
    {
        ...
        if (ngx_use_accept_mutex) {
            if (ngx_accept_disabled > 0) {
                ngx_accept_disabled--;
    
            } else {
                if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                    return;
                }
    
                if (ngx_accept_mutex_held) {
                    flags |= NGX_POST_EVENTS;
    
                } else {
                    if (timer == NGX_TIMER_INFINITE)
                        || timer > ngx_accept_mutex_delay)
                    {
                        timer = ngx_accept_mutex_delay;
                    }
                }
            }
        }
        ...
        ngx_process_events(cycle, timer, flags);
        ...
        if (ngx_accept_mutex_held) {
            ngx_shmtx_unlock(&ngx_accept_mutex);
        }
        ...
    }
    
    ---------event/ngx_event_accept.c:268-----------
    ngx_int_t
    ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
    {
        if (ngx_shmtx_trylock(&ngx_accept_mutex)) {
            ...
            if (ngx_accept_mutex_held
                && ngx_accept_events == 0
                && !(ngx_events & NGX_USE_RTSIG_EVENT))
            {
                return NGX_OK;
            }
    
            if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
                ngx_shmtx_unlock(&ngx_accept_mutex);
                return NGX_ERROR;
            }
    
            ngx_accept_events = 0;
            ngx_accept_mutex_held = 1;
    
            return NGX_OK;
        }
        ...
        if (ngx_accept_mutex_held) {
            if (ngx_disable_accept_events(cycle) == NGX_ERROR) {
                return NGX_ERROR;
            }
    
            ngx_accept_mutex_held = 0;
        }
    
        return NGX_OK;
    }
    

conclusion

  • worker 进程竞争到 accept_mutex 后,方可开始监听 listening sockets 上的读 事件 (通过调用函数 ngx_enable_accept_events 完成)。在任何一个时刻,只有一个 worker 进程允许处理 listening sockets 上的读事件,接收新的连接。

  • worker 进程在空闲连接数 free_connection_n 大于 1/8 * connection_n 时,或 者多次主循环 (ngx_process_events_timers) 使 ngx_accept_diabled 自减为非正整 数时,可以开始竞争 accept_mutex。竞锁结果由 ngx_accept_mutex_held 表示。

  • worker 进程竞锁失败后,需马上放弃监听 listening sockets 上的读事件。并且在 ngx_accept_mutex_delay 时长后,可再次尝试竞锁,并且竞锁的频度增加。

  • worker 进程竞锁成功后,开始监听 listening sockets 上的读事件,接入新的连接。 直到某次竞锁失败时,才放弃获取 listening sockets 上的读事件 (ngx_disable_accept_events)。

  • accept_mutex 打开后,在某个时刻,可能会有两个 worker 进程的 listening sockets 正处于 epoll set 中:刚刚处理完事件并释放 accept_mutexworker 进程 和 刚刚竞锁成功的 worker 进程。但是刚刚释放 accept_mutexworker 进程在下一次竞锁如果失败的话,就会将其 listening socketsepoll set 中去除,所以,它其它并不会再次获取到 listening sockets 上的读事件。

  • 所有 worker 进程的 ngx_accept_disabled 都大于 0 时,都需等待其自减到非正整 数后,方可再次竞锁。此期间,所有 worker 进程 (除了最后那个拥有 accept_mutexworker 进程外) 均不再接入新连接。也就是说,在总连接数超过所有 worker 进程连接数上限之和的 7/8 后 (同时需要最后那个拥有 accept_mutexworker 进程连接数已达上限。因为在任一时刻,Nginx 保证有且只有一个worker 进程正在监听 listening sockets 的读事件),Nginx 接入新连接速度将变缓,新连接失败率增加,响应 时间增加。 RIGHT?

  • 启用epollmulti_accept 机制后,ngx_event_accept 会一直接收新连接 (如 果有的话),直到本进程连接数到达上限 (connection_n为止)。

  • 各连接在 worker 进程中,使用上述逻辑无法达到均分 (设想新连接到达速度不均衡的 情况下,某个 worker 可以始终拥有大部分连接)。但是,这种情况比较极端。

Comments

不要轻轻地离开我,请留下点什么...

comments powered by Disqus

Published

Category

Nginx

Tags

Contact