Swooleによる高性能サーバー
コルーチンベースのSwooleで数千の接続に対応します。
「Swooleによる高性能サーバー」はCoddyKit上の無料PHP Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPHP Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 PHP Academyコースには全4レッスンが含まれています。
Swooleとは
Swoole(およびそのフォークであるOpenSwoole)は、PHPを高性能なコルーチンベースのイベント駆動ランタイムに変えるC拡張機能です。PHP-FPMがリクエストごとにプロセスを生成する代わりに、Swooleサーバーはアプリケーションを一度だけ起動し、組み込みコルーチンを備えた長時間稼働するワーカーのプールをメモリ上に保持して、そこからリクエストを処理します。その結果、スループットが桁違いに向上し、1回のリクエスト内でネイティブな並行処理を実現できます。
永続HTTPサーバー
典型的な例は、スクリプト内に完全なHTTPサーバーを構築することです。サーバーは接続を待ち受け、各リクエストをワーカーに振り分け、フレームワークを再度ブートストラップすることなく動作し続けます。なお、これはext-swooleが必要であり、標準のPHP CLIでは実行できません。
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set(['worker_num' => 4]);
$server->on('request', function ($request, $response) {
$response->header('Content-Type', 'application/json');
$response->end(json_encode(['hello' => $request->server['request_uri']]));
});
echo "listening on :9501\n";
$server->start();コールバックではなくコルーチン
Swooleの最大の特徴はコルーチンです。ブロッキング処理のように順番に実行するコードを記述し、ランタイムが協調的にスケジュールします。Coroutine\run()はコルーチンコンテキストを開始し、go()は新しいコルーチンを起動します。各コルーチンはFiberのように独自のスタックを持ちますが、SwooleはI/Oの発生箇所で自動的にスケジュールします。
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () { Co::sleep(1); echo "A done\n"; });
go(function () { Co::sleep(1); echo "B done\n"; });
// Both sleep concurrently -> total ~1s, not 2s
});ブロッキング関数のフック
コルーチンを使いやすくする仕組みがランタイムフックです。Swooleはブロッキング呼び出しであるPDO、Redis、curl、sleep、file_get_contentsを、コルーチンに対応したノンブロッキング版へ透過的に書き換えます。通常の$pdo->query()を実行しても、ワーカーを停止させる代わりにコルーチンが一時停止します。
<?php
use Swoole\Runtime;
use Swoole\Coroutine as Co;
Runtime::enableCoroutine(SWOOLE_HOOK_ALL); // hook PDO, curl, sleep, etc.
Co\run(function () {
go(function () { sleep(1); echo "task 1\n"; }); // sleep() is now async!
go(function () { sleep(1); echo "task 2\n"; });
}); // finishes in ~1s because sleep was hookedWaitGroupによる並行I/O
処理を分散して合流させるために、SwooleはGoのsync.WaitGroupに似たWaitGroupを提供します。コルーチンを起動し、タスクごとにadd()を呼び出し、完了時にdone()を呼び出してから、すべてが並行して完了するまでwait()で待機します。
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\WaitGroup;
Co\run(function () {
$wg = new WaitGroup();
$results = [];
foreach (['eu', 'us', 'asia'] as $region) {
$wg->add();
go(function () use ($wg, $region, &$results) {
Co::sleep(0.5); // simulated API call
$results[$region] = "ok-$region";
$wg->done();
});
}
$wg->wait(); // all three ran in parallel (~0.5s)
var_dump($results);
});通信用チャネル
コルーチンはチャネルを介して連携します。チャネルは型付きで容量制限のあるキューで、満杯になるとプロデューサーを、空になるとコンシューマーを一時停止します。これはCSPスタイルのメッセージングであり、共有状態のロックを完全に避けられます。
<?php
use Swoole\Coroutine as Co;
use Swoole\Coroutine\Channel;
Co\run(function () {
$chan = new Channel(2);
go(function () use ($chan) {
foreach (range(1, 3) as $n) $chan->push($n);
$chan->push(null); // sentinel
});
go(function () use ($chan) {
while (($v = $chan->pop()) !== null) echo "got $v\n";
});
});コネクションプーリング
永続サーバーでは、リクエストごとにDB接続を開くべきではありません。Swooleはコルーチンから安全に利用できるコネクションプールを提供します。これにより、ワーカーは固定数の接続を数千の並行コルーチン間で再利用でき、接続の頻繁な生成と破棄をなくせます。
<?php
use Swoole\Database\PDOConfig;
use Swoole\Database\PDOPool;
use Swoole\Coroutine as Co;
Co\run(function () {
$pool = new PDOPool((new PDOConfig())
->withHost('127.0.0.1')->withDbname('app')
->withUsername('u')->withPassword('p'), 8);
go(function () use ($pool) {
$pdo = $pool->get(); // borrow
$row = $pdo->query('SELECT NOW()')->fetch();
$pool->put($pdo); // return to pool
var_dump($row);
});
});状態リーク:永続メモリの落とし穴
プロセスがリクエストをまたいで存続するため、グローバル状態とstatic状態が保持されます。無制限に増え続けるstaticキャッシュ、リクエスト固有のデータを保持するシングルトン、または解放されないDBトランザクションは、後続のリクエストを壊し、メモリを使い果たす可能性があります。ルールは次のとおりです。staticやシングルトンにリクエストスコープのデータを置かないこと、リクエストごとのコンテキストをリセットすること、そしてmax_requestを設定してワーカーを定期的に再起動することです。
<?php
$server = new Swoole\Http\Server('0.0.0.0', 9501);
$server->set([
'worker_num' => 8,
'max_request' => 10000, // recycle a worker after N requests to bound leaks
]);
// Never store $request data in a static property between requests!コルーチンローカルコンテキスト
1つのワーカーが多数のコルーチンを交互に実行するため、リクエストごとのデータに通常のstaticを使うことはできません。Swooleは各コルーチンにコンテキスト(Coroutine::getContext())を提供します。これはコルーチンの存続期間だけ存在するオブジェクトで、現在のユーザー、リクエストID、トランザクションハンドルなどを保持するのに最適です。
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$ctx = Co::getContext();
$ctx['request_id'] = 'req-123';
// ... deep call chain can read Co::getContext()['request_id']
echo Co::getContext()['request_id'], PHP_EOL;
});
});プロセスモデルとタスクワーカー
Swooleは、masterプロセス(イベントループスレッドを管理)、manager(ワーカーを生成・監視)、workerプロセス(リクエストハンドラーとコルーチンを実行)、そして必要に応じてtask worker($server->task()で委譲された負荷の高い処理、ブロッキング処理、CPUバウンドのジョブを実行)を動かします。画像処理や長時間のCPU処理にはタスクワーカーを使い、リクエスト用コルーチンを停止させないようにします。これにより、並行性と並列性の両方を実現できます。
Deferとタイマー
Swooleでは、Coroutine::defer()を使って現在のコルーチンの終了後に処理をスケジュールできます。例外が発生した場合でもプールされたリソースを確実に解放できるため、最適です。サーバー全体のタイマー(Timer::tick)を使えば、外部のcronなしで、キャッシュ更新などの定期処理をワーカー内で実行できます。
<?php
use Swoole\Coroutine as Co;
Co\run(function () {
go(function () {
$conn = 'borrowed-connection';
Co::defer(function () use ($conn) {
echo "released $conn\n"; // runs when coroutine exits, even on error
});
echo "using $conn\n";
});
});確認問題
Swooleサーバーでリクエストデータをstaticプロパティに保存してはいけないのはなぜですか。
まとめ
SwooleはPHPを常駐型のコルーチン駆動サーバーに変えます。
- アプリケーションは一度だけ起動し、ワーカープールが組み込みのコルーチン(
Co\run/go())でリクエストを処理します。 - ランタイムフックにより、通常のPDO、curl、sleepがノンブロッキングになります。WaitGroupとチャネルで並行処理を連携できます。
- コネクションプールにより、コルーチン間でDB接続を再利用できます。
- メモリが永続するため、状態リークを避ける必要があります。リクエストごとのデータには
Coroutine::getContext()を使い、max_requestを設定し、負荷の高い処理はタスクワーカーに委譲します。
よくある質問
「Swooleによる高性能サーバー」レッスンは無料ですか?
はい。「Swooleによる高性能サーバー」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、PHP Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 PHP Academyコースには全4レッスンが含まれています。
「Swooleによる高性能サーバー」で何を学びますか?
コルーチンベースのSwooleで数千の接続に対応します。 ブラウザで直接実行するハンズオンコードでPHP Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。
PHP Academyを始めるのに経験は必要ですか?
事前経験は必要ありません。CoddyKitのPHP Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。
「Swooleによる高性能サーバー」レッスンにはどのくらい時間がかかりますか?
ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。
このPHP Academyレッスンでコードを書いて実行できますか?
はい。すべてのPHP Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。
このコースのすべてのレッスン
- PHPの並行実行モデル
- PHP 8.1 Fibers
- ReactPHPによるイベントループ
- Swooleによる高性能サーバー