环境
PHP_VERSION=7.4
laravel/framework: ^7.0
动态变量
- 很多编程语言对于动态变量的解释都是: 与程序有着雷同生命周期的变量, 只初始化一次
- 不过因为
PHP
的罕用运行环境是php-fpm
模式, 每次申请完结过程就会被回收, 动态变量不会常驻内存(只会在此次申请失效) - PHP 官网是这么介绍的
变量范畴的另一个重要个性是动态变量(static variable)。动态变量仅在部分函数域中存在,但当程序执行来到此作用域时,其值并不失落。看看上面的例子:https://www.php.net/manual/zh/language.variables.scope.php
前言
- 我的项目中有以下伪代码逻辑: 因为数据库中的
json_data
是一个json
字符串, 所以不用每次获取都解析, 应用static
变量修饰符使得下一次拜访不须要再次解析
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class AttributeRequestLog extends Model
{public function getJsonData($key)
{
static $jsonData;
if (is_null($jsonData)) {$jsonData = json_decode($this->attributes['json_data'], true);
}
return $jsonData[$key] ?? null;
}
}
因为之前没上队列解决异步工作, 程序始终没问题. 直到某一天上了队列之后, 有共事反馈, 有异样数据上报. 连忙排查了一下日志, 发现队列中的日志打点数据有问题, 随后减少更多打点, 最初定位到了这个中央.
- 因为
Laravel
的队列采纳CLI
运行模式, 这时候解决的工作都是后盾运行 - 队列启动时载入代码, 直到队列过程被杀死, 否则代码也不会更新,
剖析源码
- 队列的启动命令:
php artisan queue:work
- 找到启动文件
src\Illuminate\Queue\Console\WorkCommand.php
是一个继承于Illuminate\Console\Command
的类, 运行artisan
的时候, 会运行其的handle
办法
- 实际上是拿到队列的驱动, 而后转到
worker
去运行工作, 传递了一个参数once
是否只运行一个工作, 这里咱们间接查看daemon
办法 - 转到
src\Illuminate\Queue\Worker.php
的daemon
办法 - 后面三行代码去监听退出信号, 而后被动退出过程
- 下一行的
$lastRestart
是缓存中获取一个工夫戳, 用于之后的被动退出过程, 这个工夫戳只会被php artisan queue:restart
重置 - 所以能够用
queue:restart
这条命令去进行队列过程 (并不会主动启动队列过程, 能够配合Supervisor
来主动重启) - 接下来是一个死循环, 来达到过程不被杀死
- 第一个逻辑判断死看程序是否曾经启动的保护模式, 强制运行等等, 就是队列工作是否能持续解决的前置判断
- 所以咱们想长期暂停队列过程, 能够向过程发送一个
SIGUSR2
信号, 这时候队列过程解决完当前任务下一次就会进行, 当想持续解决的时候, 再发送一个SIGCONT
信号 - 而后到
getNextJob
这个办法去配置的队列驱动 (redis, database 等等) 里获取下一个待处理的工作 - 如果反对异步扩大,
registerTimeoutHandler
对工作的超时做了一些解决, 如果工作超时了, 那么就结束任务 - 下一步如果取出来的没工作, 那么就程序休眠, 否则就运行工作, 这里能够去看一下工作的理论运行代码
- 这里咱们间接看
fire
办法即可, 而后找到对应的队列驱动类, 继承了父级的fire
办法 - 实际上是反射了这个
job
类而后调用它对应的办法 -
循环前的最初一个代码块就是
stopIfNecessary
, 看过程是否须要终止, 后面说的queue:restart
也是在这里解决
- 所以当咱们应用动态变量的时候, 尽管每次反射实例化了一个新的
job
, 但实际上job
去拿模型的属性的时候,static
变量是始终没有发生变化的, 这就导致了后面说的Bug
原文链接 https://www.shiguopeng.cn/archives/516