PHP 的 Web 運行原理 ( 4 ) - Reactor 的實現之 Swoole

栏目: PHP · 发布时间: 7年前

内容简介:本篇文章,咱們將要在說明另一個在 php 實現 reactor 模式的東西本篇文章分為以下幾個章節 :swoole 官網寫到 :

本篇文章,咱們將要在說明另一個在 php 實現 reactor 模式的東西 swoole 。

本篇文章分為以下幾個章節 :

  • Swoole 的架構
  • Swoole 非阻塞 I/O 的處理
  • Swoole 的 Coroutine
  • Swoole 實際使用的注意事項

swoole 的架構

swoole 官網寫到 :

event-driven asynchronous & coroutine-based concurrency networking communication engine with high performance written in C and C++ for PHP.

swoole 它是一個用 c++ 所寫的 php extension,一個非常高效能的通訊引擎,而它能達到高效能的基礎在於以下幾個重點 :

  • event-drivent
  • coroutine

一句話來說他可以幹麻。

它可以讓我們建立一個高效能的網路服務

下面就是它實現非阻塞 I/O 的架構圖。

當它建立一個 http server 以後,你會看到產生出下圖這些 process 與 thread,基本上可以分為幾個部份 :

PHP 的 Web 運行原理 ( 4 ) - Reactor 的實現之 Swoole
  • Master Process : 用來管理 reactor thread,主要就是當 client 發送一個 http 請求後,由它來決定那個 reactor thread 來接客。
  • Reactor Thread : 每個 thead 中都有使用 I/O 多路復用的技術來監聽多個 socket,當有事件(讀或寫資料)進來時,會發送給某個 worker process 來處理。
  • Manager Process : 用來管理 worker process,也是用來決定那個 worker process 來接客。
  • Worker Process : 實際運行 php 代碼的地方,它這裡也有使用 I/O 多路復用技術來監聽 socket。這裡提供同步阻塞或是異步非阻塞操作。
  • Task Process : 一樣 php 代碼運行的地方,它會接受由 worker 丟過來的任務(開發者自已撰寫),通常都是一些 cpu 密集的運算。這裡只能同步阻塞操作。

上面是主要的架構,然後我們來理一下所謂的阻塞 I/O 會在那幾個地方,而 swoole 又是如何解決。

Swoole 非阻塞 I/O 的處理

這裡基本上可以分成兩個地方來看。

高併發建立連線的地方

swoole 這裡是在 reactor thread 實作 reactor 模式來解決,而 reactor 最主要透過的技術為 I/O 多路復用技術來做到大量 socket I/O 監控,並且在將事情丟給 worker process 處理。

順到說一下,與 nodejs 或 reactPHP 相比,swoole 這裡勝在它開啟多個 thread 來監控多條連線,理論上它這樣應該可以承受的比 nodejs 與 reactPHP 更多的併發量。

Worker process 中大量的 db 或 redis 操作

swoole 有提供幾個機制可以給 worker process 實現非阻塞 I/O 操作。

  • reactor 模式 (網路 I/O)
  • 丟 task worker 處理 (網路、檔案 I/O)
  • 底層使用 aio 處理 (檔案 I/O)

reactor 模式 (網路 I/O)

首先第一個 reactor 模式就與 reactor thread 的機制一樣,都是會使用 I/O 多路復用來監聽 socket,當有事件進來時,就執行 handler (callback)。

下面為範例,當在 worker 執行了這段程式碼以後,會將連 redis 的這條 socket 丟到 reactor 來進行監控,當有事件進來時,會執行 callback。

而這就是所謂的 異步非阻塞 。( 異步: callback 非阻塞: 不會卡住 process )

<?php

$client = new swoole_redis;
$client->connect('127.0.0.1', 6379, function (swoole_redis $client, $result) {
    // callback function
    if ($result === false) {
        echo "connect to redis server failed.\n";
        return;
    }
    $client->set('key', 'swoole', function (swoole_redis $client, $result) {
        var_dump($result);
    });
});

而所謂的 同步阻塞 的範例如下,假設你在 worker 中直接使用 predis 來操作 redis,那這就是所謂的同步阻塞。

同步就是依順序走,而阻塞就是指會在 $cient->get("foo") 那會卡住整個 process。

<php

$client = new Predis\Client();
$client->set('foo', 'bar');
$value = $client->get('foo');

丟 task worker 處理 (網路、檔案 I/O)

而下面為處理 file 的範例,但這裡要注意,這種處理檔案的方式受限於 task process 的數量,假設我們 task 的數量為二,那當如果開啟三個 task 來處理檔案時,第三個就會無法處理。

<?php

$server = new swoole_http_server("0.0.0.0", 9501);

$server->set(array(
    'worker_num' => 1,
    'task_worker_num' => 2
));

$server->on('request', function ($request, $response) use ($server) {

    // ----------------------------------------------------
    var_dump('wait...');
    $server->task("hi", 0);
    var_dump('done !...');
    // ----------------------------------------------------

    $response->header("Content-Type", "text/html; charset=utf-8");
    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});


$server->on('task', function($server, $task_id, $from_id, $data){
    $request = file_get_contents('file 位置');
});

$server->on('Finish', function (swoole_server $serv, $task_id, $data) {
    echo "Task#$task_id finished, data_len=".strlen($data).PHP_EOL;
});

$server->start();

底層使用 aio 處理 (檔案 I/O)

這裡注意要將 runtime 的 coroutine 開啟,才能讓 file_get_contents 使用底層 aio 來完成同步非阻塞操作。

<?php
Swoole\Runtime::enableCoroutine(true);

go(function () {
    $fp = file_get_contents("test.log");
    fclose($fp);
});

Q&A 為什麼 Reactor 的機制無法處理 filesystem 的東西呢 ?

