技术经验分享:fastcgimain

简介: 技术经验分享:fastcgimain

  main函数里


当kill -TERM pid 时, 这篇文章 说是


1)master主进程接收到sigterm信号,并执行回调函数sig_handler,往sp【1】写字符T, 事先sp【0】已经写到epoll_ctl里,当执行epoll_wait时,从sp【0】读出T


2)执行相应的回调用函数fpm_got_signal,遍历进程池中每个进程,执行函数kill(子进程号, sigterm),并设置定时器,设置超时时间(也是写到epoll_ctl里), 就是初始化一个结构体, 设置当前时间加1s(默认),即 当前时间之后的1s就过期 放到定时器队列里(链表),


3)假设在规定的超时时间内,子进程结束了工作,并返回了sigchild信号,master主进程使用waitpid捕获了该信号,根据相应信息,判断子进程是意外退出,还是正常退出


 如果在规定的超时时间内,子进程没有返回sigchild信号, 就触发了定时器,master主进程就向各个子进程执行 kill(子进程号, sigkill),sigkill是无条件执行,子进程必须退出,同时向master主进程返回sigchild信号,同时master主进程也要用waitpid函数判断相应信息


4)子进程向master主进程发送sigchild信号,主进程收到后,调用sig_handler函数,向sp【1】中写入C, 在epoll_wait中的sp【0】中读取C, 通过waitpid判断子进程退出状态, 当处理完最后一个子进程后,删除pid文件,主进程也退出了


这里有个疑问,sp【0】和sp【1】只是master主进程在使用,worker没有使用,感觉多此一兴趣


因为当master主进程接收到sigterm信号后,执行其回调函数sig_handler,然后马上遍历各个子进程,执行kill(子进程号, sigterm) 也就是执行第2,3步


后来看了这篇文章, 大意是说避免同时执行信号的回调函数 和IO事件的业务逻辑,我猜测了下,如果按照上面的方法,当接收到sigterm信号时, 除了执行相应的回调函数外,此时还要处理epoll_wait中的io事件,如果有的话,但还是不太明白?


php关于进程池的文章,参考这里


php脚本执行时间超时request_terminame_timeout 参考这里


nginx 502 bad gateway 要么是上面设置参数为0,或太大,要么是php-fpm的listen中的backlog设置太小,以至于nginx的请求无法进入php-fpm的全连接队列


//代码效果参考:http://hnjlyzjd.com/xl/wz_24517.html

nginx 504 timeout listen中的backlog设置太大了,等php-fpm处理完,向nginx的进程写数据时,nginx的超时时间到了,就断开了连接

php-fpm 中 listen 中的 backlog 详见这里


php-fpm 中关于信号的处理,详见这里,讲的很好


信号事件的处理


1)信号是指用户输入的sigquit,sigterm,sigint,以及子进程结束后向父进程返回的sigchild信号


  a)利用socketpair创建全双工管道sp【2】,这个管道通常来说是父子进程使用,但在这里,是父进程自己使用


  b)利用sigaction设置信号以及相应的回调函数, 回调函数就是将各个信号的首个字母写到sp【1】里


c)利用pipe创建两个管道fd_stdout【2】和fd_stderr【2】,用于子进程向父进程传递log数据


d)建socket套接字sfd,bind(),listen()


e)efd=epoll_create(sfd);epoll_ctl(efd,EPOLL_ADD,sfd,events);


f)epoll_ctl(efd,EPOLL_ADD,sp【0】);


2) io操作,参考这里


  在主进程内完成


    a)pipe(fd_stdout);pipe(fd_stderr);


    b)epoll_ctl(efd,EPOLL_ADD,fd_stdout【0】); epoll_ctl(efd,EPOLL_ADD,fd_stderr【0】);  并注册回调函数 fpm_stdio_child_said ( 因为使用了 epoll 中的 边缘触发方式 ,和水平触发相比 ,需要我们不停的读、写,只有当状态从unread到read或unwrite到write的时候, 内核才会通知我们,而水平触发方式,只要缓冲区中有数据,内核会一直通知我们,但这样浪费 详见这里 这里)


    见这个图


关于php-fpm的关闭有两个方法 参考这里


kill -TERM pid 和


kill -QUIT pid


当使用第一种方法时:


主进程向sp【1】里写入了字符 T,通过 epoll_wait知道sp【0】里有数据可读,调用相应的回调函数,发现是T,向各个子进程发送kill(子进程pid,SIGTERM),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器


  1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp【0】里写入字符C,通过epoll_wait得知sp【1】可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,当为0时,就将主进程的pid文件删除掉


  2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGKILL);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作


