環境
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.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
原文連結www.shiguopeng.cn/archives/516
本作品採用《CC 協議》,轉載必須註明作者和本文連結