那一天,人类终于回想起了曾一度被粗心大意所支配的恐怖和忘了写某几文章的那份屈辱。.中二病这辈子可能都不会好了吧...
  (在我的笔记里是:'上一篇通过'...)之前通过EasySwoole + Crontab定时去执行的文件.但是Crontab最小级别是每分钟.我们其他的业务可能会用到秒.毫秒的方式去解决,所以在这些场景下Crontab对我们就不是很友好.我们可以通过EasySwoole中自带的毫秒定时器去解决一些相应的问题.这里就整合到静态API中去一起学习.

源码阅读

  通过阅读官方文档3.x可以看到定时器类的命名空间为EasySwoole\Component\Timer在目录的vendor\easyswoole\component\scr\Timer.php

/**
* 循环调用
* @param int      $microSeconds 循环执行的间隔毫秒数 传入整数型
* @param \Closure $func 定时器需要执行的操作 传入一个闭包
* @param string    $name 定时器名称,用于取消该定时器
* @return int 返回整数型的定时器编号 可以用该编号停止定时器
*/
use Singleton;

function loop(int $ms, callable $callback, $name = null): int
{
    $id = swoole_timer_tick($ms, $callback);
    $this->timerList[$id] = $id;
    if ($name !== null) {
        $this->timerMap[md5($name)] = $id;
    }
    return $id;
}

  注释是从文档里抄的.源码中连个注释都没有.

测试demo

  我们可以简单来做一个小测试.可以看到应用了单例模式,在App\HttpCroller\Test.php下创建个测试方法

use EasySwoole\Component\Timer;
public function timer()
{
    // 500毫秒说句话.
    $name = 'QvBiLam';
    Timer::getInstance()->loop(500, function () use ($name) {
        echo 'hello : ' . $name . PHP_EOL;
    });
}

  请求测试curl 127.0.0.1:9511/test/timer.可以看到在`EasySwoole终端中有打印.以500毫秒的速度像我打招呼

2019-06-19T19:24:29.png
  这样请求相当于通过接口才触发.我们需要一个当服务一启动就去执行.而不是我们自己去触发启动.但是将这段代码放到EasySwoole的启动服务中就不会去执行.下面是我的启动服务代码

/*主服务创建事件*/
public static function mainServerCreate(EventRegister $register)
{
    // TODO: Implement mainServerCreate() method.
    /*Di注入数据库设置*/
    self::setDb();
    /*执行异步任务*/
    self::Consumer();
    /*通过crontab执行task任务*/
     self::CrontabTask();
    $name = 'QvBiLam';
    Timer::getInstance()->loop(500, function () use ($name) {
        echo 'hello : ' . $name . PHP_EOL;
    });
}

  通过查看文档.发现犯了个好2的错误.注意:

  • 定时器不能在服务启动之前使用。在服务启动以后,添加的定时器仅仅在当前进程中有效。在workerStart事件中添加定时器时,请注意判断需要添加定时器的workerId,否在该定时器在每个进程中均会被执行

自动启动定时器

  官方文档写法

public static function mainServerCreate(EventRegister $register)
{
    $register->add(EventRegister::onWorkerStart, function (\swoole_server $server, $workerId) {
        //如何避免定时器因为进程重启而丢失
        //例如在第一个进程 添加一个10秒的定时器
        if ($workerId == 0) {
            \EasySwoole\Component\Timer::getInstance()->loop(10 * 1000, function () {
                // 从数据库,或者是redis中,去获取下个就近10秒内需要执行的任务
                // 例如:2秒后一个任务,3秒后一个任务 代码如下
                \EasySwoole\Component\Timer::getInstance()->loop(2 * 1000, function () {
                    //为了防止因为任务阻塞,引起定时器不准确,把任务给异步进程处理
                    Logger::getInstance()->console("time 2", false);
                });
                \EasySwoole\Component\Timer::getInstance()->after(3 * 1000, function () {
                    //为了防止因为任务阻塞,引起定时器不准确,把任务给异步进程处理
                    Logger::getInstance()->console("time 3", false);
                });
            });
        }
    });
}

  我们按照官方文档给的实例进行修改看看workerId

/*主服务创建事件*/
public static function mainServerCreate(EventRegister $register)
{
    ...
    $register->add(EventRegister::onWorkerStart, function (\swoole_server $server, $workerId) {
        Timer::getInstance()->loop(500, function () use ($workerId) {
            echo 'workerId : ' . $workerId . PHP_EOL;
        });
    });
}

2019-06-19T19:42:26.png

  卧槽真的凶残啊.出的还挺快...无伤大雅,这就是EasySwoole巧妙的使用定时器的方式.不看文档今天可能有纳死个b闷了.
  结合我们之前写的定时生成数据文件的方法做测试.之前用Crontab的时候把数据整合都写在了run方法下.不太合适呀.我们在App下创建Cache目录进入并创建VideoIndex.php.我们可以看到进击的巨人第二季和第三季关于猿巨人投掷的动作一摸一样.除了背景不一样,既然动漫都可以复制粘贴模型动作.那么我们为什么不能复制粘贴代码呢?

  算了还是剪切吧...在App\Crontab\IndexVideo下的run方法更改为

use App\Cache\VideoIndex;
static function run(\swoole_server $server, int $taskId, int $fromWorkerId, $flags = null)
{
    // TODO: Implement run() method.
    // 定时任务处理逻辑
    VideoIndex::setVideoIndexData();
}

  App\Cache\VideoIndex.php代码如下

namespace App\Cache;
use App\Model\Video as VideoModel;

class VideoIndex
{
    static public function setVideoIndexData()
    {
        // 定时任务处理逻辑
        // 获取配置文件中的栏目Id.因为不是从0开始的.在开始追加个0
        $catIds = array_keys(\Yaconf::get('navigation.cat'));
        array_unshift($catIds, 0);
        // 使用Model里的对每个不同栏目Id默认生成默认1000条的方法.
        $videoModel = new VideoModel();
        foreach ($catIds as $catId) {
            $condition = [];
            if (!empty($catId)) {
                $condition['cat_id'] = $catId;
            }
            try {
                $data = $videoModel->setVideoData($condition);
            } catch (\Exception $e) {
                // 日志 短信 邮件报警
                $data = [];
            }
            // 如果某个栏目id查询的结果为空.跳出本次循环
            if (empty($data)) {
                continue;
            }
            foreach ($data as &$val) {
                $val['create_time'] = date('Y-m-d H:i:s');
                $val['update_time'] = date('Y-m-d H:i:s');
                $val['video_duration'] = gmstrftime("%H:%I:%S", $val['video_duration']);
            }
            $dir = EASYSWOOLE_ROOT . '/website/video/json';
            if (!is_dir($dir)) {
                mkdir($dir, 0777);
            }
            $flag = file_put_contents($dir . '/video_' . $catId . '.json', json_encode($data));
            if (empty($flag)) {
                // 日志 短信 邮件报警
                echo 'cat_id' . $catId . 'put data error' . PHP_EOL;
            } else {
                // echo 'cat_id' . $catId . 'put data success' . PHP_EOL;
            }
        }

    }
}

  删掉之前在website\video\json目录下生成的所有文件.重启测试没有问题.可以把Crontab中的Task任务注释掉.步骤同上删除文件测试毫秒定时器.修改\EasySwooleEvent.php

use App\Cache\VideoIndex;
/*主服务创建事件*/
public static function mainServerCreate(EventRegister $register)
{
    ...
    $obj = new VideoIndex();
    $register->add(EventRegister::onWorkerStart, function (\swoole_server $server, $workerId) use ($obj) {
        Timer::getInstance()->loop(500, function () use ($workerId,$obj) {
            if ($workerId == 0) {
                $obj->setVideoIndexData();
            }
        });
    });
}

  重启测试成功!时间就改长一点吧.比如一分钟10000 * 60的.随您的意~

109.jpg

Last modification:February 18th, 2020 at 10:24 pm