第三方儲存媒介
前面我們介紹了基於 Swoole 的 Process
及 Process\Pool
模組在 PHP 中實現多程式管理,但是多程式模式下程式間是相互隔離的,無法共享資料和變數,即便是通過 global
定義的全域性或超全域性變數,也只是在所屬程式中有效,如果要在 Swoole 實現的多程式間共享資料,需要藉助第三方儲存媒介實現:
- 資料庫:MySQL、MongoDB
- 快取:Redis、Memcached
- 磁碟檔案
但是這也會引入新的問題,多程式同時操作一條記錄或一個檔案存在併發訪問問題,以資料庫操作為例,兩個程式可能會同時讀取一條資料,或者一個程式對某條記錄進行更新處理時,另一個程式也來讀取這條記錄並進行操作,會導致最終結果資料與預期不一致的情況,這個時候,我們就需要引入鎖的概念,當一個程式(比如程式A)對某個記錄進行寫操作時,對該記錄加鎖,這樣其它程式就無法操作該條記錄, 直到程式 A 事務提交再釋放這個鎖,讓其他程式可以進行操作。
記憶體共享
PHP 相關擴充套件
對於單機操作來說,除了這些第三方儲存媒介之外,還可以通過共享記憶體的方式實現程式間資料讀寫操作,有多個 PHP 擴充套件可以支援共享記憶體資料操作:
- Semaphore 擴充套件:可通過該擴充套件包提供的
shm_get_var
和shm_put_var
函式實現記憶體共享資料的讀寫操作; - Shmop 擴充套件:可通過該擴充套件包提供的
shmop_read
和shmop_write
函式實現記憶體共享資料的讀寫操作; - APCu(APC User Cache)擴充套件:可通過該擴充套件包提供的
apc_fetch
和apc_store
實現記憶體共享資料的讀寫操作。
Swoole Table
但是上述擴充套件要麼不支援鎖,要麼高併發時效能比較差,所以 Swoole 自己實現了一個共享記憶體讀寫工具 —— Swoole\Table
,該工具是一個基於共享記憶體和鎖實現的高效能併發資料結構,可用於解決多程式/多執行緒資料共享和同步加鎖問題:
- 效能強悍,單執行緒每秒可讀寫200萬次;
- 應用程式碼無需加鎖,內建行鎖自旋鎖,所有操作均是多執行緒/多程式安全,使用者層完全不需要考慮資料同步問題;
- 支援多程式,可用於多程式之間共享資料;
- 使用行鎖,而不是全域性鎖,僅當 2 個程式在同一 CPU 時間,併發讀取同一條資料才會進行發生搶鎖。
Swoole\Table
支援以 Key-Value 方式讀寫,使用起來非常簡單:
<?php // 初始化一個容量為 1024 的 Swoole Table $table = new \Swoole\Table(1024); // 在 Table 中新增 id 列 $table->column('id', \Swoole\Table::TYPE_INT); // 在 Table 中新增 name 列,長度為 50 $table->column('name', \Swoole\Table::TYPE_STRING, 10); // 在 Table 中新澤 score 列 $table->column('score', \Swoole\Table::TYPE_FLOAT); // 建立這個 Swoole Table $table->create(); // 設定 Key-Value 值 $table->set('student-1', ['id' => 1, 'name' => '學小君', 'score' => 80]); $table->set('student-2', ['id' => 2, 'name' => '學院君', 'score' => 90]); // 如果指定 Key 值存在則列印對應 Value 值 if ($table->exist('student-1')) { echo "Student-" . $table->get('student-1', 'id') . ':' . $table->get('student-1', 'name').":". $table->get('student-1', 'score') . "\n"; } // 自增操作 $table->incr('student-2', 'score', 5); // 自減操作 $table->decr('student-2', 'score', 5); // 表中總記錄數 $count = $table->count(); // 刪除指定表記錄 $table->del('student-1');
此外 Swoole\Table
類還實現了迭代器介面,支援通過 foreach
進行遍歷。
在 Laravel 中使用 Swoole\Table
如果要在 Laravel 中整合 Swoole 使用 Swoole\Table
,以 LaravelS 擴充套件包為例,首先要在配置檔案 config/laravels.php
中定義 swoole_tables
配置項:
'swoole_tables' => [ 'ws' => [ // 表名,會加上 Table 字尾,比如這裡是 wsTable 'size' => 102400, // 表容量 'column' => [ // 表欄位,欄位名為 value ['name' => 'value', 'type' => \Swoole\Table::TYPE_INT, 'size' => 8], ], ], ... // 還可以定義其它表 ],
然後我們可以在程式碼中通過swoole
例項上的wsTable
屬性訪問 SwooleTable:
class WebSocketService implements WebSocketHandlerInterface { ... // 連線建立時觸發 public function onOpen(Server $server, Request $request) { // 在觸發 WebSocket 連線建立事件之前,Laravel 應用初始化的生命週期已經結束,你可以在這裡獲取 Laravel 請求和會話資料 // 呼叫 push 方法向客戶端推送資料,fd 是客戶端連線標識欄位 Log::info('WebSocket 連線建立:' . $request->fd); app('swoole')->wsTable->set('fd:' . $request->fd, ['value' => $request->fd]); $server->push($request->fd, 'Welcome to WebSocket Server built on LaravelS'); } // 收到訊息時觸發 public function onMessage(Server $server, Frame $frame) { foreach (app('swoole')->wsTable as $key => $row) { if (strpos($key, 'fd:') === 0 && $server->exist($row['value'])) { Log::info('Receive message from client: ' . $row['value']); // 呼叫 push 方法向客戶端推送資料 $server->push($frame->fd, 'This is a message sent from WebSocket Server at ' . date('Y-m-d H:i:s')); } } } ... }
然後我們參考在 Laravel 中整合 Swoole 實現 WebSocket 伺服器這篇教程從客戶端向 WebSocket 伺服器發起請求,即可在最新日誌檔案中看到相應的日誌資訊:
[2020-04-24 19:39:03] local.INFO: WebSocket 連線建立:1 [2020-04-24 19:39:07] local.INFO: Receive message from client: 1