accept_mutex与性能的关系 (nginx)

注:运行环境CentOS 6+

背景

在对启动了20个worker的nginx进行压力测试的时候发现:如果把配置文件中event配置块中的accept_mutex开关打开(1.11.3版本之前默认开),就会出现worker压力不均,少量的worker的cpu利用率达到了98%,大部分的worker的压力只有1%左右;如果把accept_mutex关掉,所有的worker的压力差别就不大,而且QPS会有大幅提升;

分析过程

  1. nginx的 1(master)+N(worker) 多进程模型:master在启动过程中负责读取nginx.conf中配置的监听端口,然后加入到一个cycle->listening数组中。
  2. init_cycle函数中会调用init_module函数,init_module函数会调用所有注册模块的module_init函数完成相关模块所需资源的申请以及其他一些工作;其中event模块的module_init函数申请一块共享内存用于存储accept_mutex锁信息以及连接数信息

    ////////////   nginx/src/event/ngx_event.c   ///////
    
        shm.size = size;
        shm.name.len = sizeof("nginx_shared_zone") - 1;
        shm.name.data = (u_char *) "nginx_shared_zone";
        shm.log = cycle->log;
    
        if (ngx_shm_alloc(&shm) != NGX_OK) {
            return NGX_ERROR;
        }
    
        shared = shm.addr;
    
        ngx_accept_mutex_ptr = (ngx_atomic_t *) shared;
        ngx_accept_mutex.spin = (ngx_uint_t) -1;
    
        if (ngx_shmtx_create(&ngx_accept_mutex, (ngx_shmtx_sh_t *) shared,
                             cycle->lock_file.data)
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    

所有的worker进程均是由master进程通过fork() 函数启动的,所以所有的worker进程也就继承了master进程所有打开的文件描述符(包括之前创建的共享内存的fd)以及变量数据(这其中就包括之前创建的accept_mutex锁)。worker启动的过程中会调用各个模块的process_init函数,其中event模块的process_init函数中就会将master配置好的listening数组加入到epoll监听的events中,这样初始阶段所有的worker的epoll监听列表中都包含listening数组中的fd。

////////////   nginx/src/event/ngx_event.c   ///////

    /* for each listening socket */

    ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {

#if (NGX_HAVE_REUSEPORT)
        if (ls[i].reuseport && ls[i].worker != ngx_worker) {
            continue;
        }
#endif

        c = ngx_get_connection(ls[i].fd, cycle->log);

        if (c == NULL) {
            return NGX_ERROR;
        }

        c->type = ls[i].type;
        c->log = &ls[i].log;

        c->listening = &ls[i];
        ls[i].connection = c;

        rev = c->read;

        rev->log = c->log;
        rev->accept = 1;
        ...............

当各个worker实际运行时,就会执行ngx_process_events_and_timers函数,这个函数会获取accept_mutex锁,同时所有的事件也会增加一个NGX_POST_EVENTS标识,其他获取不到的锁的worker就会将listening数组中fd从epoll监听列表中移除,这样下次listenling数组发生新的连接时,只有持有accept_mutex的worker才会响应。

////////////   nginx/src/event/ngx_event.c   ///////

     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;          //增加NGX_POST_EVENTS标识

            } else {
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

////////////////////////
ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "accept mutex locked");

        if (ngx_accept_mutex_held && ngx_accept_events == 0) {
            return NGX_OK;
        }

        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {     //将cycle->listening加入到当前worker进程的epoll
            ngx_shmtx_unlock(&ngx_accept_mutex);
            return NGX_ERROR;
        }

        ngx_accept_events = 0;
        ngx_accept_mutex_held = 1;

        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "accept mutex lock failed: %ui", ngx_accept_mutex_held);

    if (ngx_accept_mutex_held) {
        if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {     //将cycle->listening从当前worker进程的epoll移除
            return NGX_ERROR;
        }

        ngx_accept_mutex_held = 0;
    }

    return NGX_OK;
}

对于持有accept_mutex锁的worker进程,会将epoll返回的fd放到队列中,尽可能的从epoll中接受新连接。当不持有accept_mutex 锁时在对队列中的这些fd进行处理。

