设为首页 收藏本站
查看: 554|回复: 0

[经验分享] 从nginx角度看服务器多进程模型设计(一)

[复制链接]

尚未签到

发表于 2016-12-28 10:57:33 | 显示全部楼层 |阅读模式
多进程你可能很熟悉,也许有一套自己的使用习惯和方法。这东西没有什么权威建议,书上只是给出了基本知识点,至于具体怎么去用,因人而异。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.channel[0], FIOASYNC, &on) == -1) {
...
}
if (fcntl(ngx_processes.channel[0], F_SETOWN, ngx_pid) == -1) {
...
}

...

ngx_channel = ngx_processes.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.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[n].pid == -1) {
continue;
}
if (n == ngx_process_slot) {
continue;
}
if (ngx_processes[n].channel[1] == -1) {
continue;
}
if (close(ngx_processes[n].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.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初始化时做的事情,那么在实际运行中,进程间又有哪些交互和通信呢?后面的文章讨论。一篇讨论文章太长的话,大家都蛋疼,你懂得。。。

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-320646-1-1.html 上篇帖子: Nginx的浏览器/服务器双向SSL证书认证配置 下篇帖子: Nginx 0.8.x + PHP 5.2.13(FastCGI)搭建胜过Apache十倍的Web服务器(第6版)(转)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表