Nginx源码分析:惊群处理与负载均衡
发布日期:2021-07-25 13:04:57 浏览次数:8 分类:技术文章

本文共 6116 字,大约阅读时间需要 20 分钟。

nginx源码分析

nginx-1.11.1参考书籍《深入理解nginx模块开发与架构解析》

Nginx的惊群处理与负载均衡概述

当Nginx工作在master/worker模式下时,就会涉及到多个子进程共同接受请求并处理请求,由于在早期版本的Linux内核中,当多个子进程监听同一个端口的时候,此时当新连接请求进入的时候,多个子进程会被唤醒,但是能处理的新进的连接就这有一个子进程处理,导致其他被唤醒的子进程接受请求失败,导致新增了唤醒子进程的系统开销进而影响服务器处理请求的性能(主要是accept建立连接,当前Linux内核已经处理了该问题);由于Nginx使用的epoll模型中在每个子进程中,都重新初始化一个epfd,但是监听的却是同一个socket,会导致监听的套接字在每个子进程会被epoll唤醒,Nginx通过了锁的方式进行了处理(有关epoll惊群的内容大家可以自行查阅资料)。由于多个子进程之间监听同一个端口时,会造成各个子进程之间的处理请求会不一样,所以Nginx也采取了负载均衡的策略同时也通过锁的机制来在应用层保证同一个请求被一个子进程处理。

惊群处理与负载均衡

Nginx中为了保证在接受请求与epoll处理过程中,使用了锁来规避这个问题,本文我们就在代码中去查看相关内容。

请求与时间处理函数

在前文的分析中,子进程处理请求的核心函数如下;

ngx_process_events_and_timers(cycle);

处理通知时间与定时器相关操作。

