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

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属性。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值