Nginx源码分析 - 主流程篇 - 多进程的惊群和进程负载均衡处理(15)

nginx源码阅读(一).综述 前言 nginx作为一款开源的轻量级高性能web服务器,是非常值得立志从事服务端开发方向的人学习的。现今nginx的最新版本是nginx-1.13.6,代码量也日渐庞大,但是由于其核心思想并没改变,为了降低阅读难度,我选择的是nginx-1.0.15版本,并且由于时间和水平有限,重点关注的是nginx的启动以及进程模型、事件模块中的epoll模块、负载均衡以及整体的框架等方面。 这里先推荐两本 阅读详情
AI摘要

本文介绍 Nginx 如何解决惊群问题并实现进程间的负载均衡。通过 ngx_process_events_and_timers 函数,Nginx 能够有效避免多个进程竞争同一资源,并确保连接请求均匀分配。

目录

Nginx解决惊群和进程负载均衡处理的要点

具体的实现

1. ngx_process_events_and_timers 进程事件分发器

2. ngx_trylock_accept_mutex 获取accept锁

3. ngx_enable_accept_events 和 ngx_disable_accept_events

4. ngx_event_process_posted 事件队列处理

5. ngx_process_events 事件的核心处理函数


Linux2.6版本之前还存在对于socket的accept的惊群现象。之后的版本已经解决掉了这个问题。

惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象。

Nginx采用的是多进程的模式。假设Linux系统是2.6版本以前,当有一个客户端要连到Nginx服务器上,Nginx的N个进程都会去监听socket的accept的,如果全部的N个进程都对这个客户端的socket连接进行了监听,就会造成资源的竞争甚至数据的错乱。我们要保证的是,一个链接在Nginx的一个进程上处理,包括accept和read/write事件。

 

Nginx解决惊群和进程负载均衡处理的要点


  • Nginx的N个进程会争抢文件锁,当只有拿到文件锁的进程,才能处理accept的事件。
  • 没有拿到文件锁的进程,只能处理当前连接对象的read事件
  • 当单个进程总的connection连接数达到总数的7/8的时候,就不会再接收新的accpet事件。
  • 如果拿到锁的进程能很快处理完accpet,而没拿到锁的一直在等待(等待时延:ngx_accept_mutex_delay),容易造成进程忙的很忙,空的很空

具体的实现


1. ngx_process_events_and_timers 进程事件分发器


此方法为进程实现的核心函数。主要作用:事件分发;惊群处理;简单的负载均衡。

负载均衡:

  • 当事件配置初始化的时候,会设置一个全局变量:ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
  • 当ngx_accept_disabled为正数的时候,connection达到连接总数的7/8的时候,就不再处理新的连接accept事件,只处理当前连接的read事件

惊群处理:

  • 通过ngx_trylock_accept_mutex争抢文件锁,拿到文件锁的,才可以处理accept事件。
  • ngx_accept_mutex_held是拿到锁的一个标志,当拿到锁了,flags会被设置成NGX_POST_EVENTS,这个标志会在事件处理函数ngx_process_events中将所有事件(accept和read)放入对应的ngx_posted_accept_events和ngx_posted_events队列中进行延后处理。
  • 当没有拿到锁,调用事件处理函数ngx_process_events的时候,可以明确都是read的事件,所以可以直接调用事件ev->handler方法回调处理。
  • 拿到锁的进程,接下来会优先处理ngx_posted_accept_events队列上的accept事件,处理函数:ngx_event_process_posted
  • 处理完accept事件后,就将文件锁释放
  • 接下来处理ngx_posted_events队列上的read事件,处理函数:ngx_event_process_posted
/**
 * 进程事件分发器
 */
