php使用Swoole实现毫秒级定时任务的方法


Posted in PHP onSeptember 04, 2020

项目开发中,如果有定时任务的业务要求,我们会使用linux的crontab来解决,但是它的最小粒度是分钟级别,如果要求粒度是秒级别的,甚至毫秒级别的,crontab就无法满足,值得庆幸的是swoole提供的强大的毫秒定时器。

应用场景举例

我们可能会遇到这样的场景:

场景一:每隔30秒获取一次本机内存使用率

场景二:2分钟后执行报表发送任务

场景三:每天凌晨2点钟定时请求第三方接口,如果接口有数据返回则停止任务,如果接口由于某种原因没有响应或者没有数据返回则5分钟后继续尝试请求该接口,尝试5次后仍然失败则停止该任务

以上的三个场景我们都可以归纳为定时任务的范畴。

Swoole毫秒定时器

Swoole提供了异步毫秒定时器函数:

swoole_timer_tick(int $msec, callable $callback):设置一个间隔时钟定时器,每隔$msec毫秒执行一次$callback,类似于javascript中的setInterval()

swoole_timer_after(int $after_time_ms, mixed $callback_function):在指定的时间$after_time_ms后执行$callback_function,类似于javascript的setTimeout()

swoole_timer_clear(int $timer_id):删除指定id的定时器,类似于javascript的clearInterval()

解决方案

对于场景一,经常用在系统检测统计方面,实时性要求比较高,但又能控制好频率,多用于后台服务器性能监控,可以生成可视化图表。可以是30秒获取一次内存使用率,也可以是10秒,而crontab最小粒度只能设置为1分钟。

swoole_timer_tick(30000, function($timer) use ($task_id) { // 启用定时器,每30秒执行一次 
  $memPercent = $this->getMemoryUsage(); //计算内存使用率 
  echo date('Y-m-d H:i:s') . '当前内存使用率:'.$memPercent."\n"; 
});

对于场景二,直接定义xx时间后执行某项任务的话,貌似crontab比较困难,而使用swoole的swoole_timer_after可以实现:

swoole_timer_after(120000, function() use ($str) { //2分钟后执行 
  $this->sendReport(); //发送报表 
  echo "send report, $str\n"; 
});

对于场景三,用来作尝试请求,请求失败后继续,如果成功则停止请求。用crontab也能解决,但是比较傻,比如设置每隔5分钟请求一次,不管成功会失败都会去执行一次。而用swoole定时器则智能多了。

swoole_timer_tick(5*60*1000, function($timer) use ($url) { // 启用定时器,每5分钟执行一次 
   $rs = $this->postUrl($url); 

   if ($rs) { 
     //业务代码... 
     swoole_timer_clear($timer); // 停止定时器 
     echo date('Y-m-d H:i:s'). "请求接口任务执行成功\n"; 
   } else { 
     echo date('Y-m-d H:i:s'). "请求接口失败,5分钟后再次尝试\n"; 
   } 
 });

示例代码

新建文件\src\App\Task.php:

namespace Helloweba\Swoole; 

use swoole_server; 

/** 
* 任务调度 
*/ 
class Task 
{ 
  protected $serv; 
  protected $host = '127.0.0.1'; 
  protected $port = 9506; 
  // 进程名称 
  protected $taskName = 'swooleTask'; 
  // PID路径 
  protected $pidPath = '/run/swooletask.pid'; 
  // 设置运行时参数 
  protected $options = [ 
    'worker_num' => 4, //worker进程数,一般设置为CPU数的1-4倍  
    'daemonize' => true, //启用守护进程 
    'log_file' => '/data/log/swoole-task.log', //指定swoole错误日志文件 
    'log_level' => 0, //日志级别 范围是0-5,0-DEBUG,1-TRACE,2-INFO,3-NOTICE,4-WARNING,5-ERROR 
    'dispatch_mode' => 1, //数据包分发策略,1-轮询模式 
    'task_worker_num' => 4, //task进程的数量 
    'task_ipc_mode' => 3, //使用消息队列通信,并设置为争抢模式 
  ]; 

  public function __construct($options = []) 
  { 
    date_default_timezone_set('PRC'); 
    // 构建Server对象,监听127.0.0.1:9506端口 
    $this->serv = new swoole_server($this->host, $this->port); 

    if (!empty($options)) { 
      $this->options = array_merge($this->options, $options); 
    } 
    $this->serv->set($this->options); 

    // 注册事件 
    $this->serv->on('Start', [$this, 'onStart']); 
    $this->serv->on('Connect', [$this, 'onConnect']); 
    $this->serv->on('Receive', [$this, 'onReceive']); 
    $this->serv->on('Task', [$this, 'onTask']);  
    $this->serv->on('Finish', [$this, 'onFinish']); 
    $this->serv->on('Close', [$this, 'onClose']); 
  } 

  public function start() 
  { 
    // Run worker 
    $this->serv->start(); 
  } 

  public function onStart($serv) 

  { 
    // 设置进程名 
    cli_set_process_title($this->taskName); 
    //记录进程id,脚本实现自动重启 
    $pid = "{$serv->master_pid}\\n{$serv->manager_pid}"; 
    file_put_contents($this->pidPath, $pid); 
  } 

  //监听连接进入事件 
  public function onConnect($serv, $fd, $from_id) 
  { 
    $serv->send( $fd, "Hello {$fd}!" ); 
  } 

  // 监听数据接收事件 
  public function onReceive(swoole_server $serv, $fd, $from_id, $data) 
  { 
    echo "Get Message From Client {$fd}:{$data}\n"; 
    //$this->writeLog('接收客户端参数:'.$fd .'-'.$data); 
    $res['result'] = 'success'; 
    $serv->send($fd, json_encode($res)); // 同步返回消息给客户端 
    $serv->task($data); // 执行异步任务 
  } 

  /** 
 
  * @param $serv swoole_server swoole_server对象 
  * @param $task_id int 任务id 
  * @param $from\id int 投递任务的worker_id 
  * @param $data string 投递的数据 
  */ 
  public function onTask(swoole_server $serv, $task_id, $from_id, $data) 
  { 
    swoole_timer_tick(30000, function($timer) use ($task_id) { // 启用定时器,每30秒执行一次 
      $memPercent = $this->getMemoryUsage(); 
      echo date('Y-m-d H:i:s') . '当前内存使用率:'.$memPercent."\n"; 
    }); 
  } 


  /** 
  * @param $serv swoole_server swoole_server对象 
  * @param $task_id int 任务id 
  * @param $data string 任务返回的数据 
  */ 
  public function onFinish(swoole_server $serv, $task_id, $data) 
  { 
    // 
  } 

 
  // 监听连接关闭事件 
  public function onClose($serv, $fd, $from_id) { 
    echo "Client {$fd} close connection\n"; 
  } 

  public function stop() 
  { 
    $this->serv->stop(); 
  } 