当使用第二种方法时


主进程向sp【1】里写入了字符 Q,通过 epoll_wait知道sp【0】里有数据可读,调用相应的回调函数,发现是Q,向各个子进程发送kill(子进程pid,SIGQUIT),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器


  1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp【0】里写入字符C,通过epoll_wait得知sp【1】可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,


    然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出 ,但这个fpm_state为FPM_PCTL_STATE_FINISHING(也就是quit), 所以会判断 如果记录当前子进程个数那个全局变量为0时 ,且当前statie为terminate或finishing时    就将主进程的pid文件删除掉


  2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGTERM);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作


第二种方法是优雅的退出,第一种在发送SIGKILL时,可能子进程还没有完成工作


php-fpm某个子进程工作超时重启


php-fpm中当某个子进程执行时间大于request_terminate_timout时(终止处理),父进程发信号SIGTERM给子进程,然后再fork一个新的子进程


主进程向sp【1】里写入了字符 T,通过 epoll_wait知道sp【0】里有数据可读,调用相应的回调函数,发现是Q,向各个子进程发送kill(子进程pid,SIG_x005f_x0008_TERM),不再设置定时器


  1)子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp【0】里写入字符C,通过epoll_wait得知sp【1】可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1


    然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出, 这里是退出的,然后重新fork一个子进程 当为0时,就将主进程的pid文件删除掉,假设子进程个数为3,其中一个服务的子进程超时时,先关闭,所以这里的全局变量为2,还走不到这里,走到了fpm_children_make这里,执行fpm_pctl_can_spawn_children,判断return fpm_state 是否等于 FPM_PCTL_STATE_NORMAL,因为这里的state就是NORMAL,所以顺利的fork;如果是主进程收到SIGQUIT的时候,其实也会走到fpm_pctl_can_spawn_children,在判断state的时候,由于state的状态为finishing,所以不再fork


php-fpm某个子进程slow_log


  设置最小的超时时间 在request_terminate_timeout和request_slowlog_timeout之间


  加入定时器链表,假设超时时间为2S,当前时间为2017-02-13 10:00:00 那么超时的时间为2017-02-13 10:02:00


每个子进程工作时会记录当前的时间,在fpm_event_loop函数里,遍历这个定时器链表,如果当前时间 大于或等于2017-02-13 10:02:00的时候,会触发相应的回调函数,当前时间-每个子进程当时时间 >= request_slowlog_timeout时,父进程向子进程发送sigstop暂停信号,并向该子进程设置一个回调函数,子进程结束工作后,返回sigchild信号,父进程接收后,利用tracer收集子进程的信息,写入日志,再向子进程发送sigcont信号,子进程继续执行


php-fpm重启sigus2


每个子进程在工作的时候会先记录下当前的间,


主进程向sp【1】里写入了字符 2,通过 epoll_wait知道sp【0】里有数据可读,调用相应的回调函数,发现是2,向各个子进程发送kill(子进程pid,SIGQUIT),并且设定定时器(假设当前时间为2017-02-12 11:14:00,那么超时时间为2017-02-12 11:15:00),并放到定时器队列里,因为有许多的定时器


  1)在这一分钟内,如果子进程结束自己的工作,向父进程发送了sigchild信号,父进程接收此信号后,向 sp【0】里写入字符C,通过epoll_wait得知sp【1】可读,并调用相应回调函数,通过C,进入函数fpm_children_bury,这里主进程通过waitpid得到子进程退出的原因,这里是通过信息导致的退出,这时有一个表示子进程个数的全局变量,这时要减1,就是说每waitpid等到一个子进程,这个全局变量就减1,


    然后执行fpm_pctl_child_exited ,当fpm_state等于FPM_PCTL_STATE_NORMAL时,这个函数就退出 ,但这个fpm_state为FPM_PCTL_STATE_FINISHING(也就是quit), 所以会判断 如果记录当前子进程个数那个全局变量为0时 ,因为当前state为reloading,所以执行execvp(execvp就是用一个新的进程把自己替换掉,一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。(不过exec类函数中有的还允许继承环境变量之类的信息。1>exec()函数调用并没有生成新进程,一个进程一旦调用exec函数,它本省就“死亡了”--就好比被鬼上身一样,身体还是你的,但灵魂和思想已经被替换了 --系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一保留的就是进程ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。execvp执行方式是代码空间、数据空间、堆栈的替换);)exit(FPM_EXIT_SOFTWARE);) 参考这里 这里,然后被替换的老进程退出 ,fpm本身是守护进程 参考这里


  2)当当前时间大于或等于2017-02-12 11:15:00时候,向各个子进程,执行kill(子进程pid,SIGTERM);这个时候子进程会放下手头工作,无条件的结束,接着执行1的操作