void ngx_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
	}

	/**
	 * ngx_use_accept_mutex变量代表是否使用accept互斥体
	 * 默认是使用,可以通过accept_mutex off;指令关闭;
	 * accept mutex 的作用就是避免惊群,同时实现负载均衡
	 */
	if (ngx_use_accept_mutex) {

		/**
		 * 	ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
		 * 	当connection达到连接总数的7/8的时候,就不再处理新的连接accept事件,只处理当前连接的read事件
		 * 	这个是比较简单的一种负载均衡方法
		 */
		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_process_events的一个参数,这个函数中所有事件将延后处理。
				 * accept事件都放到ngx_posted_accept_events链表中,
				 * epollin|epollout普通事件都放到ngx_posted_events链表中
				 **/
				flags |= NGX_POST_EVENTS;

			} else {

				/**
				 * 1. 获取锁失败,意味着既不能让当前worker进程频繁的试图抢锁,也不能让它经过太长事件再去抢锁
				 * 2. 开启了timer_resolution时间精度,需要让ngx_process_change方法在没有新事件的时候至少等待ngx_accept_mutex_delay毫秒之后再去试图抢锁
				 * 3. 没有开启时间精度时,如果最近一个定时器事件的超时时间距离现在超过了ngx_accept_mutex_delay毫秒,也要把timer设置为ngx_accept_mutex_delay毫秒
				 * 4. 不能让ngx_process_change方法在没有新事件的时候等待的时间超过ngx_accept_mutex_delay,这会影响整个负载均衡机制
				 * 5. 如果拿到锁的进程能很快处理完accpet,而没拿到锁的一直在等待,容易造成进程忙的很忙,空的很空
				 */
				if (timer == NGX_TIMER_INFINITE
						|| timer > ngx_accept_mutex_delay) {
					timer = ngx_accept_mutex_delay;
				}
			}
		}
	}

	delta = ngx_current_msec;

	/**
	 * 事件调度函数
	 * 1. 当拿到锁,flags=NGX_POST_EVENTS的时候,不会直接处理事件,
	 * 将accept事件放到ngx_posted_accept_events,read事件放到ngx_posted_events队列
	 * 2. 当没有拿到锁,则处理的全部是read事件,直接进行回调函数处理
	 * 参数:timer-epoll_wait超时时间  (ngx_accept_mutex_delay-延迟拿锁事件   NGX_TIMER_INFINITE-正常的epollwait等待事件)
	 */
	(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);
	/**
	 * 1. ngx_posted_accept_events是一个事件队列,暂存epoll从监听套接口wait到的accept事件
	 * 2. 这个方法是循环处理accpet事件列队上的accpet事件
	 */
	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();
	}

	/**
	 *1. 普通事件都会存放在ngx_posted_events队列上
	 *2. 这个方法是循环处理read事件列队上的read事件
	 */
	ngx_event_process_posted(cycle, &ngx_posted_events);
}

2. ngx_trylock_accept_mutex 获取accept锁


  • ngx_accept_mutex_held是拿到锁的唯一标识的全局变量。
  • 当拿到锁,则调用ngx_enable_accept_events,将新的connection加入event事件上
  • 如果没有拿到锁,则调用ngx_disable_accept_events。
/**
 * 获取accept锁
 */
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;
		}

		/* 调用ngx_enable_accept_events,开启监听accpet事件*/
		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;
	}

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

	/**
	 * 没有拿到锁,但是ngx_accept_mutex_held=1
	 */
	if (ngx_accept_mutex_held) {
		/* 没有拿到锁,调用ngx_disable_accept_events,将accpet事件删除 */
		if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
			return NGX_ERROR;
		}

		ngx_accept_mutex_held = 0;
	}

	return NGX_OK;
}

3. ngx_enable_accept_events 和 ngx_disable_accept_events


/**
 * 开启accept事件的监听
 * 并将accept事件加入到event上
 */
static ngx_int_t ngx_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;
}

/**
 * 关闭accept事件的监听
 * 并将accept事件从event上删除
 */
static ngx_int_t ngx_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;

		/* 如果c->read->active,则表示是活跃的连接,已经被使用中 */
		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;
}

