当前位置: 首页 > news >正文

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。

转载于:https://www.cnblogs.com/sxhlinux/p/6254396.html

相关文章:

  • MGW——美团点评高性能四层负载均衡
  • vue-router2.0 组件之间传参及获取动态参数
  • 电脑端下载今日头条的短视频
  • 分方式缓存常用的一致性hash是什么原理
  • webbench压力测试工具
  • 飞天5K实战经验:大规模分布式系统运维实践
  • 我的jQuery动态表格插件
  • ExcelReport第一篇:使用ExcelReport导出Excel
  • select空间提交form表单传递参数
  • 反射:InvokeMethod 活动调用多种方法的方法配置要点
  • wampserver配置https
  • 小型软件项目开发流程探讨
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • iOS根据网络图片的size大小设置UIImageView的大小
  • sqlmap 本地安装
  • 【node学习】协程
  • Bytom交易说明(账户管理模式)
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • ES6 ...操作符
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • Java IO学习笔记一
  • Js基础——数据类型之Null和Undefined
  • Laravel Mix运行时关于es2015报错解决方案
  • mysql_config not found
  • Terraform入门 - 3. 变更基础设施
  • vue--为什么data属性必须是一个函数
  • windows-nginx-https-本地配置
  • 搭建gitbook 和 访问权限认证
  • 基于 Ueditor 的现代化编辑器 Neditor 1.5.4 发布
  • 基于Dubbo+ZooKeeper的分布式服务的实现
  • 模仿 Go Sort 排序接口实现的自定义排序
  • 使用Gradle第一次构建Java程序
  • 世界上最简单的无等待算法(getAndIncrement)
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 我从编程教室毕业
  • 我有几个粽子,和一个故事
  • 无服务器化是企业 IT 架构的未来吗?
  • 应用生命周期终极 DevOps 工具包
  • 容器镜像
  • ​TypeScript都不会用,也敢说会前端?
  • ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException
  • #经典论文 异质山坡的物理模型 2 有效导水率
  • $(function(){})与(function($){....})(jQuery)的区别
  • (27)4.8 习题课
  • (Matalb时序预测)WOA-BP鲸鱼算法优化BP神经网络的多维时序回归预测
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (三)终结任务
  • (十)T检验-第一部分
  • (一)为什么要选择C++
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • ****** 二 ******、软设笔记【数据结构】-KMP算法、树、二叉树
  • .net CHARTING图表控件下载地址
  • .net FrameWork简介,数组,枚举
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter