zoukankan      html  css  js  c++  java
  • 纯PHP实现定时器任务(Timer)

    基础知识

      此程序在Linux下开发,以cli模式运行,一下是基本知识的简要介绍。

    • CLI:PHP的命令行模式,常见的WEB应用使用的是fpm;
    • 进程:进程是程序运行的基本单元,进程之间是独立运行且互不干扰的,有独立的运行空间,每个进程都有一个进程控制块;
    • 进程间通信:既然进程是独立运行,我们需要一种机制保证不同进程信息的交换,进程间通信主要包括:管道,IPC(共享内存,信号,消息队列),套接字;
    • PCNTL扩展:PHP的一个进程扩展,主要用到pcntl_alarm()函数,详细介绍请查阅官网.
    • 实现原理    

        用一个三维数组保存所有需要执行的任务,一级索引为时间戳,值为执行任务的方法、回调参数等,具体数组形式如下:

    • array(
              '1438156396' => array(
                      array(1,array('Class','Func'), array(), true),  
              )
      )
      说明:
      时间戳
      array(1,array('Class','Func'), array(), true) 
      参数依次表示: 执行时间间隔,回调函数,传递给回调函数的参数,是否持久化(ture则一直保存在数据中,否则执行一次后删除)

    这些任务可以是任意类的方法。既然是定时任务,我们需要一个类似计时的东东,此方案采用信号量去做,每一秒向当前进程发送SIGALRM信号,并捕获该信号,触发信号处理函数,循环遍历数据,判断是否有当前时间需要执行的任务。如果有则采用回调方式触发,并把参数传递给该方法。

    <?php
    /**
    *定时器
    */
    class Timer
    {
        //保存所有定时任务
        public static $task = array();
    
            //定时间隔
            public static $time = 1;
    
            /**
        *开启服务
            *@param $time int
           */
        public static function run($time = null)
            {
            if($time)
                {
                        self::$time = $time;
                }
                self::installHandler();
                pcntl_alarm(1);
             }
            /**
            *注册信号处理函数
            */
            public static function installHandler()
            {
                pcntl_signal(SIGALRM, array('Timer','signalHandler'));
            }
    
            /**
            *信号处理函数
            */
            public static function signalHandler()
            {
                self::task();
            //一次信号事件执行完成后,再触发下一次
            pcntl_alarm(self::$time);
            }
    
            /**
            *执行回调
            */
            public static function task()
            {
                if(empty(self::$task))
                {//没有任务,返回
                        return ;
                }
                foreach(self::$task as $time => $arr)
            {
                        $current = time();
            
                    foreach($arr as $k => $job)
                {//遍历每一个任务
                            $func = $job['func'];    /*回调函数*/
                            $argv = $job['argv'];    /*回调函数参数*/
                    $interval = $job['interval'];    /*时间间隔*/
                            $persist = $job['persist'];    /*持久化*/
    
                            if($current == $time)
                            {//当前时间有执行任务
    
                        //调用回调函数,并传递参数
                                   call_user_func_array($func, $argv);
                        
                        //删除该任务
                                unset(self::$task[$time][$k]);
                            }
                            if($persist)
                            {//如果做持久化,则写入数组,等待下次唤醒
                                   self::$task[$current+$interval][] = $job;
                            }
                }
                if(empty(self::$task[$time]))
                {
                    unset(self::$task[$time]);
                }
                }
            }
    
            /**
            *添加任务
            */
            public static function add($interval, $func, $argv = array(), $persist = false)
            {
                if(is_null($interval))
                {
                    return;
                }
                $time = time()+$interval;
            //写入定时任务
            self::$task[$time][] = array('func'=>$func, 'argv'=>$argv, 'interval'=>$interval, 'persist'=>$persist);
            }
    
            /**
            *删除所有定时器任务
            */
            public function dellAll()
            {
                self::$task = array();
            }
    }

    这是定时器类核心部分,有一个静态变量保存有所有需要执行的任务,这里为什么是静态的呢?大家自行思考.当进程接受到 SIGALRM 信号后,触发 signalHandler 函数,随后循序遍历数组查看是否有当前时间需要执行的任务,有则回调,并传递参数,删除当前job,随后检查是否要做持久化任务,是则继续将当前job写入事件数组等待下次触发,最后再为当前进程设置一个闹钟信号.可以看出这个定时器,只要触发一次就会从内部再次触发,得到自循环目的.

    <?php
    
    class DoJob
    {
        public function job( $param = array() )
        {
            $time = time();
            echo "Time: {$time}, Func: ".get_class()."::".__FUNCTION__."(".json_encode($param).")
    ";
        }
    }

    这是回调类及函数,为方便说明,加入不少调试信息.Timer类及回调都有了,我们看看使用场景是怎么样的.

    <?php
    
    require_once(__DIR__."/Timer.php");
    require_once(__DIR__."/DoJob.php");
    
    
    Timer::dellAll();
    
    Timer::add( 1, array('DoJob','job'), array(),true);
    
    Timer::add( 3, array('DoJob','job'),array('a'=>1), false);
    
    echo "Time start: ".time()."
    ";
    Timer::run();
    
    while(1)
    {
        sleep(1);
        pcntl_signal_dispatch();
    }

    代码非常短,这里注册了两个job,随后运行定时器,在一个无限循环里捕捉信号触发动作,如果不捕获将无法触发事先注册的处理函数.这样一个自循环的定时器开发完成.运行结果如下:

     如我们场景类添加的任务一样,在90的时候执行了两个任务,一个为持久化的不带参数的job,一个为非持久化带参数的job,随后非持久化job不再执行.

    总结

    • 在收到信号前,当前进程不能退出.这里我使用了条件永远为真的循环.在我们实际生产环境中,需要创造这么一个先决条件,比如说,我们有一组服务,这些服务都是一直运行的,不管是IO访问,等待socket链接等等,当前服务都不会终止,即使进程阻塞也不会有问题,这种场景,也就是有一个一直运行的服务中使用.
    • 目前PHP只支持以秒为单位的触发,不支持更小时间单位,对位定时任务而言基本足够

     服务器定时任务

    Unix平台

    如果您使用 Unix 系统,您需要在您的 PHP 脚本的最前面加上一行特殊的代码,使得它能够被执行,这样系统就能知道用什么样的程序要运行该脚本。为 Unix 系统增加的第一行代码不会影响该脚本在 Windows 下的运行,因此您也可以用该方法编写跨平台的脚本程序。

    1、在Crontab中使用PHP执行脚本

    就像在Crontab中调用普通的shell脚本一样(具体Crontab用法),使用PHP程序来调用PHP脚本,每一小时执行 myscript.php 如下:

    # crontab -e
    00 * * * * /usr/local/bin/php /home/john/myscript.php

    /usr/local/bin/php为PHP程序的路径。

    2、在Crontab中使用URL执行脚本

    如果你的PHP脚本可以通过URL触发,你可以使用 lynx 或 curl 或 wget 来配置你的Crontab。

    下面的例子是使用Lynx文本浏览器访问URL来每小时执行PHP脚本。Lynx文本浏览器默认使用对话方式打开URL。但是,像下面的,我们在lynx命令行中使用-dump选项来把URL的输出转换来标准输出。

    00 * * * * lynx -dump http://www.sf.net/myscript.php

    下面的例子是使用 CURL 访问URL来每5分执行PHP脚本。Curl默认在标准输出显示输出。使用 "curl -o" 选项,你也可以把脚本的输出转储到临时文件temp.txt。

    */5 * * * * /usr/bin/curl -o temp.txt http://www.sf.net/myscript.php

    下面的例子是使用WGET访问URL来每10分执行PHP脚本。-q 选项表示安静模式。"-O temp.txt" 表示输出会发送到临时文件。

    */10 * * * * /usr/bin/wget -q -O temp.txt http://www.sf.net/myscript.php
  • 相关阅读:
    学术诚信与职业道德
    第8,9,10章读后感
    Scrum项目7.0
    燃尽图
    Scrum 项目4.0
    Sprint计划
    复利计算再升级——连接数据库
    软件工程---做汉堡,结对2.0
    软件工程---复利计算-结对
    学习进度条博客
  • 原文地址:https://www.cnblogs.com/beyang/p/9661172.html
Copyright © 2011-2022 走看看