溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PHP中怎么搭建一個HTTP服務

發布時間:2021-06-30 14:21:19 來源:億速云 閱讀:269 作者:Leah 欄目:大數據

本篇文章給大家分享的是有關PHP中怎么搭建一個HTTP服務,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        // 'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = new Redis;
            $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}

首先,我們創建了一個Swoole\Process對象,這個對象會開啟一個子進程,在子進程中,我創建了一個HTTP Server,這個服務器BASE模式的。除了BASE模式之外,還有一種PROCESS模式。在PROCESS模式下,套接字連接是在Master進程維持的,Master進程和Worker進程會多一層IPC通信的開銷,但是,當Worker進程奔潰的時候,因為連接是在Master進程維持的,所以連接不會被斷開。所以,Process模式適用于維護大量長連接的場景。

BASE模式是在每個工作進程維持自己的連接,所以性能會比Master更好。并且,在HTTP Server下,BASE模式會更加的適用。

這里,我們將worker_num,也就是進程的數量設置為當前機器CPU核數的兩倍。但是,在實際的項目中,我們需要不斷的壓測,來調整這個參數。

workerStart的時候,也就是工作進程啟動的時候,我們讓子進程向管道中隨意寫入一個數據給父進程,父進程此時會讀到一點數據,讀到數據后,父進程才開始壓測。

此時,壓測的請求會進入onRequest回調。在這個回調中,我們創建了一個Redis客戶端,這個客戶端會連接Redis服務器,并請求一條數據。得到數據后,我們調用end方法來響應壓測的請求。當錯誤時,我們返回一個錯誤碼為500的響應。

在開始壓測前,我們需要安裝Redis擴展:

pecl install redis

然后php.ini配置中開啟redis擴展即可。

我們還需要在Redis服務器里面插入一條數據:

127.0.0.1:6379> SET greeter swoole
OK
127.0.0.1:6379> GET greeter
"swoole"
127.0.0.1:6379>

OK,我們現在進行壓測:

~/codeDir/phpCode/swoole/server # php server.php
Concurrency Level:      256
Time taken for tests:   2.293 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    4361.00 [#/sec] (mean)
Time per request:       58.702 [ms] (mean)
Time per request:       0.229 [ms] (mean, across all concurrent requests)
Transfer rate:          715.48 [Kbytes/sec] received

我們發現,現在的QPS比較低,只有4361.00。

因為,我們目前使用的Redis擴展是PHP官方的同步阻塞客戶端,沒有利用到協程(或者說異步的特性)。當進程去連接Redis服務器的時候,可能會阻塞整個進程,導致進程無法處理其他的連接,這樣,這個HTTP Server處理請求的速度就不可能快。但是,這個壓測結果會比FPM下好,因為Swoole是常駐進程的。

現在,我們來開啟Swoole提供的RuntimeHook機制,也就是在運行時動態的將PHP同步阻塞的方法全部替換為異步非阻塞的協程調度的方法。我們只需要在server->set配置中加入一行即可:

'hook_flags' => SWOOLE_HOOK_ALL

此時,我們再來運行這個腳本:

Concurrency Level:      256
Time taken for tests:   1.643 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    6086.22 [#/sec] (mean)
Time per request:       42.062 [ms] (mean)
Time per request:       0.164 [ms] (mean, across all concurrent requests)
Transfer rate:          998.52 [Kbytes/sec] received

我們發現,此時的QPS還是有一定的提升的。(這里,視頻中壓測的時候,會夯住請求,導致QPS非常的低,但是我實際測試的時候沒有發生這個情況,估計是和Redis服務器本身對連接個數的的配置有關)

但是,為了避免請求數量過多,導致創建連接個數過多的問題,我們可以使用一個Redis連接池來解決。(同步阻塞是沒有Redis連接過多的問題的,因為一旦worker進程阻塞住了,那么后面的請求就不會繼續執行了,也就不會創建新的Redis連接了。因此,在同步阻塞的模式下,Redis的連接數量最大是worker進程的個數)

現在,我們來實現一下Redis連接池:

class RedisQueue
{
    protected $pool;
    public function __construct()
    {
        $this->pool = new SplQueue;
    }
    public function get(): Redis
    {
        if ($this->pool->isEmpty()) {
            $redis = new \Redis();
            $redis->connect('127.0.0.1', 6379);
            return $redis;
        }
        return $this->pool->dequeue();
    }
    public function put(Redis $redis)
    {
        $this->pool->enqueue($redis);
    }
    public function close()
    {
        $this->pool = null;
    }
}

這里通過spl的隊列實現的連接池。如果連接池中沒有連接的時候,我們就新建一個連接,并且把創建的這個連接返回;如果連接池里面有連接,那么我們獲取隊列中前面的一個連接。當我們用完連接的時候,就可以調用put方法歸還連接。這樣,我們就可以在一定程度上復用Redis的連接,緩解Redis服務器的壓力,以及頻繁創建Redis連接的開銷也會降低。

我們現在使用這個連接池隊列:

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $server->pool = new RedisQueue;
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = $server->pool->get();
            // $redis = new Redis;
            // $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $server->pool->put($redis);
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}
class RedisQueue
{
    protected $pool;
    public function __construct()
    {
        $this->pool = new SplQueue;
    }
    public function get(): Redis
    {
        if ($this->pool->isEmpty()) {
            $redis = new \Redis();
            $redis->connect('127.0.0.1', 6379);
            return $redis;
        }
        return $this->pool->dequeue();
    }
    public function put(Redis $redis)
    {
        $this->pool->enqueue($redis);
    }
    public function close()
    {
        $this->pool = null;
    }
}

我們在worker進程初始化的時候,創建了這個RedisQueue。然后在onRequest的階段,從這個RedisQueue里面獲取一個Redis連接。

現在,我們來進行壓測:

Concurrency Level:      256
Time taken for tests:   1.188 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    8416.18 [#/sec] (mean)
Time per request:       30.418 [ms] (mean)
Time per request:       0.119 [ms] (mean, across all concurrent requests)
Transfer rate:          1380.78 [Kbytes/sec] received

QPS提升到了8416.18。

但是,通過splqueue實現的連接池是有缺陷的,因為這個隊列是可以無限長的。這樣,當并發量特別大的時候,還是會有可能創建非常多的連接,因為連接池里面可能始終都是空的。

這個時候,我們可以使用Channel來實現連接池。代碼如下:

class RedisPool
{
    protected $pool;
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++)
        {
            while (true) {
                try {
                    $redis = new \Redis();
                    $redis->connect('127.0.0.1', 6379);
                    $this->put($redis);
                    break;
                } catch (\Throwable $th) {
                    usleep(1 * 1000);
                    continue;
                }
            }
        }
    }
    public function get(): \Redis
    {
        return $this->pool->pop();
    }
    public function put(\Redis $redis)
    {
        $this->pool->push($redis);
    }
    public function close()
    {
        $this->pool->close();
        $this->pool = null;
    }
}

