通過 Swoole\Table 實現 Swoole 多進程數據共享

第三方存儲媒介

前面我們介紹了基於 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 服務器發起請求,即可在最新日誌文件中看到相應的日誌信息:

[2019-06-19 22:09:03] local.INFO: WebSocket 連接建立:1  
[2019-06-19 22:09:07] local.INFO: Receive message from client: 1

更多學習內容請訪問:

騰訊T3-T4標準精品PHP架構師教程目錄大全,只要你看完保證薪資上升一個臺階(持續更新)​

以上內容希望幫助到大家,很多PHPer在進階的時候總會遇到一些問題和瓶頸,業務代碼寫多了沒有方向感,不知道該從那裏入手去提升,對此我整理了一些資料,包括但不限於:分佈式架構、高可擴展、高性能、高併發、服務器性能調優、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql優化、shell腳本、Docker、微服務、Nginx等多個知識點高級進階乾貨需要的可以免費分享給大家,需要的可以加入我的官方羣點擊此處


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章