4. ngx_event_process_posted 事件队列处理


对ngx_posted_accept_events或ngx_posted_events队列上的accept/read事件进行回调处理。

/**
 * 处理事件队列
 *
 */
void ngx_event_process_posted(ngx_cycle_t *cycle, ngx_queue_t *posted) {
	ngx_queue_t *q;
	ngx_event_t *ev;

	while (!ngx_queue_empty(posted)) {

		q = ngx_queue_head(posted);
		ev = ngx_queue_data(q, ngx_event_t, queue);

		ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
				"posted event %p", ev);

		ngx_delete_posted_event(ev);

		/* 事件回调函数 */
		ev->handler(ev);
	}
}

5. ngx_process_events 事件的核心处理函数


这个方法,我们主要看epoll模型下的ngx_epoll_process_events方法(ngx_epoll_module.c)

  • 如果抢到了锁,则会将accpet/read事件放到队列上延后处理。
  • 没有抢到锁的进程都是处理当前连接的read事件,所以直接进行处理。
        /* 读取事件 EPOLLIN */
        if ((revents & EPOLLIN) && rev->active) {

#if (NGX_HAVE_EPOLLRDHUP)
            if (revents & EPOLLRDHUP) {
                rev->pending_eof = 1;
            }

            rev->available = 1;
#endif

            rev->ready = 1;

            /* 如果进程抢到锁,则放入事件队列 */
            if (flags & NGX_POST_EVENTS) {
                queue = rev->accept ? &ngx_posted_accept_events
                                    : &ngx_posted_events;

                ngx_post_event(rev, queue);

            } else {
            	/* 没有抢到锁,则直接处理read事件*/
                rev->handler(rev);
            }
        }

下一章节开始,我们会进入Nginx的事件模块。

Nginx Upstream模块源码分析(上) Upstream模块是一个很重要的模块,很多其他模块都会使用它来完成对后端服务器的访问, 达到反向代理和负载均衡的效果。例如Fastcgi、Memcached、SessionSticky等。 如果自己实现这部分功能,采用传统的实现方式,很可能会阻塞Nginx降低其性能,因为Nginx是全异步非阻塞的。 所以要想不破坏其优美的架构,就得按照其规范实现很多回调函数,注册这些钩子到Ngin 阅读详情

相关推荐

Nginx实现if多重判断配置方法示例

