swoft中Crontab定时器的坑

2

这两天老大给了个需求想把商城热点数据同步到redis缓存。我们项目使用的是swoft框架,所以我就想到用框架的Crontab定时器。但是在测试的时候发现把Table的size设置为1024时(实际上设置为任何大小都一样,贴上swoole的解释)发现内存溢出了

clipboard.png

普及一下Table(来自swoole文档):
Table底层是建立在共享内存之上的HashTable数据结构。$size最大行数,决定了HashTable的总行数。由于Table是在共享内存之上,所以无法动态扩容。这个$size必须在创建前设置好。
$size参数指定表格的最大行数,如果$size不是为2的N次方,如1024、8192,65536等,底层会自动调整为接近的一个数字,如果小于1024则默认成1024,即1024是最小值

先把框架任务投递流程走一下:

  1. 首先当框架启动一秒后,启动定时器每秒去更新执行一次Task(任务)。更新任务之前先去队列内存表中清理已完成的队列数据(这点很重要)
  2. 然后获取出所有的任务中的队列(可以理解为获取所有的Task类中的方法),以任务规则,以及taskClass,分钟,时间戳这些数据以md5方式加密得到每个任务队列的key值,保存在runTimeTable 中。(originTable,以及runTimeTable 的结构)

clipboard.png

注:在定时器这块使用到两个Table 一个是originTable用于存储任务的(Task)实例。另一个是runTimeTable 存储任务队列实例,通俗地说就是存需要执行的任务实例

再看看任务执行流程,任务的执行就很简单了

  1. 首先通过getExecTasks这个方法把所有满足条件的队列任务放在一个数组,然后通过遍历数据把runStatus的值改为self::START
  2. 之后执行所有runStatus的值为self::START的队列任务
  3. 把执行后的队列任务的runStatus的值改为self::FINISH
  4. 最后把runStatus的值改为self::FINISH的剔除掉

重新梳理一下我们逻辑
当我们新建执行一个任务的时候,系统每秒钟都回去更新执行一个每个任务中的队列数。
代码如下:
clipboard.png

clipboard.png

通过代码我们能够发现每一分钟他都会往runTimeTable 中添加60个任务队列
但是当我们getExecTasks获取将要执行的任务队里的时候是根据当前的时候是否等于执行时间而标志状态的
那么现在就会出现一个问题。当前时间往任务队里中添加数据的时候 他把前面执行过的任务队列再次添加进runTimeTable 中
举个栗子:
假如我有个异步任务Sync,其中有个每秒执行一次的方法cronTask,
现在时间是2019-03-22 10:01:20 现在往更新runTimeTable 的时候 他会往里面添加60的任务队列key分别会是
MD5(" ".'Sync'.'cronTask'.'01'.'00')
MD5(" ".'Sync'.'cronTask'.'01'.'01')
MD5(" ".'Sync'.'cronTask'.'01'.'02')
MD5(" ".'Sync'.'cronTask'.'01'.'03')
MD5(" ".'Sync'.'cronTask'.'01'.'04')
...
MD5(" ".'Sync'.'cronTask'.'01'.'59')

当时间到下一秒(是2019-03-22 10:01:21)的时候后 依然会往更新runTimeTable数据 key值为
MD5(" ".'Sync'.'cronTask'.'01'.'00')
MD5(" ".'Sync'.'cronTask'.'01'.'01')
MD5(" ".'Sync'.'cronTask'.'01'.'02')
MD5(" ".'Sync'.'cronTask'.'01'.'03')
MD5(" ".'Sync'.'cronTask'.'01'.'04')
...
MD5(" ".'Sync'.'cronTask'.'01'.'59')

那么我们可以很明确地看出来在2019-03-22 10:01:21秒前的数据都是没用的了 。这些数据永远不会被消费,也不会被删除。因此一段时间后会出现内存溢出的情况。
所以解决方法是在清理消费数据的时候把过期数据也同时清理
把cleanRunTimeTable中的

if ($value['runStatus'] === self::FINISH) {

改为

$currentTime = time();
if ($value['runStatus'] === self::FINISH || $value['sec'] < $currentTime) {

clipboard.png

本文为本人学习过程记录。如果有哪些地方描述不当望各位大佬指出。

如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

birdylee · 5月7日

遇到一样的坑。

有另一种方法,就是修改vendor/swoft/task/src/Crontab/Crontab.php: getExecTasks()

if ($value['minute'] == $min) 

改为

if ($value['minute'] >= $min) 

if (time() == $value['sec']

改为

if (time() >= $value['sec']

回复

载入中...