从nginx角度看服务器多进程模型设计(一)
2012-06-05 16:25
330 查看
多进程你可能很熟悉,也许有一套自己的使用习惯和方法。这东西没有什么权威建议,书上只是给出了基本知识点,至于具体怎么去用,因人而异。nginx在多进程设计方面有很多值得学习和借鉴的东西,我认为是一套比较好的实现方案。你也许认为这东西很简单,是老生常谈的东西了,但是我这里要提醒你一下,俗话道酒是陈的香,越经典的东西越值得去琢磨,不要对自己太自信。善于思考的家伙总是会在一些老的技术上给你许多新鲜的见解,这种牛人你不会没遇到过吧!扯淡罢了,回到正题。
看函数ngx_spawn_process的骨架:
在worker进程的进程信息数组中,把当前worker进程信息结构(即ngx_process_slot对应的位置)中的channel[0]关掉,
同时会把其他进程的channel[1]关掉,而只把他们的channel[0]留着,如下代码所示。那么这样做的意图是什么?
在ngx_worker_process_init的最后,调用了函数ngx_add_channel_event:
ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler)
主要的目的是向epoll中注册读事件。读数据的fd为变量ngx_channel,处理函数是ngx_channel_handler,那么ngx_channel是什么, 这个处理函数又做了些什么呢?
在ngx_spawn_process函数中,有句:ngx_channel = ngx_processes[s].channel[1],我们知道s是本次用来放置新创建进程信息的(在ngx_processes中)位置,即也是ngx_process_slot变量,因为这两个变量,ngx_channel和ngx_process_slot是在fork之前设置的,那么在随后子进程的init时,就会用到这两个刚刚在父进程中设置的值,ngx_add_channel_event中的ngx_channel就是当前子进程的channle[1],所以这里的意图也就明了了,该worker进程就是通过监听channel[1]的读事件来获取信息。好了,然后我们搜遍所有代码,并没有看到在某个channel上监听写事件的动作,那么我们要问了,子进程有没有需要写一些数据的时候呢?好吧,我们全文搜索"channel[0]",看看会发现什么。我想你肯定发现了ngx_write_channel函数,它的作用就是往channel[0]中去写数据,那么是都是谁在写呢?
我们找到了ngx_pass_open_channel,也就是我上文所说的“广播”。你要知道的一点就是,父进程中通过向channle[0]里写数据,可以在子进程中相应的channel[1]中读取。好了到这里我们前面提到的一些疑问基本上都有了答案,父进程通过向各个channel[0]中“广播”数据,子进程在其自己的channel[1]中读取相应的数据,他们正是通过这种方式来通信的。那么nginx进程间通信的机制仅仅就是这些吗?远不止。。。
我们前面提到父进程写channel[0],子进程读channel[1],那么父进程都传了些啥?我们最容易看到的载体是ngx_channel_t结构,在ngx_pass_open_channel中,参数ch就是这样的一个结构:
对于command,在当前为NGX_CMD_OPEN_CHANNEL,也就是告诉其他的子进程,“某个新的进程刚刚诞生,注意同步相关信息”。那么子进程得到这个信息会怎么做呢?
还记得这个ngx_channel_handler函数吗,在注册读事件时设置的,它里面会调用ngx_read_channel来或者这个channel数据。如果发现是NGX_CMD_OPEN_CHANNEL命令,那么就在ngx_processes的相应位置上更新新诞生进程的信息:
ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
这个NGX_CMD_OPEN_CHANNEL算是最简单的命令了,其他的处理有什么特别的地方?
以上我们讨论的这些算是基础设施了,在这之上,nginx做了很多的好东西。目前看到的只是在nginx初始化时做的事情,那么在实际运行中,进程间又有哪些交互和通信呢?后面的文章讨论。一篇讨论文章太长的话,大家都蛋疼,你懂得。。。
看函数ngx_spawn_process的骨架:
for (s = 0; s < ngx_last_process; s++) { ... } ... /* * 实现异步通知的两个步骤,在nginx事件模型的rtsig机制中会用到 * 参考:http://blog.csdn.net/adc0809608/article/details/7368119 */ if (ioctl(ngx_processes[s].channel[0], FIOASYNC, &on) == -1) { ... } if (fcntl(ngx_processes[s].channel[0], F_SETOWN, ngx_pid) == -1) { ... } ... ngx_channel = ngx_processes[s].channel[1]; ... // 当前产生的子进程在ngx_processes数组中的下标 ngx_process_slot = s; /* * 在fork之后,父进程的ngx_processes数组,“传递”给了子进程,但是这时子进程拿到的数组是截至创建该进程之前其他进程的信息。 * 由于子进程是父进程fork得到的,那么在之后父进程的操作结果在子进程中就不可见了。假设当前诞生的是进程1,用p1表示,当父进程 * 创建p5时,那么p2-p5的进程信息在p1中是缺失的,那么p1需要这些信息吗?如果需要的话,该通过什么手段给它呢? * 见ngx_start_worker_processes相关分析 */ ngx_pid = fork(); switch (pid) { ... case 0: ngx_pid = ngx_getpid(); proc(cycle, data); break; default: break; } ... ngx_processes[s].pid = pid; ... if (s == ngx_last_process) { ngx_last_process++; } /* * 父进程,也就是master进程,依次创建work子进程,为了确保ngx_processes数组在子进程间同步,每次创建完一个子进程, * 就通过ngx_pass_open_channel,做一次广播,告诉先前已经创建的子进程: "新进程诞生,注意更新进程数组相关项" * 那么这些子进程又是如何更新这些信息的呢?答案在ngx_worker_process_init中。 */ ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t n, ngx_int_t type) { ngx_int_t i; ngx_channel_t ch; ch.command = NGX_CMD_OPEN_CHANNEL; for (i = 0; i < n; i++) { ch.pid = ngx_processes[ngx_process_slot].pid; ch.slot = ngx_process_slot; ch.fd = ngx_processes[ngx_process_slot].channel[0]; ngx_pass_open_channel(cycle, &ch); } }
在worker进程的进程信息数组中,把当前worker进程信息结构(即ngx_process_slot对应的位置)中的channel[0]关掉,
同时会把其他进程的channel[1]关掉,而只把他们的channel[0]留着,如下代码所示。那么这样做的意图是什么?
ngx_worker_process_init(ngx_cycle_t *cycle, ngx_uint_t priority) { ... for (n = 0; n < ngx_last_process; n++) { if (ngx_processes .pid == -1) { continue; } if (n == ngx_process_slot) { continue; } if (ngx_processes .channel[1] == -1) { continue; } if (close(ngx_processes .channel[1]) == -1) { ... } } if (close(ngx_processes[ngx_process_slot].channel[0]) == -1) { ... } }
在ngx_worker_process_init的最后,调用了函数ngx_add_channel_event:
ngx_add_channel_event(cycle, ngx_channel, NGX_READ_EVENT, ngx_channel_handler)
主要的目的是向epoll中注册读事件。读数据的fd为变量ngx_channel,处理函数是ngx_channel_handler,那么ngx_channel是什么, 这个处理函数又做了些什么呢?
在ngx_spawn_process函数中,有句:ngx_channel = ngx_processes[s].channel[1],我们知道s是本次用来放置新创建进程信息的(在ngx_processes中)位置,即也是ngx_process_slot变量,因为这两个变量,ngx_channel和ngx_process_slot是在fork之前设置的,那么在随后子进程的init时,就会用到这两个刚刚在父进程中设置的值,ngx_add_channel_event中的ngx_channel就是当前子进程的channle[1],所以这里的意图也就明了了,该worker进程就是通过监听channel[1]的读事件来获取信息。好了,然后我们搜遍所有代码,并没有看到在某个channel上监听写事件的动作,那么我们要问了,子进程有没有需要写一些数据的时候呢?好吧,我们全文搜索"channel[0]",看看会发现什么。我想你肯定发现了ngx_write_channel函数,它的作用就是往channel[0]中去写数据,那么是都是谁在写呢?
我们找到了ngx_pass_open_channel,也就是我上文所说的“广播”。你要知道的一点就是,父进程中通过向channle[0]里写数据,可以在子进程中相应的channel[1]中读取。好了到这里我们前面提到的一些疑问基本上都有了答案,父进程通过向各个channel[0]中“广播”数据,子进程在其自己的channel[1]中读取相应的数据,他们正是通过这种方式来通信的。那么nginx进程间通信的机制仅仅就是这些吗?远不止。。。
我们前面提到父进程写channel[0],子进程读channel[1],那么父进程都传了些啥?我们最容易看到的载体是ngx_channel_t结构,在ngx_pass_open_channel中,参数ch就是这样的一个结构:
/* * command传递的命令 * pid本次产生的新进程pid * slot新进程在ngx_processes数组中的位置 * fd新进程中channel[0] */ typedef struct { ngx_uint_t command; ngx_pid_t pid; ngx_int_t slot; ngx_fd_t fd; } ngx_channel_t;
对于command,在当前为NGX_CMD_OPEN_CHANNEL,也就是告诉其他的子进程,“某个新的进程刚刚诞生,注意同步相关信息”。那么子进程得到这个信息会怎么做呢?
还记得这个ngx_channel_handler函数吗,在注册读事件时设置的,它里面会调用ngx_read_channel来或者这个channel数据。如果发现是NGX_CMD_OPEN_CHANNEL命令,那么就在ngx_processes的相应位置上更新新诞生进程的信息:
ngx_processes[ch.slot].pid = ch.pid;
ngx_processes[ch.slot].channel[0] = ch.fd;
这个NGX_CMD_OPEN_CHANNEL算是最简单的命令了,其他的处理有什么特别的地方?
以上我们讨论的这些算是基础设施了,在这之上,nginx做了很多的好东西。目前看到的只是在nginx初始化时做的事情,那么在实际运行中,进程间又有哪些交互和通信呢?后面的文章讨论。一篇讨论文章太长的话,大家都蛋疼,你懂得。。。
相关文章推荐
- 从nginx角度看服务器多进程模型设计(二)
- Nginx 之三:nginx服务器模块、web请求处理机制及事件驱动模型、进程功能和进程间通信
- nginx源码分析--高性能服务器开发 常见进程模型
- nginx源码分析--高性能服务器开发 常见进程模型
- Nginx 之三:nginx服务器模块、web请求处理机制及事件驱动模型、进程功能和进程间通信
- nginx源码分析--框架设计 & master-worker进程模型
- nginx源码分析(3)——进程模型
- WIN网络编程-EventSelectServer模型的服务器设计
- 5.多进程同步模型的daytime服务器
- nginx进程模型
- 四个 服务器设计模型(42)
- IIS 之 Web 服务器上的 ASP.NET 进程模型设置
- Nginx源码学习(三) nginx进程模型
- nginx进程模型
- Nginx模块开发 —进程模型
- Nginx学习之六-nginx核心进程模型
- 高并发服务器设计之多路复用模型
- Samba 文件服务器用户复杂权限模型设计和实现
- Nginx学习之六-nginx核心进程模型
- Nginx服务器的进程