/////////////////  nginx/src/event/modules/ngx_epoll_module.c  //////////

    if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;

                ngx_post_event(rev, queue);

            } else {
                rev->handler(rev);
            }

worker每次处理从epoll中读取新连接结束后,就会调用accept处理新建连接,并调用新连接的异步回掉函数进行处理,在处理新建连接的同时会实时统计1/8 * worker_connection - free_connecttion 的差值ngx_accept_disabled 。处理accept新连接结束后,就会释放accept_mutex锁,然后在去处理一些普通的连接请求。worker的下次cycle,会首先判断ngx_accept_disabled的值,如果大于0说明该worker当前处理连接数大于总连接数的7/8,不再参与accept_mutex的竞争。

////////////    nginx/src/event/ngx_event.c ///////////

    (void) ngx_process_events(cycle, timer, flags);

    delta = ngx_current_msec - delta;

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "timer delta: %M", delta);

    ngx_event_process_posted(cycle, &ngx_posted_accept_events);

    if (ngx_accept_mutex_held) {
        ngx_shmtx_unlock(&ngx_accept_mutex);
    }

    if (delta) {
        ngx_event_expire_timers();
    }

    ngx_event_process_posted(cycle, &ngx_posted_events);

结论

  1. 上述分析的主要是accept_mutex打开的情况。对于不打开的情况,比较简单,所有worker的epoll都会监听listening数组中的所有fd,所以一旦有新连接过来,就会出现worker“抢夺资源“的情况。对于分布式的大量短链接来讲,打开accept_mutex选项较好,避免了worker争夺资源造成的上下文切换以及try_lock的锁开销。但是对于传输大量数据的tcp长链接来讲,打开accept_mutex就会导致压力集中在某几个worker上,特别是将worker_connection值设置过大的时候,影响更加明显。因此对于accept_mutex开关的使用,根据实际情况考虑,不可一概而论。
  2. 根据分析我们的压测程序,发现是采用的长tcp连接的方式,然后调用http请求;而且worker_connection也比较大,这样就出现了accept_mutex打开worker负载不均造成QPS下降的问题。
  3. 目前新版的Linux内核中增加了EPOLLEXCLUSIVE选项,nginx从1.11.3版本之后也增加了对NGX_EXCLUSIVE_EVENT选项的支持,这样就可以避免多worker的epoll出现的惊群效应,从此之后accept_mutex从默认的on变成了默认off。
时间: 2024-10-10 02:25:43

accept_mutex与性能的关系 (nginx)的相关文章

报表工具与报表性能的关系

在选择报表工具时,性能指标一向是用户非常关心的,但是,报表工具的性能和整个报表系统的性能会有多大关系呢? 要回答这个问题,首先要分析一下报表的处理过程包含哪些环节,其中有哪些环节容易出现性能问题,如何优化这些环节. 一.报表处理的一般过程分析 1.用户选择报表输入参数后,报表引擎会根据报表模板和输入参数来解析报表,并将数据计算和读取请求以SQL的方式发送给数据库. 2.数据层负责读取.计算和返回数据.数据层一般都是传统关系数据库:如Oracle.DB2等. 3.数据层通过JDBC等接口将结果数据

浅析I/O处理过程与存储性能的关系

浅析I/O处理过程与存储性能的关系 https://community.emc.com/docs/DOC-28653 性能”这个词可以说伴随着整个IT行业的发展,每次新的技术出现,从硬件到软件大多数情况下都围绕着性能提升而展开.“摩尔定理”指出CPU的处理速度每18个月会翻一番,但是进入21世纪的第二个十年来,似乎它的速度慢了下来.但是IT行业的各个行业领导者们,还是不断在计算机的性能寻求突破,继续挑战物理极限.细看存储行业,每款新的存储产品的推出,也围绕着如何更快.更好的服务前端服务器的I/O

HTTP/2 服务器推送(Server Push)教程(HTTP/2 协议的主要目的是提高网页性能,配置Nginx和Apache)