  private function getMemoryUsage() 
  { 
    // MEMORY 
    if (false === ($str = @file("/proc/meminfo"))) return false; 
    $str = implode("", $str); 
    preg_match_all("/MemTotal\s{0,}\:+\s{0,}([\d\.]+).+?MemFree\s{0,}\:+\s{0,}([\d\.]+).+?Cached\s{0,}\:+\s{0,}([\d\.]+).+?SwapTotal\s{0,}\:+\s{0,}([\d\.]+).+?SwapFree\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buf); 
    //preg_match_all("/Buffers\s{0,}\:+\s{0,}([\d\.]+)/s", $str, $buffers); 

    $memTotal = round($buf[1][0]/1024, 2); 
    $memFree = round($buf[2][0]/1024, 2); 
    $memUsed = $memTotal - $memFree; 
    $memPercent = (floatval($memTotal)!=0) ? round($memUsed/$memTotal*100,2):0; 

    return $memPercent; 
  } 
}

我们以场景一为例,在onTask启用定时任务,每隔30秒计算一次内存使用率。实际应用中可以把计算好的内存按时间写入数据库等存储中,然后可以根据前端需求用来渲染成统计图表,如:

php使用Swoole实现毫秒级定时任务的方法

接着服务端代码 public\taskServer.php :

<?php
require dirname(__DIR__) . '/vendor/autoload.php'; 
use Helloweba\Swoole\Task; 
$opt = [ 
  'daemonize' => false 
]; 
$ser = new Task($opt); 
$ser->start();

客户端代码 public\taskClient.php :

<?php
class Client 
{ 
  private $client; 
  public function __construct() { 
    $this->client = new swoole_client(SWOOLE_SOCK_TCP); 
  } 
  public function connect() { 
    if( !$this->client->connect("127.0.0.1", 9506 , 1) ) { 
      echo "Error: {$this->client->errMsg}[{$this->client->errCode}]\n"; 
     } 
    fwrite(STDOUT, "请输入消息 Please input msg:"); 
    $msg = trim(fgets(STDIN)); 
    $this->client->send( $msg ); 
    $message = $this->client->recv(); 
    echo "Get Message From Server:{$message}\n"; 
  } 
} 
$client = new Client(); 
$client->connect();

验证效果

1.启动服务端:

php taskServer.php

2.客户端输入:

另开命令行窗口,执行

[root@localhost public]# php taskClient.php

请输入消息 Please input msg:hello

Get Message From Server:{"result":"success"} 
[root@localhost public]#

3.服务端返回:

php使用Swoole实现毫秒级定时任务的方法

如果返回上图中的结果,则定时任务正常运行,我们会发现每隔30秒会输出一条信息。

总结

到此这篇关于php使用Swoole实现毫秒级定时任务的方法的文章就介绍到这了,更多相关php Swoole实现毫秒级定时任务内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
PHP在Web开发领域的优势
Oct 09 PHP
也谈php网站在线人数统计
Apr 09 PHP
php检查字符串中是否包含7位GSM字符的方法
Mar 17 PHP
PHP微信红包API接口
Dec 05 PHP
程序员的表白神器“520”大声喊出来
May 20 PHP
php封装的数据库函数与用法示例【参考thinkPHP】
Nov 08 PHP
PHP+AJAX 投票器功能
Nov 11 PHP
php curl批处理实现可控并发异步操作示例
May 09 PHP
PHP PDOStatement::setFetchMode讲解
Feb 03 PHP
Laravel5.7框架安装与使用学习笔记图文详解
Apr 02 PHP
浅谈laravel aliases别名的原理
Oct 24 PHP
PHP http请求超时问题解决方案
Nov 13 PHP
Laravel Reponse响应客户端示例详解
Sep 03 #PHP
PHP 实现base64编码文件上传出现问题详解
Sep 01 #PHP
PHP copy函数使用案例代码解析
Sep 01 #PHP
PHP超全局变量实现原理及代码解析
Sep 01 #PHP
PHP终止脚本运行三种实现方法详解
Sep 01 #PHP
PHP如何使用array_unshift()在数组开头插入元素
Sep 01 #PHP
PHP数组Key强制类型转换实现原理解析
Sep 01 #PHP
You might like
使用JavaScript创建新样式表和新样式规则
2016/06/14 PHP
php实现背景图上添加圆形logo图标的方法
2016/11/17 PHP
PHP对称加密算法(DES/AES)类的实现代码
2017/11/14 PHP
Laravel 框架路由原理与路由访问实例分析
2020/04/14 PHP
IE中jquery.form中ajax提交没反应解决方法分享
2012/09/11 Javascript
form.submit()不能提交表单的原因分析
2014/10/23 Javascript
jQuery后代选择器用法实例
2014/12/23 Javascript
简单讲解AngularJS的Routing路由的定义与使用
2016/03/05 Javascript
jQuery 弹出层插件(推荐)
2016/05/24 Javascript
老生常谈js动态添加事件--- 事件委托
2016/07/19 Javascript
Bootstrap提示框效果的实例代码
2017/07/12 Javascript
JS动态修改网页body的背景色实例代码
2017/10/07 Javascript
NodeJs实现定时任务的示例代码
2017/12/05 NodeJs
利用vue + element实现表格分页和前端搜索的方法
2017/12/25 Javascript
JS判断字符串是否为整数的方法--简单的正则判断
2018/07/23 Javascript
[jQuery] 事件和动画详解
2019/03/05 jQuery
jQuery实现点击滚动到指定元素上的方法分析
2020/03/19 jQuery
angular共享依赖的解决方案分享
2020/10/15 Javascript
寻找网站后台地址的python脚本
2014/09/01 Python
Python 抓取动态网页内容方案详解
2014/12/25 Python
python使用tcp实现局域网内文件传输
2020/03/20 Python
python使用threading.Condition交替打印两个字符
2019/05/07 Python
详解pandas如何去掉、过滤数据集中的某些值或者某些行?
2019/05/15 Python
python元组和字典的内建函数实例详解
2019/10/22 Python
django 模版关闭转义方式
2020/05/14 Python
New Balance天猫官方旗舰店:始于1906年,百年慢跑品牌
2017/11/15 全球购物
马来西亚银饰品牌:JEOEL
2017/12/15 全球购物
现代绅士日常奢侈品:Todd Snyder
2019/12/13 全球购物
高一新生军训方案
2014/05/12 职场文书
七一党日活动总结
2014/07/08 职场文书
机关驾驶员违规检讨书
2014/09/13 职场文书
2014法院干警廉洁警示教育思想汇报
2014/09/13 职场文书
朋友离别感言
2015/08/04 职场文书
Python中tkinter的用户登录管理的实现
2021/04/22 Python
Python 如何将integer转化为罗马数(3999以内)
2021/06/05 Python
什么是SOLID
2022/03/24 Javascript