nginx的配置中不支持if条件的逻辑与/逻辑或运算 ,并且不支持if的嵌套语法,我们可以用变量的方式来实现: 首先是伪代码(即不被nginx支持),写在这里只是为了方便理解: 代码如下: if ($remote_addr ~ “^(12.34|56.78)” && $http_user_agent ~* “spider”) {     return 403; } 下面这是等效的,并真实可用的配置 代码如下: set $flag 0; if ($remote_addr ~ “^(12.34|56.78)”) {     set $flag “${flag}1”; } if ($

Nginx中进行简单的if-else判断

Nginx中进行简单的if-else判断

Lzp_csdn1的博客 5914

Dify + mem0插件实战:如何让AI记住你的饮食习惯(附完整代码)

本文通过Dify平台与mem0插件实战,详细演示了如何为AI助手构建长期记忆系统,使其能记住并理解用户的复杂饮食习惯(如过敏源、饮食偏好)。文章提供了完整的工作流配置与Python代码示例,帮助开发者实现跨会话的个性化饮食建议,解决AI对话中的“健忘”问题。

lg888的博客 1094

nginx如何实现if else

【代码】nginx如何实现if else。

Lubin的编程专栏 2653

Go语言集成Ollama本地大模型:gollama库实战指南

在当今的AI应用开发中,大型语言模型(LLM)的集成已成为关键技术。其核心原理是通过API调用,将自然语言处理能力嵌入到各类应用中。从技术价值看,本地化部署的LLM解决了数据隐私、网络延迟和持续成本三大痛点,尤其适合处理敏感数据或对实时性要求高的场景。在工程实践中,开发者常使用专门的客户端库来简化与模型服务的交互,例如通过封装REST API、提供类型安全接口和流式处理支持,从而提升开发效率和代码健壮性。具体到Go语言生态,结合Ollama这一流行的本地大模型运行工具,可以实现完全离线、低延迟的AI功能集成

weixin_28728443的博客 605

代谢组学数据分析实战:用R语言从PCA、PLS-DA到OPLS-DA一步步搞定(附代码)

本文详细介绍了使用R语言进行代谢组学数据分析的全流程,从数据预处理到PCA、PLS-DA和OPLS-DA模型构建与验证。通过实际案例和代码示例,帮助研究者掌握关键分析技术,提升数据解读能力,特别适合生命科学领域的研究人员。

weixin_28689507的博客 133

nginx逻辑指令笔记(if等)

nginx是不支持if else的。 if指令 if 指令语法是:if (condition) { … }; if 指令只能用于 server {} 和 location {} 中; if 指令的 condition 可以是: 单个变量 - 变量值为空字符串或者 “0” 时表达式为 false,其余情形为 true; 一元运算符和一个变量 - 支持的一元运算符有 -f, !-f, -d, !-d, -e, !-e, -x, !-x。这些运算符的具体作用参见官方文档; 二元运算符和两个变量 (或一个变量和一个

enthan809882的博客 1365

动态表单设计

自定一表单数据结构。内容展示如有必要行转列。前端根据题目类型自定义组件后端跟俊题目类型用策略模式校验各种类型这样有统计功能的简单动态问卷就可以了展示时可能某些场景需要行转列。大概估摸使用的话有可能kmic_form_data_detail表的记录数比较大,后期再考虑数据或者表迁移或拆分。

zquwei的博客 3455

解决:Nginx如何实现类似if else的检测

解决:Nginx如何实现类似if else的检测 前言: 项目中所用Gateway 在处理OAuth Notification时设置了certificate verification, 燃鹅架构师觉得这是internal url handling, 并不需要设置。 "x509: certificate signed by unknown authority" 所以在report ticket 的同时做了暂时的解决方案 -> 让OAuth Notification URL 发送到 public acc

weixin_43278912的博客 1万+

BAPI_OUTB_DELIVERY_CHANGE扩展指南:利用EXTENSION2与SMOD_V50B0001实现自定义字段更新

本文详细解析了如何利用BAPI_OUTB_DELIVERY_CHANGE的EXTENSION2参数与增强点SMOD_V50B0001,在不修改标准代码的前提下,安全、标准地更新SAP交货单的自定义字段。通过清晰的步骤指南和实战代码示例,帮助开发者掌握通过增强字段实现定制化业务需求的完整流程与调试技巧。

fanta的博客 451

c++ webrtc基础(1) :webrtc源码下载与编译、项目模块介绍、学习资料推荐

本篇博客介绍webrtc源码的下载与编译,以及推荐webrtc学习资料。 源码下载 电脑环境:win10/win11 64bit,合理的上网环境,没有合理的网络环境无法拉取、下载webrtc代码和工具,宽带速度要快,尽量50M. 主要步骤如下: (1) 安装git (2) 打开cmd控制台,下载google开发工具depot_tools: git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git (3) 新建一个目

琅嬛福地 3295

nginx配置文件中对于if条件语句的写法(附nginx跨域文件配置)

前言 在nginx配置文件中,可以使用if语句,但是对于else语句其实是不支持的,并且and条件和or条件也是不支持的 实现 else条件的写法 新建一个开关变量flag,初始值为0,如果为1说明进入了if set $flag 0; if () { // 这里是if的代码 // ----- // 将控制变量置为1,代表已经进入了if set $flag 1; } if ($flag != 1) { // 进入这里说明没有进入if中 // 这里是else的代码 } or条件的写法

蓝枫秋千的踩坑记录 1万+

基于潜在空间分数匹配的混合型表格数据合成方法

然而,在我们的模型中,β 预计会更小,因为我们不需要嵌入的分布精确地遵循标准高斯分布,因为我们还有一个额外的扩散模型。受 Transformer 在表格数据分类/回归任务中取得成功的启发,我们首先为每一列学习一个独特的标记器,然后将标记(列)级表示输入到 Transformer 中,以捕捉列之间错综复杂的关系。在第 4 节中,我们证明了通过这种设计,TABSYN 可以在不到 20 次函数评估(NFE)的情况下生成高质量的合成表格数据,这比其他基于扩散的表格数据合成方法要少得多。是标记器的可学习参数。

步子哥的博客 853

Nginx源码分析(25篇)

1、Nginx源码分析 - 初探Nginx的架构(01) 2、Nginx源码分析 - 基础数据结构篇 - 内存池 ngx_palloc.c(02) 3、Nginx源码分析 - 基础数据结构篇 - 数组结构 ngx_array.c(03) 4、Nginx源码分析 - 基础数据结构篇 - 缓冲区结构 ngx_buf.c(04) 5、Nginx源码分析 - 基础数据结构篇 - 双向链表结构 ngx_queue.c(05) 6、Nginx源码分析 - 基础数据结构篇 - 单向链表结构 ngx_list.c(06)

xiaohuozi2015的博客 5029

AI Agent开发第85课-DeepSeek-OCR 安装全指南:从 Transformers 到 vLLM,手把手教你跑通企业级 OCR

本文系统讲解 DeepSeek-OCR 的两种部署方式——基于 Hugging Face Transformers 的标准推理与基于 vLLM 的高性能加速推理。详细说明 CUDA 与 PyTorch 版本匹配、环境验证、依赖安装等关键细节,并提供可直接运行的完整代码示例。无论你是单卡调试还是高并发服务部署,都能在这里找到清晰路径。

打造全国最全的AI Agent开发知识领域的博客 2542

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

菜鸟nginx源码剖析架构篇(二) nginx进程模型

nginx采用的是多进程模型,典型的master-worker方式,采用一个master process(监控进程,也叫做主进程)和多个woker process(工作进程)的设计方式,此外,还有1个可选的chache manager和 1 个可选的cache loader进程。

@Echo· 个人技术笔记 1万+

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

Nginx源码解析

Nginx

qq_20999867的博客 640

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

nginx负载均衡的5种策略及原理

nginx的upstream目前支持的5种方式的分配 1、轮询(默认) 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。  upstream backserver {  server 192.168.0.14;  server 192.168.0.15;  } 2、指定权重 指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。  upst...

zxing的博客 9万+

解析 Nginx 负载均衡策略

1 前言 随着网站负载的不断增加,负载均衡(load balance)已不是陌生话题。负载均衡是将流量负载分摊到不同的服务单元,保证服务器的高可用,保证响应足够快,给用户良好的体验。 nginx第一个公开版发布于2004年。2011年发布了1.0版。它的特点是稳定性高、功能强大、资源消耗低。从服务器市场占有率来看,nginx已有与Apache分庭抗礼势头。其中,不得不提到的特性就是其负载均衡功...

October 491

解析nginx负载均衡

原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处、作者信息和本声明。否则将追究法律责任。http://baidutech.blog.51cto.com/4114344/1033718 摘要:对于一个大型网站来说,负载均衡是永恒的话题。随着硬件技术的迅猛发展,越来越多的负载均衡硬件设备涌现出来,如F5 BIG-IP、Citrix NetScaler、Radware等等...

dkyptnz538386的博客 106
上一篇: Nginx源码分析 - 主流程篇 - 多进程实现(14)
下一篇: Nginx源码分析 - Event事件篇 - Nginx的Event事件模块概览(17)
老码农zhuli
老码农zhuli 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄14年 2456粉丝 312原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值