static 静态变量引起 Laravel 中队列一个 Bug

黑洞算法
• 阅读 2057

环境

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方法

static 静态变量引起 Laravel 中队列一个 Bug
static 静态变量引起 Laravel 中队列一个 Bug

  • 实际上是拿到队列的驱动,然后转到worker去运行任务, 传递了一个参数once是否只运行一个任务,这里我们直接查看daemon方法
  • 转到src\Illuminate\Queue\Worker.phpdaemon方法
    static 静态变量引起 Laravel 中队列一个 Bug
  • 前面三行代码去监听退出信号,然后主动退出进程
  • 下一行的$lastRestart是缓存中获取一个时间戳,用于之后的主动退出进程,这个时间戳只会被php artisan queue:restart重置
  • 所以可以用queue:restart这条命令去停止队列进程(并不会自动启动队列进程,可以配合Supervisor来自动重启)
  • 接下来是一个死循环,来达到进程不被杀死
  • 第一个逻辑判断死看程序是否已经启动的维护模式,强制运行等等,就是队列任务是否能继续处理的前置判断
  • 所以我们想临时暂停队列进程,可以向进程发送一个SIGUSR2信号,这时候队列进程处理完当前任务下一次就会停止,当想继续处理的时候,再发送一个SIGCONT信号
  • 然后到getNextJob这个方法去配置的队列驱动(redis, database 等等)里获取下一个待处理的任务
  • 如果支持异步扩展,registerTimeoutHandler对任务的超时做了一些处理, 如果任务超时了, 那么就结束任务
  • 下一步如果取出来的没任务, 那么就程序休眠, 否则就运行任务, 这里可以去看一下任务的实际运行代码
    static 静态变量引起 Laravel 中队列一个 Bug
    static 静态变量引起 Laravel 中队列一个 Bug
  • 这里我们直接看fire方法即可, 然后找到对应的队列驱动类,继承了父级的fire方法
    static 静态变量引起 Laravel 中队列一个 Bug
  • 实际上是反射了这个job类然后调用它对应的方法
  • 循环前的最后一个代码块就是stopIfNecessary, 看进程是否需要终止, 前面说的queue:restart也是在这里处理


  • 所以当我们使用静态变量的时候,虽然每次反射实例化了一个新的job,但实际上job去拿模型的属性的时候,static变量是一直没有发生变化的,这就导致了前面说的Bug

原文链接https://www.shiguopeng.cn/archives/516

点赞
收藏
评论区
推荐文章
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
3年前
java中一些常考知识
一、static的作用 static是修饰符,用于修饰成员变量(静态变量/类变量)。static修饰的成员被所有对象共享。static优先于对象存在。static修饰的成员可以用类名.静态成员来访问。注:1.静态方法只能访问静态成员,非静态方法既能访问静态成员又可以访问非静态成员。2.静态方法中不可
CuterCorley CuterCorley
4年前
C语言基础习题50例(九)41-45
习题41学习static定义静态变量的用法。实现思路:用static声明和未用static声明变量进行对比,即可得出static的作用。代码如下:cinclude<stdio.hintmain(){voidfunc();inti;for(i0;i<10;i){func();}return
xiguaapp xiguaapp
4年前
jvm
类的加载连接与初始化加载:查找并加载类的二进制数据连接验证:确保被加载的类的正确性准备:为类的静态变量分配内存,并将其初始化为默认值解析:把类中的符号引用转换成为直接引用初始化:为类的静态变量赋予正确的初始值主动使用创建类的实例访问某个类或接口的静态变量,或者对该静态变量赋值调用类的
Wesley13 Wesley13
3年前
Java 初始化执行顺序以及成员变量初始化顺序
一、静态变量初始化顺序大家先看两个例子:(1)!(https://oscimg.oschina.net/oscnet/66be9168f7cdf36484b71f1d67069f12492.jpg)!(https://oscimg.oschina.net/oscnet/bf5d0b172f00f9aa237b3aee5b58cad5d0
Stella981 Stella981
3年前
Django之常用配置
<h1在其它文件导入及变量命名注意事项</h1变量命名:必须都大写<preclass'brush:python'fromdjango.confimportsettings</pre<h1静态文件夹配置</h1比如需要引入jquery、bootstrap等文件,需要配置静态文件,步骤如下:步骤一、在<项目名称目录下新建
Stella981 Stella981
3年前
SpringBoot使用@Value给静态变量注入值,不能正确读取相应的值
今天在调试代码的时候发现SpringBoot中使用@Value()给变量赋值,给普通变量赋值是可以的,但是给静态变量即static变量赋值的时候,读取不到相应的值,如果是字符串会读取为null,数字值会读取为0.网上查了一下才发现不能直接给static变量赋值。1、SpringBoot中使用@Value()给普通变量注入值:在applica
Wesley13 Wesley13
3年前
Java中类的加载顺序剖析(常用于面试题)
如果类A和类B中有静态变量,静态语句块,非静态变量,非静态语句块,构造函数,静态方法,非静态方法,同时类A继承类B,请问当实例化A时,类内部的加载顺序是什么?Demo:ClassB:publicclassB{//静态变量staticinti1;//静态语句块static{
Wesley13 Wesley13
3年前
C++中静态变量、常量、静态整型常量、静态非整型常量、引用变量的初始化方法
C中静态变量、常量、静态整型常量、静态非整型常量、引用变量的初始化方法先看一段代码classCTypeInit{public:CTypeInit(intc):m_c(c),m_ra(c){}private:intm_a;//通过初始化列表初始化,
Wesley13 Wesley13
3年前
Java类的初始化顺序 (静态变量、静态初始化块、变量、初始...
大家在去参加面试的时候,经常会遇到这样的考题:给你两个类的代码,它们之间是继承的关系,每个类里只有构造器方法和一些变量,构造器里可能还有一段代码对变量值进行了某种运算,另外还有一些将变量值输出到控制台的代码,然后让我们判断输出的结果。这实际上是在考查我们对于继承情况下类的初始化顺序的了解。我们大家都知道,对于静态变量、静态初始化块、变量、初始化块