当前位置: 首页 > 后端技术 > PHP

关于PHP定时器的一些事情

时间:2023-03-29 15:09:12 PHP

转载请注明文章出处:https://tlanyan.me/timer-in-php/常见的定时器有两种:一种是周期性执行的,比如每天凌晨三点点击那个报告;另一种是在指定时间(一次)后执行,例如每日登录奖励在会员登录系统后五分钟发放。两种情况对应shell中的cron和at命令,类似于JavaScript中的setInterval和setTimeout函数(严格来说setInterval是周期性执行的,指定时间点的执行需要自己处理)。做web开发的PHP程序员应该对JavaScript中的两个定时器函数不陌生,但是再回到PHP层面却有些傻眼:PHP中有sleep,却没有(内置的)定时器函数可用。sleep函数勉强可以做到,但会导致进程阻塞,期间其他事情不能做(或无响应)。为什么PHP没有提供定时器(Timer)的功能呢?之所以我个人认为PHP在web开发中不能使用定时器的本质原因是缺乏一个可控的常驻内存运行环境。两个关键点:第一是永久记忆,第二是可控。CGI方式下,进程执行脚本后直接退出,不能指望在指定时间运行任务;在PHP-FPM模式下,进程(大部分)驻留在内存中,但它是不可控的。不可控是指执行PHP的进程不受PHP代码的影响,进程的进入点和退出时机由附加程序控制。比如在FPM模式下,PHP脚本中的exit和die函数只是中断脚本的执行,不会对脚本的执行过程产生特殊影响(内存泄漏除外)。PHP开发者编写的脚本是流程的执行体。执行后,它会从进程的执行上下文中卸载。在这种情况下,执行PHP脚本的时机仍然是外部驱动的,PHP代码会在没有外部请求的情况下安安静静地躺在硬盘上,什么都不做,成为定时任务。由于PHP主要用于web开发,PHP的执行方式稳定可靠,开发效率快。比如省去资源释放这一步,避免了很多开发上的工作量和坑。想想在一些第三方库代码中更改时区、字符编码等而不进行恢复,这几乎肯定会导致常驻内存运行环境下后续请求出现问题。但是在FPM模式下,这种坑直接被无心挖平,节省了大量的调试时间,对程序员保持发际线有很大的贡献。问题已经明白了,那么在PHP中如何使用定时器来执行定时任务呢?危险的做法在web环境中,PHP脚本默认是有超时的。去掉超时设置,可以保持程序在后台运行(如果进程不退出)。例如下面的代码在响应请求后继续在后台运行,每五秒将时间输出到文件中:#test.phpset_time_limit(0);#取消超时设置,让脚本一直运行echo'Thisisabackgroundrunforeverscript.现在你可以别管我了。';fastcgi_finish_request();#结束当前请求睡眠(5);}而(真);请求http://localhost:8080/test.php文件后,监控/tmp/out.dat文件,会发现有连续不断的内容输出,无论客户端断开、关闭浏览器、重启计算机(服务器无法重新启动)。这说明程序一直在执行,也实现了我们想要的定时器功能。如果我们把sleep改成usleep和time_nanosleep,那岂不是很好,我们也可以实现微秒和纳秒定时器?在实践中,应该尽可能避免以这种方式实现定时器,不仅因为它效率低下,而且还存在轻微的危险。其中一个原因是每个请求都会占用一个进程,10万个请求需要10万个进程,这基本上会导致系统崩溃或者后续请求无响应;另外,如果session打开了,但是忘记调用session_write_close,会导致同一个用户后续的请求挂掉(session在active的时候是锁住的,如果session没有关闭,后面的流程将无法进行打开会话)。Web开发应该尽快响应用户请求。在web开发中强行以这种方式实现定时器,会使整个web应用处于不稳定、不可靠或不可预测的状态。孟子曰:知而慎之,君子不立于危墙之下。不靠谱的做法要尽量避免,顺便避免背锅甩锅。接下来,我们就来看看在PHP中使用定时器的正确姿势。正确姿势PHP实现定时器的方式可以简单概括为:使用cron、Jenkins等调度工具做周期性的定时任务(要么执行脚本,要么请求某个URL);一次性执行任务,通过消息队列、数据库等交付给第三方程序执行;像WordPress一样模拟定时任务,但是记住这种方式依赖于客户端请求,需要自己处理进程并发问题;使用常驻内存运行PHP程序,即CLI方式。除第三种方法外,推荐其他方法,具体解决方案需结合实际需要。作为PHP程序员,首选当然是使用PHP,也就是CLI方式。CLI模式感觉良心,CLI模式给PHP的发挥空间拓展了很多。CLI模式下,程序的入口是脚本,代码可以常驻内存,进程完全由PHP代码控制。在这种形式下,定时器的实现方式有很多种。本文列举了几种吸引jade的方法:使用swoole、workerman等框架,内置(高精度)定时器;使用多进程(pool)/多线程(pool)技术(pcntl、pthreads扩展只在CLI模式下可用);处理信号,例如滴答声或警报;使用事件驱动库,例如libevent和libev;加入sleep循环或者自己实现事件循环。想折腾就自己用2-5的方案。如果不想折腾,swoole、workerman等框架是首选,稳定可靠。总结一下HTTP请求和任务的关系,很容易实现定时任务。至于用不用PHP,那是另外一回事。当然,作为web开发的首选语言,PHP实现定时任务也轻而易举。本文感谢“广州伟通”的赞助。参考http://php.net/manual/en/func...