`
bollaxu
  • 浏览: 219894 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Nginx事件处理(epoll)

阅读更多

事件处理是Nginx处理请求的核心,每个子进程在ngx_worker_process_cycle()的循环里不断调用ngx_process_events_and_timers()函数来处理各种事件。下面,分析使用epoll机制下(Linux最常用支持大并发的事件触发机制)Nginx事件处理的过程,用源代码分析和debug信息追踪两种方法。


我们从ngx_worker_process_cycle()函数(即工作进程处理请求的循环)切入:

static void ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
{
	/*...*/
	//第一部分:初始化
	ngx_worker_process_init(cycle, 1);
	/*...*/
	for ( ;; ) {
		/*...*/
		//第二部分:处理事件
		ngx_process_events_and_timers(cycle);
		/*...*/
	}
	/*...*/
}
 

//第一部分:初始化
static void ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority)
{
	//配置一些环境变量
	//...
	//设置uid,groupid等
	//...
	//如果有设置CPU affinity
	//...
	//换到当前工作的目录下
	//...
	//清空所有的信号
	//...
	//清掉监听socket上以前的事件
	//...

	//调用所有模块的init_process钩子函数
	//所有模块-->每个子进程可以调用这些模块的功能
	//init_process
	for (i = 0; ngx_modules[i]; i++) {
		if (ngx_modules[i]->init_process) {
			//如果是event module: ngx_event_process_init()被调用
			if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
				exit(2);
			}
		}
	}
	//将其他进程的channel[1]关闭,自己的除外
	//子进程继承了父进程的ngx_processes数组,但子进程只监听自己的channel[1]
	//...
	//将自己的channel[0]关闭
	//因为自己的channel[0]是给其他子进程,用来发送消息的sendmsg
	//...
	//调用ngx_add_channel_event()函数,给ngx_channel注册一个读事件处理函数。
	//在ngx_start_worker_processes()函数中,ngx_channel = ngx_processes[s].channel[1];
	//ngx_channel就是进程自身的channel[1],用来读取的socket
	//ngx_channel_handler处理从channel中收到的信号,当事件触发时,调用这个方法
	if (ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler) == NGX_ERROR)
	{
		exit(2);
	}
}
 

每个模块(module)都有一个全局的ngx_module_t结构变量,在worker process被创建(fork)以后,在ngx_worker_process_init()内调用到每个模块的init_process钩子。其中ngx_event_core_module的init_process钩子指向的是ngx_event_process_init()函数,这个函数是这样的:

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
	/*...*/

	for (m = 0; ngx_modules[m]; m++) {
		//只有ngx_event_core_module和ngx_epoll_module是NGX_EVENT_MODULE类型的
		if (ngx_modules[m]->type != NGX_EVENT_MODULE) {
			continue;
		}
		if (ngx_modules[m]->ctx_index != ecf->use) {
			continue;
		}
		//获取模块上下文
		module = ngx_modules[m]->ctx;
		//初始化模块
		//ngx_epoll_module(类型ngx_module_t)是全局的结构变量,在初始化的时候由ngx_epoll_module_ctx传入参数,而init函数也在这个时候确定
		//如epoll就是ngx_epoll_init
		if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) {
			exit(2);
		}
		break;
	}

	/*...*/

	/*=========初始化connections=========*/

	//分配connection_n个空间给connections
	cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
	c = cycle->connections;
	//分配connection_n个空间给read_events和write_events,初始化这些read_events和write_events
	cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
	rev = cycle->read_events;
	for (i = 0; i < cycle->connection_n; i++) {
		rev[i].closed = 1;
		rev[i].instance = 1;
		/*不考虑线程*/
	}
	cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n, cycle->log);
	wev = cycle->write_events;
	for (i = 0; i < cycle->connection_n; i++) {
		wev[i].closed = 1;
		/*不考虑线程*/
	}

	//把connection和read_event,write_event联系起来,每个connection都指向一个read_event和write_event
	i = cycle->connection_n;
	next = NULL;
	do {
		//把connection链接起来
		i--;
		c[i].data = next;
		c[i].read = &cycle->read_events[i];
		c[i].write = &cycle->write_events[i];
		c[i].fd = (ngx_socket_t) -1;
		next = &c[i];
		/*不考虑线程*/
	} while (i);

	//free_connections指向connections的头
	cycle->free_connections = next;
	//初始化free connection的数目
	cycle->free_connection_n = cycle->connection_n;

	/* for each listening socket */
	ls = cycle->listening.elts;
	for (i = 0; i < cycle->listening.nelts; i++) {
		c = ngx_get_connection(ls[i].fd, cycle->log);//获取一个空闲的connection,并设置其file descriptor
		c->log = &ls[i].log;
		c->listening = &ls[i];//获取ngx_listening_s结构
		ls[i].connection = c; //设置ngx_listening_s.connection
		rev = c->read;
		rev->log = c->log;
		rev->accept = 1;      //接受请求
	
		/*...*/

		//设置c->read的handler为ngx_event_accept
		rev->handler = ngx_event_accept;
		if (ngx_use_accept_mutex) {
			continue;
		}
		if (ngx_event_flags & NGX_USE_RTSIG_EVENT) {
			if (ngx_add_conn(c) == NGX_ERROR) {
				return NGX_ERROR;
			}
		} 
		else {
			if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
				return NGX_ERROR;
			}
		}
	}
}
 

在上面的函数里,如果我们使用了epoll,那么epoll模块的ngx_epoll_init()函数就会被调用,而这个函数中最重要的就是

ngx_event_actions = ngx_epoll_module_ctx.actions
 

在工作进程的for循环中用来处理事件的ngx_process_events_and_timers()中,每次调用ngx_process_events()的时候,其实就是调用ngx_epoll_module_ctx.actions里面的ngx_epoll_process_events()。


//第二部分:处理事件
//被循环调用
//先接收连接(并不处理事件),以及处理进程间信号(如有)
//处理accept queue和event queue里面的事件
void ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
    	
	//如果使用了accept mutex  
	//nginx uses accept mutex to serialize accept() syscalls
	//多进程需要使用mutex
	if (ngx_use_accept_mutex) {
		//空闲连接数过少
		if (ngx_accept_disabled > 0) {
			ngx_accept_disabled--;
		}
		else {
			//调用ngx_trylock_accept_mutex()试着给cycle上锁,并把ngx_accept_mutex_held设为1
			//如果成功获得lock,将调用ngx_enable_accept_events()
			//ngx_enable_accept_events()中会调用ngx_add_event()/ngx_add_conn()
			//即,获得lock的worker process才会添加一个“接受请求”的事件
			if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
				return;
			}
			//ngx_accept_mutex_held表示当前是否已经持有锁
			//如果持有的话,就把flags添加NGX_POST_EVENTS,这样表明可以去accept请求
			//如果不持有,就去处理其他事件,在ngx_epoll_process_events里会调用
			//rev/wev->handler()
			if (ngx_accept_mutex_held) {
				flags |= NGX_POST_EVENTS;
			}
			else {
				if (timer == NGX_TIMER_INFINITE || 
				    timer > ngx_accept_mutex_delay) {
					//ngx_accept_mutex_delay 当获得锁失败后,再次去请求锁的间隔时间
					timer = ngx_accept_mutex_delay;
				}
			}
		}
	}

    	//#define ngx_process_events   ngx_event_actions.process_events
	//In epoll, ngx_event_actions = ngx_epoll_module_ctx.actions;
	//全局变量ngx_epoll_module_ctx(类型ngx_event_module_t),内有actions(类型ngx_event_actions_t),定义process_events钩子
	//钩子调用epoll的ngx_epoll_process_events()
	(void) ngx_process_events(cycle, timer, flags);

	delta = ngx_current_msec - delta;//计算process所用时间

	//ngx_posted_accept_events队列不为空-->有accept事件发生,就去处理
	//accept事件,其实最后就是调用accept函数接收新的连接
	//rev->handler = ngx_event_accept在ngx_event_process_init里面设置
	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();
	}

	//除了accept事件之外的其他事件放在这个队列中,如果队列不为空,就去处理相关的事件
	if (ngx_posted_events) {
		//一般不用线程来处理
		if (ngx_threaded) {
			ngx_wakeup_worker_thread(cycle);
		} else {
			ngx_event_process_posted(cycle, &ngx_posted_events);
		}
	}
}
 

说到这里不得不提一下一个很重要的结构ngx_module_t,这里面定义了一些非常重要的函数钩子:

struct ngx_module_s {
	//ctx_index是分类的模块计数器,nginx的模块可以分为四种:core、event、http和
	//mail,每一种的模块又会各自计数一下,这个ctx_index就是每个模块在其所属类组的计数值

	ngx_uint_t            ctx_index;    

	//index是一个模块计数器,按照每个模块在ngx_modules[]数组中的声明顺序
	//(见objs/ngx_modules.c),从0开始依次给每个模块进行编号
	ngx_uint_t            index; 

	/*...*/
	ngx_uint_t            version;

	//ctx是模块的上下文,不同种类的模块有不同的上下文,四类模块就有四种模块上下文,实现为四个不同的结构体,所以ctx是void *
	void                 *ctx; 

	//commands 是模块的指令集,nginx的每个模块都可以实现一些自定义的指令,这些指令
	//写在配置文件的适当配置项中,每一个指令在源码中对应着一个 ngx_command_t结构的
	//变量,nginx会从配置文件中把模块的指令读取出来放到模块的commands指令数组中,
	//这些指令一般是把配置项的 参数值赋给一些程序中的变量或者是在不同的变量之间合并或
	//转换数据(例如include指令),指令可以带参数也可以不带参数,你可以把这些指令想象
	//为 unix的命令行或者是一种模板语言的指令。
	ngx_command_t        *commands;

	//type就是模块的种类,前面已经说过,nginx模块分为core、event、http和mail四类,type用宏定义标识四个分类。 
	ngx_uint_t            type; 

	//init_master、 init_module、init_process、init_thread、exit_thread、
	//exit_process、 exit_master是函数指针,指向模块实现的自定义回调函数,
	//这些回调函数分别在初始化master、初始化模块、初始化工作进程、初始化线程、
	//退出线程、退出工作进程和退出master的时候被调用,如果模块需要在这些时机做处理,
	//就可以实现对应的函数,并把它赋值给对应的函数指针来注册一个回调 函数接口
	ngx_int_t           (*init_master)(ngx_log_t *log);
	ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
	ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
	ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
	void                (*exit_thread)(ngx_cycle_t *cycle);
	void                (*exit_process)(ngx_cycle_t *cycle);
	void                (*exit_master)(ngx_cycle_t *cycle);

	/*...*/

};
 

//epoll处理事件的函数
static ngx_int_t ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{

	//得到发生的事件表event_list
	events = epoll_wait(ep, event_list, (int) nevents, timer);

	if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
		ngx_time_update();
	}

	//上锁ngx_posted_events_mutex
	//ngx_posted_events_mutex只有在NGX_THREAD宏定义有效时才有效
	ngx_mutex_lock(ngx_posted_events_mutex);

	for (i = 0; i < events; i++) {
		//获取事件的connection
		c = event_list[i].data.ptr;
		instance = (uintptr_t) c & 1;
		c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
		rev = c->read;

		revents = event_list[i].events;

		if ((revents & (EPOLLERR|EPOLLHUP)) && 
		   (revents & (EPOLLIN|EPOLLOUT)) == 0) {
		/*
		* if the error events were returned without EPOLLIN or EPOLLOUT,
		* then add these flags to handle the events at least in one active handler
		*/
			revents |= EPOLLIN|EPOLLOUT;
		}

		//default rev->active is 1
		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) {
		//如果是新的连接,accept就会被设为1;accept()之后还没有断开(timeout),accept就是0
		//这个步骤不处理连接,只是把连接放在queue(ngx_posted_accept_events或者ngx_posted_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);
		}
	}
	//解锁
	ngx_mutex_unlock(ngx_posted_events_mutex);
	return NGX_OK;
}
 

//Debug追踪
下面,以单进程和多进程处理一个http请求为例,分析一下事件处理的流程。我用nginx里面已有的ngx_log_debugX()来插入事件处理的主要函数ngx_epoll_process_events()和ngx_event_process_posted()。在编译的时候,需要加上"--with-debug"参数。并指定nginx.conf里面的"error_log   logs/debug.log  debug_core | debug_event | debug_http;"。重新启动nginx。

单进程(work_processes 1):
1. 在初始化即ngx_worker_process_init()中调用两次ngx_epoll_add_event()。第一次是在ngx_event_process_init()里面,即给每个监听的端口(在我的例子里只监听80端口)添加一个NGX_READ_EVENT事件;第二次是ngx_add_channel_event(),即给进程间通行的socketpair添加NGX_READ_EVENT事件。
2. 不断调用ngx_epoll_process_events()函数,探测监听的事件是否发生。如果此时有一个http请求进来,就会触发epoll的事件。由于之前每个监听的端口已经设置handler是ngx_event_accept(),这样,就会在ngx_epoll_process_events()里面调用rev->handler(rev),即调用ngx_event_accept()。在这个函数里,accept()被调用,即接收请求并为其分配一个新的连接,初始化这个新连接,并调用listening socket的handler,即ls->handler(c)。因为ls->handler在http_block()(读取配置之后)里面已经设置了(ls->handler = ngx_http_init_connection;),那么就会调用ngx_http_init_connection()。而在这个函数里,又会添加一个读事件,并设置其处理钩子是ngx_http_init_request()。
3. epoll触发新的事件调用ngx_http_init_request(),并继续http请求处理的每一个环节。(如process request line,process headers,各个phase等)
4. 最后client关闭了连接(我用的是Linux下的curl)。调用了ngx_http_finalize_request() => ngx_http_finalize_connection() => ngx_http_set_keepalive()。ngx_http_set_keepalive()函数设置事件的处理函数是ngx_http_keepalive_handler(),并调用ngx_post_event()把它添加到ngx_posted_events队列里。然后ngx_event_process_posted()函数就会一一处理并删除队列里所有的事件。在ngx_http_keepalive_handler()函数里,调用ngx_http_close_connection() => ngx_close_connection() => ngx_del_conn(c,NGX_CLOSE_EVENT)。ngx_del_conn()即ngx_epoll_del_connection(),即把这个处理请求的connection从epoll监听的事件列表中删除。


多进程(我设置了work_processes 2):和单进程不同,单进程设置epoll timer为-1,即没有事件就一直阻塞在那里,直到监听的端口收到请求。而多进程则不同,每个进程会设置一个epoll_wait()的timeout,去轮番尝试获取在监听端口接受请求的权利,如果没有事件就去处理其它的事件,如果获得了就阻塞(*直到有任意事件发生)
1. 在ngx_event_process_init()里面,只会调用ngx_add_channel_event()给进程间通信的socketpair添加事件,而不给http监听的端口添加事件(为了保证不会有多个工作进程来同时接受请求)。而每个进程被fork()之后,父进程(master process)都会调用ngx_pass_open_channel() => ngx_write_channel()  => sendmsg()来通知所有已经存在的进程(这会触发接收方的事件,调用ngx_channel_handler()函数)
2. 在ngx_process_events_and_timers()里,用一个锁来同步所有的进程ngx_trylock_accept_mutex(),并只有一个进程能够得到ngx_accept_mutex这个锁。得到这个锁的进程会调用ngx_enable_accept_events()添加一个监听端口的事件。
3. 在ngx_epoll_process_events()里,调用了ngx_locked_post_event()添加了一个读事件到accept queue(即ngx_posted_accept_events),然后在ngx_event_process_posted()里面处理,即调用ngx_event_accept(),并添加一个读事件(后面和单进程是一样的)。在处理完ngx_posted_accept_events队列里面的所有accept事件之后,ngx_accept_mutex这个锁也会被释放,即把接受请求的权利让给其它的进程。

*在多进程的模式下,每当有新的子进程启动的时候,父进程(master process)都会向其余所有进程的socketpair channel广播新的子进程的channel。这样,就会导致之前获取监听端口权限(即ngx_accept_mutex)的进程触发epoll事件,从而释放ngx_accept_mutex,虽然这个是发生在初始化阶段(之后子进程间一般不通信),一般不会产生两个或多个进程同时在epoll添加监听端口事件的情况。但是在理论上,这样的设计可能会导致系统的bug(比如有人通过给子进程发送信号的办法来实现一些特殊的功能时,就有可能让其中一个进程放弃ngx_accept_mutex,而另外某一个进程在之后先于它再次获取到ngx_accept_mutex)。

 

0
0
分享到:
评论
1 楼 DILIGENT203 2015-06-23  
最后绿色的一段话没有看明白,还是不明白nginx为什么要广播channel给所有的子进程

相关推荐

    nginx优化详细

    - **重要性**:选择合适的事件处理模型可以极大地提高 Nginx 处理并发连接的能力。 - **配置建议**: - 使用 `epoll` 作为事件处理模型,这是 Linux 下最高效的模型之一。 - 设置较大的 `worker_connections` 数值...

    nginx HTTP处理流程.docx

    5. **事件处理**:Nginx使用异步非阻塞I/O模型,通过事件处理器如epoll来高效地处理多个连接。 ### 总结 Nginx的强大在于其模块化设计和高效的事件处理机制。通过编译时的定制和配置文件的灵活配置,Nginx可以适应...

    Nginx 配置文件 nginx.conf 详解

    事件处理模型是 Nginx 服务器处理客户端请求的重要组件,我们可以通过 `use` 指令来设置事件处理模型,例如 `use epoll;`,这将设置事件处理模型为 epoll。 客户端请求头部缓冲区大小 客户端请求头部缓冲区大小是 ...

    实战Nginx_取代Apache的高性能Web服务器].张宴.扫描版

    Nginx选择了epoll和kqueue作为网络I/O模型,在高连接并发的情况下,内存、 CPU等系统资源消耗非常低,运行稳定。  本书系统地介绍了Nginx与PHP、RUBY、Python结合的使用方法,Nginx作为反向代理与负载均衡的配置与...

    实战Nginx:取代Apache的高性能Web服务器 中文版

    nginx选择了epoll和kqueue作为网络i/o模型,在高连接并发的情况下,内存、cpu等系统资源消耗非常低,运行稳定。  本书系统地介绍了nginx与php、ruby、python结合的使用方法,nginx作为反向代理与负载均衡的配置与...

    nginx源码编译安装.docx

    2. 使用 epoll 事件处理模型:在 `conf/nginx.conf` 文件中添加 `use epoll;` 指令,用于使用 epoll 事件处理模型。 五、其他优化 1. 设置软连接:使用 ln 命令设置软连接,以便简化 Nginx 的启动过程。 2. 查看 ...

    开源电子书:Nginx 开发手册文档.pdf

    8. kqueue、epoll、rt signals、/dev/poll 和 select 支持:Nginx 支持多种事件机制,包括 kqueue、epoll、rt signals、/dev/poll 和 select,可以根据需要选择合适的事件机制。 9. sendfile 支持:Nginx 支持 send...

    nginx源代码

    6. **事件处理函数**:在源码中,可以深入学习Nginx如何处理网络事件,如accept、read、write等,以及如何进行事件调度。 7. **内存池管理**:Nginx使用内存池来高效地分配和回收内存,避免频繁的内存申请和释放...

    Nginx:取代apache的高性能服务器

    Nginx选择了epoll和kqueue作为网络I/O模型,在高连接并发的情况下,Nginx是Apache服务器不错的替代品,它能够支持高达50 000个并发连接数的响应,运行稳定,且内存、CPU等系统资源消耗非常低。, 本书主要分为4个部分...

    nginx原码版本0.9.5

    - **事件处理器**:如Epoll(Linux)、KQueue(FreeBSD)等,用于监听和响应客户端的连接请求。 ### 2. Nginx 配置文件解析 在 Nginx 0.9.5 中,配置文件通常命名为 `nginx.conf`,包含服务器块、location 块、...

    nginx-0.0.1源代码

    - Nginx 的核心是一个事件处理引擎,它通过加载各种模块来扩展功能,如HTTP模块、FTP模块、电子邮件协议模块等。 - 模块间通过API进行通信,这使得代码结构清晰,便于维护和扩展。 3. **配置文件解析** - 在 `...

    nginx代码阅读

    综上所述,Nginx通过精心设计的监听结构、事件处理机制以及灵活的变量管理方案,实现了高性能的网络服务功能。通过对这些关键点的理解,我们不仅能够更好地掌握Nginx的工作原理,还能在其基础上进行更深入的优化和...

    nginx源码学习资料

    Nginx采用事件驱动的异步非阻塞模型,这种模型使得它在处理大量并发连接时表现出色。通过使用epoll(Linux)、kqueue(FreeBSD)等高效I/O复用技术,Nginx可以有效地管理大量并发连接,避免了传统的多线程模型中的上...

    nginx源码 1.18版

    1. **事件模型**:Nginx采用异步非阻塞的事件处理机制,如epoll、kqueue等,确保在高并发环境下高效运行。这使得Nginx能处理大量并发连接,且内存占用低。 2. **网络处理模块**:包括socket通信、连接管理和数据...

    Nginx 1.22.0 Linux 版本,解压安装。

    Nginx 1.22.0 Linux 版本,解压安装。 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理...能够支持高达 50,000 个并发连接数的响应,感谢Nginx为我们选择了 epoll and kqueue作为开发模型。

    nginx-1.18.0.zip

    Nginx是一个高性能的HTTP和反向代理服务器,...在Linux操作系统下,nginx使用epoll事件模型,得益于此,nginx在Linux操作系统下效率相当高。同时Nginx在OpenBSD或FreeBSD操作系统上采用类似于Epoll的高效事件模型kqueue.

    nginx-1.2.4(最新稳定版)

    6. **高性能并发**:Nginx采用epoll事件模型,可以处理成千上万的并发连接,尤其适合高流量的网站。 7. **错误页面定制**:Nginx允许自定义404、500等错误页面,提升用户体验。 在实际部署中,Nginx通常与动态语言...

    Nginx配置文件(nginx.conf)配置详解[定义].pdf

    在这个示例中,Nginx服务器使用epoll事件模型,这是Linux操作系统下的默认事件模型。 连接数量 `worker_connections 204800;`指令指定了每个工作进程的最大连接数量。在这个示例中,每个工作进程的最大连接数量为...

    Nginx高性能Web服务器详解(完整版)pdf下载

    2. **事件驱动模型**:Nginx使用Epoll(Linux)或KQueue(FreeBSD)等高效I/O复用技术,实现非阻塞I/O,使得在处理大量并发连接时性能优秀。 3. **反向代理**:Nginx可以作为反向代理服务器,将客户端请求转发到...

Global site tag (gtag.js) - Google Analytics