slowlog 配置详见这里 自己的分析见这里


在一个非阻塞的socket上调用read/write函数, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)


从字面上看, 意思是:


EAGAIN: 再试一次


EWOULDBLOCK: 如果这是一个阻塞socket, 操作将被block


perror输出: Resource temporarily unavailable


总结:


这个错误表示资源暂时不够, 可能read时, 读缓冲区没有数据, 或者, write时,


写缓冲区满了.


遇到这种情况, 如果是阻塞socket, read/write就要阻塞掉.


而如果是非阻塞socket, read/write立即返回-1, 同 时errno设置为EAGAIN.


所以, 对于阻塞socket, read/write返回-1代表网络出错了.


但对于非阻塞socket, read/write返回-1不一定网络真的出错了.


可能是Resource temporarily unavailable. 这时你应该再试, 直到Resource available.


  在子进程完成的工作


    a)dup2(fd_stdout【1】,STDOUT_FILENO); dup2(fd_stderr【1】, STDERR_FILENO);


epoll 参考这里


php-fpm nginx通信 这里


php-fpm的主函数


int main(int argc, char argv【】)


{


int exit_status = FPM_EXIT_OK;


int cgi = 0, c, use_extended_info = 0;


zend_file_handle file_handle;


/ temporary locals /


int orig_optind = php_optind;


char orig_optarg = php_optarg;


int ini_entries_len = 0;


/ end of temporary locals /


int max_requests = 500;


int requests = 0;


int fcgi_fd = 0;


fcgi_request request;


char fpm_config = NULL;


char fpm_prefix = NULL;


char fpm_pid = NULL;


int test_conf = 0;


int force_daemon = -1;


int php_information = 0;


int php_allow_to_run_as_root = 0;


sapi_startup(&cgi_sapi_module);


cgi_sapi_module.php_ini_path_override = NULL;


cgi_sapi_module.php_ini_ignore_cwd = 1;


fcgi_init();


while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2)) != -1) {


switch (c) {


case 'c':


if (cgi_sapi_module.php_ini_path_override) {


free(cgi_sapi_module.php_ini_path_override);


}


cgi_sapi_module.php_ini_path_override = strdup(php_optarg);


break;


case 'n':


cgi_sapi_module.php_ini_ignore = 1;


break;


case 'd': {


/ define ini entries on command line /


int len = strlen(php_optarg);


char val;


if ((val = strchr(php_optarg, '='))) {


val++;


if (!isalnum(val) && val != '"' && val != '\'' && val != '\0') {


cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\"\"\n\0"));


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, (val - php_optarg));


ini_entries_len += (val - php_optarg);


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"", 1);


ini_entries_len++;


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, val, len - (val - php_optarg));


ini_entries_len += len - (val - php_optarg);


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, "\"\n\0", sizeof("\"\n\0"));


ini_entries_len += sizeof("\n\0\"") - 2;


} else {


cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("\n\0"));


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len);


memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "\n\0", sizeof("\n\0"));


ini_entries_len += len + sizeof("\n\0") - 2;


}


} else {


cgi_sapi_module.ini_entries = realloc(cgi_sapi_module.ini_entries, ini_entries_len + len + sizeof("=1\n\0"));


memcpy(cgi_sapi_module.ini_entries + ini_entries_len, php_optarg, len);


memcpy(cgi_sapi_module.ini_entries + ini_entries_len + len, "=1\n\0", sizeof("=1\n\0"));


ini_entries_len += len + sizeof("=1\n\0") - 2;


}


break;


}


case 'y':


fpm_config = php_optarg;


break;


case 'p':


fpm_prefix = php_optarg;


break;


case 'g':


fpm_pid = php_optarg;


break;


case 'e': / enable extended info output /


use_extended_info = 1;


break;


case 't':


test_conf++;


break;


case 'm': / list compiled in modules /


cgi_sapi_module.startup(&cgi_sapi_module);


php_output_activate(TSRMLS_C);