目前筆者只知道,如果你將檔案的 file descriptor 註冊到 I/O 多路復用(epoll) 中,會發生以下的錯誤:

EPERM The target file fd does not support epoll.

這也是為什麼需要使用其它 process 來處理,而在 nodejs 中碰到這種檔案類型的操作,則是開啟 thread 來進行處理,詳細可看此篇文章。

Nodejs 之運行機制原理 - 馬克筆

Swoole 中的 Coroutine

swoole 中有提供一個 coroutine 的功能,那它是做什麼呢 ?

它可以讓 worker 做到同步非阻塞的操作

剛剛上面有說 worker 可以提供以下兩種 I/O 操作型式 :

  • 同步阻塞 ( 同步:程式碼順序走 阻塞: 會卡住整個 process )
  • 異步非阻塞 ( 異步: callback 機制 非阻塞: 不會卡住整個 process )

但實際上它還有提供第三種 :

  • 同步非阻塞 ( 同步: 程式碼順序走 非阻塞: 不會卡住整個 process )

而實現的方法就是使用 coroutine 也就是協程。

coroutine 使用範例

使用情景如下程式碼,那在 go 裡面的這段程式碼就會開一個被稱為 coroutine 協程的東西來處理這一段,這樣就可以做到 同步非阻塞 操作。

go(function () {
    $redis = new Swoole\Coroutine\Redis();
    $redis->connect('127.0.0.1', 6379);
    $val = $redis->get('key');
});

而如果沒有用 coroutine 的話,那就只能做到 異步非阻塞操作 ,如下程式碼,也就是一堆 callback。

$client = new swoole_redis;
$client->connect('127.0.0.1', 6379, function (swoole_redis $client, $result) {
    $client->GET('key', 'swoole', function (swoole_redis $client, $result) {
        var_dump($result);
    });
});

Q&A - Coroutine 是實現非阻塞 I/O 的技術嗎 ?

不是。

coroutine 很多人會說它是一個比 thread 還較更小單位的操作單位,所以就會想,那我每一個阻塞 I/O 就開一個協程來處理,那這樣不就可以實現非阻塞 I/O 操作了嗎 ? 就像 multi thread 原理一樣,而且我協程更省資源。

NoNoNo ~

process 為操作系統的最小資源管理單位,而 thread 為操作系統最小操作單位。

而 coroutine 是一個比 thread 更小的操作單位,但是它的操作是 用戶 (application) 所操作,而不是像 thread 一樣是操作系統所操作。

而 I/O 的操作是指操作系統的操作,當一個阻塞 I/O 執行時,讓 process 阻塞的是操作系統,所以如果你在一個協程內開啟一個阻塞 I/O 它仍然會卡住整個 process。

coroutine 嚴格來說只能幫你做到 同步非阻塞 ,非阻塞還是需要有 reactor 機制來實現。

備註: go 語言的 coroutine 就比較特殊了,這之後會另開篇章討論。

Swoole 的實際使用注意

與 reactPHP 的範例一樣,我們一樣使用 swoole 來建立一個 http server。

<?php

$server = new swoole_http_server("0.0.0.0", 9501);

$server->set(array(
    'worker_num' => 1,
    'task_worker_num' => 0
));

$server->on('request', function ($request, $response) {

    // ----------------------------------------------------
    var_dump('wait...');
    $request = file_get_contents('http://127.0.0.1:3000');
    var_dump('done !...');
    // ----------------------------------------------------

    $response->header("Content-Type", "text/html; charset=utf-8");
    $response->end("<h1>Hello Swoole. #".rand(1000, 9999)."</h1>");
});

$server->on('task', function($server, $task_id, $from_id, $data){
});

$server->start();

如果這時發兩條 http 請求會如何呢 ?

答案: 1 次 wait ( 因為第一次就卡住 process )

主要的原因基本上在於,你沒將這 I/O 操作丟到這個 worker 的 reactor 機制中,所以這個 I/O 操作仍然會是阻塞的。

然後有些人在測試時可能會收到二次 wait,但是有一點你要注意,那就是 worker_num 你設多少,如果你設 2 ,它當然可以收到二次,因為是由不同的 process 接受,但是接下來的第三次,你絕對收不到。

正確版

首先第一種是官方現在推薦使用 coroutine 的 同步非阻塞 的寫法。

<?php

$server = new swoole_http_server("0.0.0.0", 9501);

$server->set(array(
    'worker_num' => 2,
    'task_worker_num' => 0
));

$server->on('request', function ($request, $response) {

    // ----------------------------------------------------
    var_dump('wait...');
    $client = new Swoole\Coroutine\Http\Client('127.0.0.1', 3000);
    $client->get('/');
    echo $client->body;
    var_dump('done !...');
    // ----------------------------------------------------
});

$server->start();

而另一種就是使用 callback 的 異步非阻塞 的寫法。

<?php

$server = new swoole_http_server("0.0.0.0", 9501);

$server->set(array(
    'worker_num' => 2,
    'task_worker_num' => 0
));

$server->on('request', function ($request, $response) {

    // ----------------------------------------------------
    var_dump('wait...');
    $client = new swoole_http_client('127.0.0.1', 3000);
    $client->get('/', function ($client) {
        echo "Length: " . strlen($client->body) . "\n";
        echo $client->body;
    });
    var_dump('done !...');
    // ----------------------------------------------------
});

$server->start();

參考資料


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

DOM Scripting

DOM Scripting

Jeremy Keith / friendsofED / 2010-12 / GBP 27.50

There are three main technologies married together to create usable, standards-compliant web designs: XHTML for data structure, Cascading Style Sheets for styling your data, and JavaScript for adding ......一起来看看 《DOM Scripting》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具