voidngx_process_events_and_timers(ngx_cycle_t *cycle){    ngx_uint_t  flags;    ngx_msec_t  timer, delta;    if (ngx_timer_resolution) {                         // 设置了时间精度        timer = NGX_TIMER_INFINITE;         flags = 0;    } else {        timer = ngx_event_find_timer();                 // 查找定时器任务找到最近的定时器        flags = NGX_UPDATE_TIME;#if (NGX_WIN32)        /* handle signals from master in case of network inactivity */        if (timer == NGX_TIMER_INFINITE || timer > 500) {            timer = 500;        }#endif    }    if (ngx_use_accept_mutex) {                                     // 是否使用accept_mutex        if (ngx_accept_disabled > 0) {                              // 检查当前进程是否可以继续处理请求如果大于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;                 // 获取当前最小的延迟时间                }            }        }    }    delta = ngx_current_msec;    (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);     // 处理accept请求    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);            // 处理正常的读写事件请求}

此时,从该代码可知查看ngx_trylock_accept_mutex,该函数如下;

ngx_int_tngx_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) {                         // 添加监听的读事件如果失败            ngx_shmtx_unlock(&ngx_accept_mutex);                                    // 释放锁            return NGX_ERROR;                                                       // 返回错误        }        ngx_accept_events = 0;                                                      // 成功重置标志位        ngx_accept_mutex_held = 1;                                                  // 获取锁标志置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) {                     // 删除读标志            return NGX_ERROR;        }        ngx_accept_mutex_held = 0;                                                  // 获取锁标志重置    }    return NGX_OK;                                                                  // 返回成功}static ngx_int_tngx_enable_accept_events(ngx_cycle_t *cycle){    ngx_uint_t         i;    ngx_listening_t   *ls;    ngx_connection_t  *c;    ls = cycle->listening.elts;    for (i = 0; i < cycle->listening.nelts; i++) {                                  // 遍历监听列表        c = ls[i].connection;        if (c == NULL || c->read->active) {                                         // 如果为空或者读已经使用则查找下一个            continue;        }        if (ngx_add_event(c->read, NGX_READ_EVENT, 0) == NGX_ERROR) {               // 添加读事件            return NGX_ERROR;        }    }    return NGX_OK;}static ngx_int_tngx_disable_accept_events(ngx_cycle_t *cycle, ngx_uint_t all){    ngx_uint_t         i;    ngx_listening_t   *ls;    ngx_connection_t  *c;    ls = cycle->listening.elts;                                                // 遍历监听列表    for (i = 0; i < cycle->listening.nelts; i++) {        c = ls[i].connection;        if (c == NULL || !c->read->active) {                                    // 如果为空或者不为读状态的连接            continue;        }#if (NGX_HAVE_REUSEPORT)        /*         * do not disable accept on worker's own sockets         * when disabling accept events due to accept mutex         */        if (ls[i].reuseport && !all) {            continue;        }#endif        if (ngx_del_event(c->read, NGX_READ_EVENT, NGX_DISABLE_EVENT)           // 删除监听的读事件            == NGX_ERROR)        {            return NGX_ERROR;        }    }    return NGX_OK;}

检查是否获取锁的状态,并根据获取锁添加读事件,该代码其中也说明了Nginx的简单负载均衡的调度模式;

ngx_accept_disabled = ngx_cycle->connection_n / 8                          - ngx_cycle->free_connection_n;

该行代码就是判断当前的子进程连接的数量达到了7/8的时候,此时就不会让该子进程去注册读事件去处理连接请求,而是让该进程处理已经连接进来的请求的读写时间。由此可知Nginx的worker的负载均衡与accept的惊群的处理过程。

总结

本文只是简单的概述了Nginx有关子进程的负载均衡与惊群问题的简单实现,主要通过检查已经连接的请求数量如果达到了7/8则该子进程就不再处理新连接的请求,通过锁来实现一个请求来处理连接接入的请求从而规避了惊群的问题。由于本人才疏学浅,如有错误请批评指正。

转载地址:https://blog.csdn.net/qq_33339479/article/details/89229029 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Nginx源码分析:epoll事件处理模块概述
下一篇:Nginx源码分析:核心数据结构ngx_cycle_t与内存池概述

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年03月16日 05时40分37秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

php redis zrevrange,Redis Zrevrange 命令 2019-04-21
php利用word模板导出excel文件,php生成导出word doc和excel文件实例 2019-04-21
java 边缓存边播放,java动态缓存技术:WEB缓存应用 2019-04-21
php云盘匿名,PHP7之匿名类 2019-04-21
matlab数据大小不兼容,MATLAB无法执行赋值,因为左侧的索引与右侧的大小不兼容。 求解... 2019-04-21
editor.md使用php,editor.md 配置参数和使用方法 2019-04-21
python mod,mod_python的安装 2019-04-21
python分析彩票数据,这波太炸了!Python脚本可视化居然可以这么玩 2019-04-21
简单的mysql重置root密码,重置mysql的root密码最简单的方法 2019-04-21
用matlab仿真mmc环流抑制器,一种基于准PR控制原理的MMC阀组环流抑制方法 2019-04-21
oracle 排序的分析函数,Oracle SQL:使用分析排序函数 2019-04-21
oracle direct for hdfs xi下载,ORACLE连接HDFS有个专项的解决方案 2019-04-21
java 403怎么抛出_java – 如何在Spring MVC中返回403禁止? 2019-04-21
java jsch工具类_Java工具集-JSch连接远程服务器工具类 2019-04-21
cmd背景变红1003无标题_怎样修改cmd中文字的大小、颜色和背景颜色呢 原来是这样的... 2019-04-21
php rand() 重复,php – mt_rand()给我总是相同的数字 2019-04-21
php taglib.php,thinkphp5 taglib自定义标签教程 2019-04-21
java常用包类 array,Java中的StringBuffer和数组Arrays以及常用类型的包装类 2019-04-21
ctf常见php,CTF中常见的PHP伪协议 2019-04-21
php语言冒泡法,PHP 冒泡排序法 2019-04-21