SG(headers_sent) = 1;


php_printf("【PHP Modules】\n");


print_modules(TSRMLS_C);


php_printf("\n【Zend Modules】\n");


print_extensions(TSRMLS_C);


php_printf("\n");


php_output_end_all(TSRMLS_C);


php_output_deactivate(TSRMLS_C);


fcgi_shutdown();


exit_status = FPM_EXIT_OK;


goto out;


case 'i': / php info & quit /


php_information = 1;


break;


case 'R': / allow to run as root /


php_allow_to_run_as_root = 1;


break;


case 'D': / daemonize /


force_daemon = 1;


break;


case 'F': / nodaemonize /


force_daemon = 0;


break;


default:


case 'h':


case '?':


cgi_sapi_module.startup(&cgi_sapi_module);


php_output_activate(TSRMLS_C);


SG(headers_sent) = 1;


php_cgi_usage(argv【0】);


php_output_end_all(TSRMLS_C);


php_output_deactivate(TSRMLS_C);


fcgi_shutdown();


exit_status = (c == 'h') ? FPM_EXIT_OK : FPM_EXIT_USAGE;


goto out;


case 'v': / show php version & quit */


cgi_sapi_module.startup(&cgi_sapi_module);


if (php_request_startup(TSRMLS_C) == FAILURE) {


SG(server_context) = NULL;


php_module_shutdown(TSRMLS_C);


return FPM_EXIT_SOFTWARE;


}


SG(headers_sent) = 1;


SG(request_info).no_headers = 1;


#if ZEND_DEBUG


php_printf("PHP %s (%s) (built: %s %s) (DEBUG)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, DATE, TIME, get_zend_version());


#else


php_printf("PHP %s (%s) (built: %s %s)\nCopyright (c) 1997-2015 The PHP Group\n%s", PHP_VERSION, sapi_module.name, DATE, TIME, get_zend_v

相关文章
|
5月前
|
Web App开发 人工智能 Java
技术经验分享:affineCipherandafineHacker
技术经验分享:affineCipherandafineHacker
36 2
|
22天前
|
运维 Kubernetes 网络协议
运维之道:从新手到专家的成长之路
【10月更文挑战第21天】 本文旨在探讨运维领域的成长路径,通过分享个人经历和行业见解,为读者提供一条从入门到精通的清晰路线图。我们将从基础技能的学习开始,逐步深入到高级技巧的应用,最终达到专业水平的提升。文章强调了持续学习和实践的重要性,并鼓励读者在面对挑战时保持积极态度,不断探索未知领域。
22 6
|
19天前
|
运维 Prometheus 监控
运维之道:从新手到专家的旅程
【10月更文挑战第24天】 在数字化时代,运维工作如同一座桥梁,连接着技术与业务,确保系统的稳定运行。本文将带你踏上一段从运维新手成长为专家的旅程,探索运维的核心价值、技能提升路径以及面对挑战时的应对策略。通过深入浅出的语言和生动的案例,让你领略运维世界的奥秘与魅力。
11 0
|
2月前
|
Java 关系型数据库 API
后端开发之道:从新手到专家的蜕变之旅
在数字化时代的浪潮中,后端开发如同一座桥梁,连接着数据世界的每一个角落。本文将带领读者踏上一段从零基础到精通后端开发的旅程,探索编程语言的选择、框架的应用、数据库的设计以及API的开发等关键知识点。我们将以通俗易懂的语言,结合条理清晰的结构,逐步揭开后端开发的神秘面纱,让每一位读者都能在这段旅途中找到属于自己的位置和方向。
|
5月前
技术经验分享:comparisonmethodviolates必现
技术经验分享:comparisonmethodviolates必现
25 0
技术经验分享:comparisonmethodviolates必现
|
5月前
|
存储 缓存 NoSQL
技术经验分享:braum的使用
技术经验分享:braum的使用
26 0
|
5月前
技术经验分享:Jacobi
程序技术好文:爬取梨视频网站详细过程
26 0
|
5月前
|
JavaScript
技术经验分享:javascriptvar的理解
技术经验分享:javascriptvar的理解
25 0
|
5月前
|
前端开发 JavaScript 容器
技术经验分享:CSSFIXEDpornjavhd
技术经验分享:CSSFIXEDpornjavhd
78 0
|
5月前
|
移动开发 HTML5
技术经验分享:CQUOJ9766ChillyWilly
技术经验分享:CQUOJ9766ChillyWilly
23 0