HTTP/2 协议的主要目的是提高网页性能. 头信息(header)原来是直接传输文本,现在是压缩后传输.原来是同一个 TCP 连接里面,上一个回应(response)发送完了,服务器才能发送下一个,现在可以多个回应一起发送. 服务器推送(server push)是 HTTP/2 协议里面,唯一一个需要开发者自己配置的功能.其他功能都是服务器和浏览器自动实现,不需要开发者关心. 本文详细介绍服务器推送的原理和配置方法. 一.传统的网页请求方式 下面是一个非常简单的 HTML 网页文件index.

API网关性能比较:NGINX vs. ZUUL vs. Spring Cloud Gateway vs. Linkerd(转)

前几天拜读了 OpsGenie 公司(一家致力于 Dev & Ops 的公司)的资深工程师 Turgay ?elik 博士写的一篇文章(链接在文末),文中介绍了他们最初也是采用 Nginx 作为单体应用的网关,后来接触到微服务架构后开始逐渐采用了其他组件. 我对于所做的工作或者感兴趣的技术,喜欢刨根问底,所以当读一篇文章时发现没有看到我想要看到的设计思想,我就会四处搜集资料,此外这篇文章涉及了我正在捣鼓的 Spring Cloud,所以我就决定写一篇文章,争取能从设计思路上解释为什么会有这样的性

缓存命中和性能的关系论证

<性能之巅>中关于性能和缓存部分,有两点在读到是有一些困惑,做以下思考. 1. 为什么99%的缓存命中,和98%的缓存命中,两者性能差距,远大于11%和10%的差距 具体的论证仔细思考了一下,可以推导如下: 现做以下变量定义: k:命中率,[0,1]之间 t:没有命中的处理耗时,[1,max],此处假设命中后的处理时间是1,此处假定命中和不命中的处理速度为固定倍数差异. A:总的任务量 T:总的开销时间 于是有以下公式: T = A * k *  1 + A * (1-k)* t 进行推导,就

vue-小爱ADMIN系列文章(二):微信微博等分享,国际化,前端性能优化,nginx服务器部署

最近在做我的小爱ADMIN后台管理系统,结合当前市场后台管理系统对相关功能的需求,我又开始新增了一些新的功能和组件,如分享功能组件,项目国际化功能:项目完成后,部署在nginx服务器,发现首次访问的速度特别慢,严重的影响了用户体验,因此,我又开始进行了一系列的前端性能优化;以及将优化后的项目部署到nginx服务器二级子目录的注意细节. 效果演示地址 github地址 分享功能 背景说明 用微信,微博等做网站的第三方登录及用微信和支付宝进行支付,都需要注册开发者账号和添加网站应用,比较麻烦.另外,

索引的结构和性能的关系

InnoDB和MyISAM使用了b+树和b树作为索引组织的方式. 在这些结构中,索引的深度是个关键因素.当查找被索引了的行时,查找会在索引上从根到叶子执行. 假设这些索引不在内存中,索引的深度就代表了查找的(IO)代价.当然,我们希望大部分的查找都在内存中执行(被cache在内存中).尽管如此,索引的深度仍然是一个重要因素.索引深度越深,查找越慢. 那么什么影响了索引的深度? 虽然有相当多的结构问题,但可以归结为两个重要的点: 1.表的行数: 行数越多,索引的行数越多,导致索引的深度越深 2.索

Nginx 的线程池与性能剖析

http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt158 正如我们所知,NGINX采用了异步.事件驱动的方法来处理连接.这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求.为此,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue这样有效的方法. 因为满负载进程的数量很少(通常每核CPU只有一个)而且恒定,所以任务切换只消耗很少的内

Nginx 的线程池与性能剖析【转载】

正如我们所知,NGINX采用了异步.事件驱动的方法来处理连接.这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求.为此,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue这样有效的方法. 因为满负载进程的数量很少(通常每核CPU只有一个)而且恒定,所以任务切换只消耗很少的内存,而且不会浪费CPU周期.通过NGINX本身的实例,这种方法的优点已经为众人所知.NGINX可以非常好地处理百万级规模的并