环境

PHP_VERSION=7.4laravel/framework: ^7.0

动态变量

  • 很多编程语言对于动态变量的解释都是: 与程序有着雷同生命周期的变量, 只初始化一次
  • 不过因为PHP的罕用运行环境是php-fpm模式,每次申请完结过程就会被回收, 动态变量不会常驻内存(只会在此次申请失效)
  • PHP 官网是这么介绍的
变量范畴的另一个重要个性是动态变量(static variable)。动态变量仅在部分函数域中存在,但当程序执行来到此作用域时,其值并不失落。看看上面的例子:https://www.php.net/manual/zh/language.variables.scope.php

前言

  • 我的项目中有以下伪代码逻辑: 因为数据库中的json_data是一个json字符串,所以不用每次获取都解析, 应用static变量修饰符使得下一次拜访不须要再次解析
<?phpnamespace 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.phpdaemon办法
  • 后面三行代码去监听退出信号,而后被动退出过程
  • 下一行的$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