PHP源码分析 - PHP-FPM定时事件
来源:互联网 发布:七层网络和四层模型 编辑:程序博客网 时间:2024/06/07 11:23
PHP-FPM内部采用IO和定时两种事件来保证系统流畅的运转。IO事件负责父子进程的通信、信号收集等工作,定时事件负责系统安全检查方面的工作。PHP-FPM内置fpm_pctl_perform_idle_server_maintenance_heartbeat
和 fpm_pctl_heartbeat
两个定时事件,fpm_pctl_perform_idle_server_maintenance_heartbeat
的功能在前篇《PHP-FPM运行模式详解》文章已作介绍,fpm_pctl_heartbeat
主要负责PHP-PFM的慢日志采集及子进程状态检查操作。
下面我们将重点介绍PHP-FPM定时事件。
PHP-FPM定义一个fpm_event_queue_timer
静态全局变量存放定时事件。如下。
static struct fpm_event_queue_s *fpm_event_queue_timer = NULL;
fpm_event_queue_timer
是一个fpm_event_queue_s
类型的数据。
typedef struct fpm_event_queue_s { struct fpm_event_queue_s *prev; struct fpm_event_queue_s *next; struct fpm_event_s *ev;} fpm_event_queue;
fpm_event_queue_s
是一个双向链表结构,prev
指向前一个事件地址,next
指向后一个事件地址,ev
采用fpm_event_s
结构来保存了当前事件信息。fpm_event_s
代码如下:
struct fpm_event_s { int fd; //文件描述符 struct timeval timeout; //定时事件下一次触发的时间点 struct timeval frequency; //定时事件周期 void (*callback)(struct fpm_event_s *, short, void *); //回调方法 void *arg; //回调参数 int flags; int index; short which; //事件类型};
其中timeout
、frequency
属于定时事件的特有属性。当flags
=FPM_EV_READ时,表示I/O读事件;当flags
=FPM_EV_PERSIST时,表示定时事件。
上面是事件数据结构的简单介绍,下面我们将分析定时事件的运行流程。
注册事件
FPM封装了fpm_event_set_timer
函数,用于注册一个定时事件。fpm_event_set_timer
代码如下:
#define fpm_event_set_timer(ev, flags, cb, arg) fpm_event_set((ev), -1, (flags), (cb), (arg));int fpm_event_set(struct fpm_event_s *ev, int fd, int flags, void (*callback)(struct fpm_event_s *, short, void *), void *arg){ if (!ev || !callback || fd < -1) { return -1; } memset(ev, 0, sizeof(struct fpm_event_s)); ev->fd = fd; //设置回调函数 ev->callback = callback; //回调函数参数 ev->arg = arg; ev->flags = flags; return 0;}
fpm_event_set
负责事件的初始化操作。与IO事件不同的是,此时ev->fd
=-1。
添加事件
通过执行fpm_event_add
将定时事件添加到事件列表中。fpm_event_add
代码如下:
int fpm_event_add(struct fpm_event_s *ev, unsigned long int frequency){ struct timeval now; struct timeval tmp; if (!ev) { return -1; } ev->index = -1; if (ev->flags & FPM_EV_READ) { ev->which = FPM_EV_READ; if (fpm_event_queue_add(&fpm_event_queue_fd, ev) != 0) { return -1; } return 0; } //定时事件(器) ev->which = FPM_EV_TIMEOUT; fpm_clock_get(&now); if (frequency >= 1000) { tmp.tv_sec = frequency / 1000; tmp.tv_usec = (frequency % 1000) * 1000; } else { tmp.tv_sec = 0; tmp.tv_usec = frequency * 1000; } ev->frequency = tmp; //设置超时时间点 ev->timeout fpm_event_set_timeout(ev, now); if (fpm_event_queue_add(&fpm_event_queue_timer, ev) != 0) { return -1; } return 0;}
fpm_event_queue_add
函数的功能是将事件添加到事件列表中。如果ev
是IO事件,则添加至&fpm_event_queue_fd
列表。反之,系统会认为这是一个定时事件,则会被添加到&fpm_event_queue_timer
列表。
触发事件
事件触发的功能位于fpm_event_loop
,部分代码如下:
while (1) { struct fpm_event_queue_s *q, *q2; struct timeval ms; struct timeval tmp; struct timeval now; unsigned long int timeout; int ret; fpm_clock_get(&now); timerclear(&ms); //寻找最近一个定时事件触发时间点 q = fpm_event_queue_timer; while (q) { if (!timerisset(&ms)) { ms = q->ev->timeout; } else { if (timercmp(&q->ev->timeout, &ms, <)) { ms = q->ev->timeout; } } q = q->next; } if (!timerisset(&ms) || timercmp(&ms, &now, <) || timercmp(&ms, &now, ==)) { timeout = 1000; } else { timersub(&ms, &now, &tmp); timeout = (tmp.tv_sec * 1000) + (tmp.tv_usec / 1000) + 1; } //timeout 为 定时事件触发事件 ret = module->wait(fpm_event_queue_fd, timeout); //子进程退出循环,主进程监控事件 // PM = PM_STYLE_DYNAMIC 模式,根据请求分配进程数 if (ret == -2) { return; } //IO事件 if (ret > 0) { zlog(ZLOG_DEBUG, "event module triggered %d events", ret); } //定时事件 q = fpm_event_queue_timer; while (q) { fpm_clock_get(&now); if (q->ev) { if (timercmp(&now, &q->ev->timeout, >) || timercmp(&now, &q->ev->timeout, ==)) { //执行事件 fpm_event_fire(q->ev); if (fpm_globals.parent_pid != getpid()) { return; } if (q->ev->flags & FPM_EV_PERSIST) { //重置下一次执行时间 fpm_event_set_timeout(q->ev, now); } else { //只执行单次,则从事件列表中移除该事件对象 q2 = q; if (q->prev) { q->prev->next = q->next; } if (q->next) { q->next->prev = q->prev; } if (q == fpm_event_queue_timer) { fpm_event_queue_timer = q->next; if (fpm_event_queue_timer) { fpm_event_queue_timer->prev = NULL; } } q = q->next; free(q2); continue; } } } q = q->next; }}
fpm_event_loop
内部是一个死循环,其运行流程如下:
1. 遍历fpm_event_queue_timer
事件列表,计算最近一个定时事件触发的等待时间,保存在timeout
变量中。
2. 执行module->wait(fpm_event_queue_fd, timeout)
获取IO事件,注意,这里需要传入等待的时间timeout
秒。 module
是事件驱动模块,wait
对应的是事件模块方法。以”epoll”为例,调用的是fpm_event_epoll_wait
方法。
static int fpm_event_epoll_wait(struct fpm_event_queue_s *queue, unsigned long int timeout) { int ret, i; memset(epollfds, 0, sizeof(struct epoll_event) * nepollfds); //timeout = 0 表示立刻返回 //timeout = -1 标识一直等待 //timeout>0 超时时间 ret = epoll_wait(epollfd, epollfds, nepollfds, timeout); if (ret == -1) { //...省略部分代码... } for (i = 0; i < ret; i++) { //...省略部分代码... //回调事件 fpm_event_fire((struct fpm_event_s *)epollfds[i].data.ptr); //...省略部分代码... } return ret;}
epoll_wait
: 返回需处理的事件数目,如果等待超时(在timeout时间内,未有IO事件触发),则返回0。
3. 遍历fpm_event_queue_timer
定时事件列表,判断事件是否可以执行。倘若可以,则调用fpm_event_fire
执行,根据事件类型选择调用fpm_event_set_timeout
重置下一次执行时间或者free()
来释放对象。
void fpm_event_fire(struct fpm_event_s *ev){ if (!ev || !ev->callback) { return; } (*ev->callback)( (struct fpm_event_s *) ev, ev->which, ev->arg); }
fpm_event_fire
:回调事件函数。对于定时事件,ev->which
= FPM_EV_TIMEOUT。
#ifndef timeradd# define timeradd(a, b, result) \ do { \ (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \ (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \ if ((result)->tv_usec >= 1000000) \ { \ ++(result)->tv_sec; \ (result)->tv_usec -= 1000000; \ } \ } while (0)#endif#define fpm_event_set_timeout(ev, now) timeradd(&(now), &(ev)->frequency, &(ev)->timeout);
fpm_event_set_timeout
计算事件的下一次执行时间点,并保存在ev
对象的timeout
属性。
总体来说,定时事件的实现比较简洁,在执行定时事件的时候,进程是一个阻塞的过程。所以当我们再定义一个定时事件时,应该避免冗长而又复杂的逻辑处理,减少运行的事件,降低对其他事件的影响。
- PHP源码分析 - PHP-FPM定时事件
- php-fpm源码分析
- PHP源码分析 - PHP-FPM运行原理
- PHP源码分析 - PHP-FPM运行模式详解
- PHP源码分析 - PHP-FPM scoreboard模块介绍
- php+php-fpm+nginx 源码安装
- nginx+php-fpm日志分析
- php-fpm占用系统资源分析
- php-fpm
- php-fpm
- PHP-FPM
- php-fpm
- php-fpm
- Php-fpm
- php-fpm
- PHP-FPM
- php-fpm
- php-fpm
- 多人开发版本控制时unity3d中NGUI图集混乱的解决办法
- n个学生站成一排 网易面试题之每个学生有一个能力值 牛牛想从n个学生中选出k名学生 要求相邻学生编号不超过d使得这k个学生乘积最大
- Codeforces Round #383 (Div. 1) C. Arpa’s overnight party and Mehrdad’s silent entering(贪心,二分图)
- Spring下mybatis多数据源配置
- 群聊协议发送的真正的(Message、IQ、presence)等 xmpp(二)
- PHP源码分析 - PHP-FPM定时事件
- linux命令大全——网络通信(二)
- memcached 运行状态参数及存储原理详解
- json-lib之jsonConfig详细使用
- mysql-ubuntu14.04彻底卸载mysql
- Android UI学习和布局优化
- 网易面试题之给定一个 n 行 m 列的地牢,其中 '.' 表示可以通行的位置,'X' 表示不可通行的障碍,牛牛从 (x0 , y0 ) 位置出发,遍历这个地牢, * 和一般的游戏所不同的是,他每一步
- Linux下使用UDP做心跳检测(断线检测)
- PHP中PSR-[0-4]规范