static 靜態變數引起 Laravel 中佇列一個 Bug

seth-shi發表於2021-07-06

環境

PHP_VERSION=7.4
laravel/framework: ^7.0

靜態變數

  • 很多程式語言對於靜態變數的解釋都是: 與程式有著相同生命週期的變數, 只初始化一次
  • 不過由於PHP的常用執行環境是php-fpm模式,每次請求結束程式就會被回收, 靜態變數不會常駐記憶體(只會在此次請求生效)
  • PHP 官網是這麼介紹的

變數範圍的另一個重要特性是靜態變數(static variable)。靜態變數僅在區域性函式域中存在,但當程式執行離開此作用域時,其值並不丟失。看看下面的例子:www.php.net/manual/zh/language.var...

前言

  • 專案中有以下虛擬碼邏輯: 因為資料庫中的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.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

原文連結www.shiguopeng.cn/archives/516

本作品採用《CC 協議》,轉載必須註明作者和本文連結
當神不再是我們的信仰,那麼信仰自己吧,努力讓自己變好,不辜負自己的信仰!

相關文章