一、 核心架構原理:GatewayWorker 模式
單純的 Workerman 腳本(將 Socket 監聽與業務邏輯寫在同一個檔案)無法做到完全不中斷連線重載,因為主進程或 Worker 進程重啟時,其持有的 TCP 套接字 (Socket) 勢必會斷開。
要實現真正的平滑熱更新,必須採用 Workerman 官方專為即時通訊設計的 GatewayWorker 架構:
Gateway 進程 (門戶層):
職責:專門負責維持 TCP / WebSocket 網絡連線、TLS 握手與心跳檢測。
特性:幾乎不包含任何業務邏輯,不進行 Reload,連線永遠保持開著。
BusinessWorker 進程 (業務層):
職責:處理聊天室的業務邏輯(如訊息解析、廣播、資料庫讀寫、身份驗證)。
特性:所有業務程式碼都在此層執行。當執行 Reload 命令時,GatewayWorker 會逐個平滑重啟 BusinessWorker 進程,舊進程處理完當前任務後退出,新進程載入最新的程式碼。
二、 熱更新程式碼的撰寫規範 (極度關鍵)
即使使用了 GatewayWorker,若程式碼撰寫方式不符合常駐記憶體的規範,Reload 也會失效。必須遵循以下 3 大原則:
1. 業務邏輯檔案必須在 onWorkerStart 事件觸發後才動態 require/include
如果將業務邏輯類別 (Class) 或檔案在主腳本頂層(即進程分叉 Fork 之前)就 require 進來,這些程式碼會直接載入到 Master 主進程的記憶體中,後續執行 Reload 將無法更新它們。
錯誤寫法 (無法熱更新):
代碼: 選擇全部
// 寫在檔案最上方,Master 進程啟動時就已載入記憶體
require_once __DIR__ . '/Events.php';
$worker = new BusinessWorker();
$worker->name = 'ChatBusinessWorker';
Workerman / GatewayWorker 內部已處理好此機制,只需將業務邏輯寫在 Events.php(或指定的 EventClass)中,GatewayWorker 會在 BusinessWorker 啟動時自動載入。
2. Avoid Opcache 快чи阻礙更新
若在 php.ini 啟用了 OPcache(生產環境通常會開啟),請確保 php.ini 設定包含以下配置,或者在執行 Reload 前清除 OPcache,否則 PHP 會繼續讀取記憶體中的舊位元組碼 (Bytecode):
代碼: 選擇全部
; 設定為 1,每次檔案修改時自動檢查並更新 OPcache
opcache.validate_timestamps=1
opcache.revalidate_freq=0
當你修改了 Events.php 或其他業務邏輯檔案後,切勿使用 stop 再 start,請使用以下方式進行 Reload:
方法 1:命令列原生指令 (推薦)
在專案根目錄下執行:
代碼: 選擇全部
php start.php reload
方法 2:透過 Systemd 觸發
若你使用 Systemd 管理 Workerman,可執行:
代碼: 選擇全部
sudo systemctl reload workerman-chat
方法 3:檔案變動自動熱重載 (開發環境專用)
在開發階段,手動輸入指令較為繁瑣。可以在專案中引進 Workerman 官方提供的 FileMonitor 模組,讓系統監控檔案修改並自動觸發 Reload:
代碼: 選擇全部
use Workerman\Worker;
use Workerman\Lib\Timer;
// 建立一個專門監控檔案變動的 Worker
$monitor = new Worker();
$monitor->name = 'FileMonitor';
$monitor->onWorkerStart = function() {
// 監控 applications 目錄下的檔案
$monitor_dir = __DIR__ . '/Applications';
// 每秒檢查一次檔案更新時間
Timer::add(1, function() use ($monitor_dir) {
static $last_mtime;
if (!$last_mtime) {
$last_mtime = time();
return;
}
// 遞迴檢查目錄下是否有檔案被修改
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($monitor_dir));
foreach ($iterator as $file) {
if ($file->isDir()) continue;
if ($last_mtime < $file->getMTime()) {
echo $file . " updated, reloading...\n";
// 送出 SIGUSR1 信號給 Master 進程觸發 reload
posix_kill(posix_getppid(), SIGUSR1);
$last_mtime = time();
break;
}
}
});
};
1.前端打開瀏覽器建立 WebSocket 聊天室連線,並監聽 onclose 與 onerror 事件:
代碼: 選擇全部
const ws = new WebSocket('wss://yourdomain.com/ws');
ws.onclose = () => console.log('連線斷開!'); // 若跳出此訊息代表平滑失敗
3在伺服器執行 php start.php reload
4 在前端發送訊息,若收到新的回應邏輯,且瀏覽器控制台沒有觸發 onclose,即代表平滑熱更新成功!