swoft中Crontab定时器的坑

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

clipboard.png

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

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

  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')swoole

当时间到下一秒(是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

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

点我阅读原文学习

相关文章
相关标签/搜索