可以看到,我們在這個構造方法中,將這個Channelsize設置為這個傳入的參數。并且,創建size個連接。這些連接會在初始化連接池的時候就被創建,處于就就緒狀態。這個有好處也有壞處,壞處就是在每個進程初始化的時候,就會占用一些連接,但是此時的進程并不會接收連接。好處就是提前創建好了Redis連接,這樣服務器響應的延遲就會降低。

雖然,其他地方的代碼其實和RedisQueue的實現一樣。但是,底層是和RedisQueue大有不同的。因為當Channel里面沒有Redis連接的時候,會讓當前的協程掛起,讓其他的協程繼續被執行。等有協程把Redis連接還回到連接池里面的時候,這個被掛起的協程才會繼續執行。這就是協程協作的原理。

現在,我們修改服務器的代碼:

<?php
use Swoole\Http\Request;
use Swoole\Http\Response;
$process = new Swoole\Process(function (Swoole\Process $process) {
    $server = new Swoole\Http\Server('127.0.0.1', 9501, SWOOLE_BASE);
    $server->set([
        'log_file' => '/dev/null',
        'log_level' => SWOOLE_LOG_INFO,
        'worker_num' => swoole_cpu_num() * 2,
        'hook_flags' => SWOOLE_HOOK_ALL,
    ]);
    $server->on('workerStart', function () use ($process, $server) {
        $server->pool = new RedisPool(64);
        $process->write('1');
    });
    $server->on('request', function (Request $request, Response $response) use ($server) {
        try {
            $redis = $server->pool->get();
            // $redis = new Redis;
            // $redis->connect('127.0.0.1', 6379);
            $greeter = $redis->get('greeter');
            if (!$greeter) {
                throw new RedisException('get data failed');
            }
            $server->pool->put($redis);
            $response->end("<h2>{$greeter}</h2>");
        } catch (\Throwable $th) {
            $response->status(500);
            $response->end();
        }
    });
    $server->start();
});
if ($process->start()) {
    register_shutdown_function(function () use ($process) {
        $process::kill($process->pid);
        $process::wait();
    });
    $process->read(1);
    System('ab -c 256 -n 10000 -k http://127.0.0.1:9501/ 2>&1');
}
class RedisPool
{
    protected $pool;
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++)
        {
            while (true) {
                try {
                    $redis = new \Redis();
                    $redis->connect('127.0.0.1', 6379);
                    $this->put($redis);
                    break;
                } catch (\Throwable $th) {
                    usleep(1 * 1000);
                    continue;
                }
            }
        }
    }
    public function get(): \Redis
    {
        return $this->pool->pop();
    }
    public function put(\Redis $redis)
    {
        $this->pool->push($redis);
    }
    public function close()
    {
        $this->pool->close();
        $this->pool = null;
    }
}

只需要修改workerStart里面的部分即可,其他地方不需要做修改。這樣,每個進程最多只能創建64Redis連接。

我們繼續壓測:

Concurrency Level:      256
Time taken for tests:   0.817 seconds
Complete requests:      10000
Failed requests:        0
Keep-Alive requests:    10000
Total transferred:      1680000 bytes
HTML transferred:       150000 bytes
Requests per second:    12234.30 [#/sec] (mean)
Time per request:       20.925 [ms] (mean)
Time per request:       0.082 [ms] (mean, across all concurrent requests)
Transfer rate:          2007.19 [Kbytes/sec] received

以上就是PHP中怎么搭建一個HTTP服務,小編相信有部分知識點可能是我們日常工作會見到或用到的。希望你能通過這篇文章學到更多知識。更多詳情敬請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女