热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Nginx中accept锁的机制与实现的使用

前言nginx采用多进程的模,当一个请求过来的时候,系统会对进程进行加锁操作,保证只有一个进程来接受请求。本文基于Nginx0.8.55源代码,并基于epoll机制分析1.acce
 前言
  nginx采用多进程的模,当一个请求过来的时候,系统会对进程进行加锁操作,保证只有一个进程来接受请求。
  本文基于Nginx 0.8.55源代码,并基于epoll机制分析
  1. accept锁的实现
  1.1 accpet锁是个什么东西
  提到accept锁,就不得不提起惊群问题。
  所谓惊群问题,就是指的像Nginx这种多进程的服务器,在fork后同时监听同一个端口时,如果有一个外部连接进来,会导致所有休眠的子进程被唤醒,而最终只有一个子进程能够成功处理accept事件,其他进程都会重新进入休眠中。这就导致出现了很多不必要的schedule和上下文切换,而这些开销是完全不必要的。
  而在Linux内核的较新版本中,accept调用本身所引起的惊群问题已经得到了解决,但是在Nginx中,accept是交给epoll机制来处理的,epoll的accept带来的惊群问题并没有得到解决(应该是epoll_wait本身并没有区别读事件是否来自于一个Listen套接字的能力,所以所有监听这个事件的进程会被这个epoll_wait唤醒。),所以Nginx的accept惊群问题仍然需要定制一个自己的解决方案。
  accept锁就是nginx的解决方案,本质上这是一个跨进程的互斥锁,以这个互斥锁来保证只有一个进程具备监听accept事件的能力。
  实现上accept锁是一个跨进程锁,其在Nginx中是一个全局变量,声明如下:
  ngx_shmtx_t   ngx_accept_mutex;
  这是一个在event模块初始化时就分配好的锁,放在一块进程间共享的内存中,以保证所有进程都能访问这一个实例,其加锁解锁是借由linux的原子变量来做CAS,如果加锁失败则立即返回,是一种非阻塞的锁。加解锁代码如下:
  static ngx_inline ngx_uint_t
  ngx_shmtx_trylock(ngx_shmtx_t *mtx)
  {
  return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
  }
  #define ngx_shmtx_lock(mtx) ngx_spinlock((mtx)->lock, ngx_pid, 1024)
  #define ngx_shmtx_unlock(mtx) (void) ngx_atomic_cmp_set((mtx)->lock, ngx_pid, 0)
  可以看出,调用ngx_shmtx_trylock失败后会立刻返回而不会阻塞。
  1.2 accept锁如何保证只有一个进程能够处理新连接
  要解决epoll带来的accept锁的问题也很简单,只需要保证同一时间只有一个进程注册了accept的epoll事件即可。
  Nginx采用的处理模式也没什么特别的,大概就是如下的逻辑:
  尝试获取accept锁
  if 获取成功:
  在epoll中注册accept事件FTP
  else:
  在epoll中注销accept事件
  处理所有事件
  释放accept锁
  当然这里忽略了延后事件的处理,这部分我们放到后面讨论。
  对于accept锁的处理和epoll中注册注销accept事件的的处理都是在ngx_trylock_accept_mutex中进行的。而这一系列过程则是在nginx主体循环中反复调用的void ngx_process_events_and_timers(ngx_cycle_t *cycle)中进行。
  也就是说,每轮事件的处理都会首先竞争accept锁,竞争成功则在epoll中注册accept事件,失败则注销accept事件,然后处理完事件之后,释放accept锁。由此只有一个进程监听一个listen套接字,从而避免了惊群问题。
  1.3 事件处理机制为不长时间占用accept锁作了哪些努力
  accept锁处理惊群问题的方案看起来似乎很美,但如果完全使用上述逻辑,就会有一个问题:如果服务器非常忙,有非常多事件要处理,那么“处理所有事件这一步”就会消耗非常长的时间,也就是说,某一个进程长时间占用accept锁,而又无暇处理新连接;其他进程又没有占用accept锁,同样无法处理新连接——至此,新连接就处于无人处理的状态,这对服务的实时性无疑是很要命的。
  为了解决这个问题,Nginx采用了将事件处理延后的方式。即在ngx_process_events的处理中,仅仅将事件放入两个队列中:
  ngx_thread_volatile ngx_event_t *ngx_posted_accept_events;
  ngx_thread_volatile ngx_event_t *ngx_posted_events;
  返回后先处理ngx_posted_accept_events后立刻释放accept锁,然后再慢慢处理其他事件。
  即ngx_process_events仅对epoll_wait进行处理,事件的消费则放到accept锁释放之后,来最大限度地缩短占有accept的时间,来让其他进程也有足够的时机处理accept事件。
  那么具体是怎么实现的呢?其实就是在static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)的flags参数中传入一个NGX_POST_EVENTS的标志位,处理事件时检查这个标志位即可。
  这里只是避免了事件的消费对于accept锁的长期占用,那么万一epoll_wait本身占用的时间很长呢?这种事情也不是不可能发生。这方面的处理也很简单,epoll_wait本身是有超时时间的,限制住它的值就可以了,这个参数保存在ngx_accept_mutex_delay这个全局变量中。
  下面放上ngx_process_events_and_timers 的实现代码,可以大概一观相关的处理:
  void
  ngx_process_events_and_timers(ngx_cycle_t *cycle)
  {
  ngx_uint_t flags;
  ngx_msec_t timer, delta;
  /* 省略一些处理时间事件的代码 */
  // 这里是处理负载均衡锁和accept锁的时机
  if (ngx_use_accept_mutex) {
  // 如果负载均衡token的值大于0, 则说明负载已满,此时不再处理accept, 同时把这个值减一
  if (ngx_accept_disabled > 0) {
  ngx_accept_disabled--;
  } else {
  // 尝试拿到accept锁
  if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
  return;
  }
  // 拿到锁之后把flag加上post标志,让所有事件的处理都延后
  // 以免太长时间占用accept锁
  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_accept_mutex_delay个毫秒,防止占用太久accept锁
  }
  }
  }
  }
  delta = ngx_current_msec;
  // 调用事件处理模块的process_events,处理一个epoll_wait的方法
  (void) ngx_process_events(cycle, timer, flags);
  delta = ngx_current_msec - delta; //计算处理events事件所消耗的时间
  ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,  "timer delta: %M", delta);
  // 如果有延后处理的accept事件,那么延后处理这个事件
  if (ngx_posted_accept_events) {
  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_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"posted events %p", ngx_posted_events);
  if (ngx_posted_events) {
  if (ngx_threaded) {
  ngx_wakeup_worker_thread(cycle);
  } else {
  // 处理所有的延后事件
  ngx_event_process_posted(cycle, &ngx_posted_events);
  }
  }
  }
  再来看看ngx_epoll_process_events的相关处理:
        //读事件                                      
        if ((revents & EPOLLIN) && rev->active) {
        if ((flags & NGX_POST_THREAD_EVENTS) && !rev->accept) {
        rev->posted_ready = 1;
        } else {
        rev->ready = 1;
        }                                       
        if (flags & NGX_POST_EVENTS) {
        queue = (ngx_event_t **) (rev->accept ?
        &ngx_posted_accept_events : &ngx_posted_events);
       ngx_locked_post_event(rev, queue);
        } else {
       rev->handler(rev);

         }
      }                                        
      wev = c->write;
      // 写事件
      if ((revents & EPOLLOUT) && wev->active) {
      if (flags & NGX_POST_THREAD_EVENTS) {
      wev->posted_ready = 1;
      } else {
       wev->ready = 1;
      }
      if (flags & NGX_POST_EVENTS) {
      ngx_locked_post_event(wev, &ngx_posted_events);
      } else {
     wev->handler(wev);
      }
      }

Nginx中accept锁的机制与实现的使用


推荐阅读
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了一种解析GRE报文长度的方法,通过分析GRE报文头中的标志位来计算报文长度。具体实现步骤包括获取GRE报文头指针、提取标志位、计算报文长度等。该方法可以帮助用户准确地获取GRE报文的长度信息。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
  • 深入理解CSS中的margin属性及其应用场景
    本文主要介绍了CSS中的margin属性及其应用场景,包括垂直外边距合并、padding的使用时机、行内替换元素与费替换元素的区别、margin的基线、盒子的物理大小、显示大小、逻辑大小等知识点。通过深入理解这些概念,读者可以更好地掌握margin的用法和原理。同时,文中提供了一些相关的文档和规范供读者参考。 ... [详细]
author-avatar
l彡id夏日阳光
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有