PHP源码分析 - PHP-FPM定时事件

来源:互联网 发布:七层网络和四层模型 编辑:程序博客网 时间:2024/06/07 11:23

PHP-FPM内部采用IO和定时两种事件来保证系统流畅的运转。IO事件负责父子进程的通信、信号收集等工作,定时事件负责系统安全检查方面的工作。PHP-FPM内置fpm_pctl_perform_idle_server_maintenance_heartbeatfpm_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; //事件类型};

其中timeoutfrequency属于定时事件的特有属性。当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属性。

总体来说,定时事件的实现比较简洁,在执行定时事件的时候,进程是一个阻塞的过程。所以当我们再定义一个定时事件时,应该避免冗长而又复杂的逻辑处理,减少运行的事件,降低对其他事件的影响。

0 0
原创粉丝点击