[转]使用Swoole协程一键代理PHP-FPM服务

原文:《使用Swoole协程一键代理PHP-FPM服务php

原创做者:陈曹奇昊 [学而思网校技术团队] 1周前 [2020.4.17]前端

1、什么是FastCGI

在Swoole最新发布的v4.5(RC)版本中,咱们实现了一项很是有意思的新特性,那就是协程版本的FastCGI客户端。nginx

那么什么是FastCGI呢?首先先来一个官方解释:git

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通讯的协议。github

其实很简单,你们使用PHP-FPM搭建服务的时候必然少不了前面架一个Nginx丶Apache或者IIS之类的东西做为代理,咱们应用程序和代理通讯的时候,可能会使用各类各样的协议(常见的好比浏览器使用的是HTTP/1.1,HTTP2,QUIC等),而代理的职责就是把各类协议的请求翻译成FastCGI来和PHP-FPM通讯,这样PHP服务就无需关心各类类型协议的解析,而能够只关心处理请求自己的内容,且FastCGI是二进制协议,相较于HTTP1.x这样的文本协议,FastCGI能够说是很是高效。sql

实现了FastCGI客户端,那么咱们就能够直接与PHP-FPM服务进行交互,但这有什么用呢?浏览器

2、Swoole中的Task进程

在一个Swoole的异步/协程服务中,咱们没法容忍任何阻塞的存在,只要有一处调用阻塞,那么整个服务程序都会退化为阻塞程序,而此时若是咱们又没有太多的资源去重构老项目,咱们一般会选择使用Task进程来解决。安全

Task进程是Swoole异步服务器中专门设计用来执行同步阻塞程序的工做进程,咱们能够很方便地调用$server->task方法去投递一个同步阻塞任务给Task进程并当即返回,Task进程在完成后再通知Worker进程接收结果,这样就构成了一个半异步半同步的服务器。服务器

咱们须要大量的task进程来处理少许的同步阻塞任务,但只须要少许的Worker就能够处理大量的异步非阻塞任务,这就是多路IO复用技术带来的好处。swoole

虽然这样看起来已经很是方便了,但仍是有一些不足,如:不少项目不单是同步阻塞,还只能运行在PHP-FPM语境下;此外,若是是协程服务器或是本身用socket写的服务器,就没法使用task功能。那么这时候协程版本的FastCGI就能够一展身手了。

3、使用协程FastCGI客户端调用PHP-FPM程序

首先咱们本地得有一个正在运行的PHP-FPM,默认配置,知道它的地址便可。

而后咱们写一个世界级的Hello程序,存档为/tmp/greeter.php,咱们只需在命令行中输入:

echo "<?php echo 'Hello ' . (\$_POST['who'] ?? 'World');" > /tmp/greeter.php

而后咱们得确保咱们已经安装了Swoole扩展,这时候咱们只须要在命令行输入:

php -n -dextension=swoole -r \
"Co\run(function() { \
     echo Co\FastCGI\Client::call('127.0.0.1:9000', '/tmp/greeter.php',  ['who' => 'Swoole']); \
});"

就能获得输出:

Hello Swoole

这样一个最简单的调用就完成了,而且是协程非阻塞的,咱们甚至能够经过多个客户端并发调用多个PHP-FPM提供的接口再提供给前端以提升响应速度。

咱们能够先写一个sleep程序来模拟同步阻塞的PHP-FPM应用:

<?php #blocking.php
sleep(1);
echo $_POST['id'] . PHP_EOL;

协程FastCGI支持了PSR风格(并不是规范)的操做方法,咱们也能够本身手动构造一个HTTP请求传入,籍此咱们能够灵活地构造任意FastCGI请求和PHP-FPM程序交互:

use Swoole\Coroutine;
use Swoole\Coroutine\FastCGI\Client;
use Swoole\FastCGI\HttpRequest;

$s = microtime(true);
Coroutine\run(function () {
    for ($n = 0; $n < 2; $n++) {
        Co::create(function () use ($n) {
            try {
                $client = new Client('127.0.0.1', 9000);
                $request = (new HttpRequest())
                    ->withScriptFilename('/path/to/blocking.php')
                    ->withMethod('POST')
                    ->withBody(['id' => $n]);
                $response = $client->execute($request);
                echo "Result: {$response->getBody()}\n";
            } catch (Client\Exception $exception) {
                echo "Error: {$exception->getMessage()}\n";
            }
        });
    }
});
$s = microtime(true) - $s;
echo 'use ' . $s . ' s' . "\n";

最终程序输出多是:

Result: 1

能够看到咱们并发请求两个阻塞1s的接口,而总耗时仅需1s(其实是MAX(...全部接口响应时间)),并且咱们能够看到先请求不必定先返回,这一样也证实了这是一个非阻塞的程序。

固然这里要注意的是,你能并发的数量取决于你机器上PHP-FPM的工做进程数量,若是工做进程数量不足,那么请求不得不进行排队。

协程FastCGI客户端的到来,至关于咱们的协程应用如今拥有了PHP-FPM这样一个无比强大稳定的进程管理器做为Task进程池来完成同步阻塞任务,借此咱们能够解决不少问题,如:

有一些协议暂未受到Swoole协程的支持,但却有可用的同步阻塞的版本(MongoDB、sqlserver等),咱们就能够经过它放心地投递给PHP-FPM来完成。

或是你有一个很老的PHP-FPM项目饱受性能困扰又因积重难返而没法快速重构,咱们仍是能够借助它来更平滑地将旧业务迁移到新的异步/协程服务器中。

4、使用协程FastCGI一键代理WordPress

最强大的是协程FastCGI客户端还支持一键代理功能,能够将其它HTTP请求对象转化为FastCGI请求(目前只支持了Swoole\Http,后续可能加入PSR支持),也能够将FastCGI响应转化为HTTP响应,基于这个特性,咱们能够作到代理世界上最好的博客程序:

declare(strict_types=1);

use Swoole\Constant;
use Swoole\Coroutine\FastCGI\Proxy;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\Http\Server;

$documentRoot = '/path/to/wordpress'; // WordPress目录的绝对路径
$server = new Server('0.0.0.0', 80, SWOOLE_BASE);
$server->set([
    Constant::OPTION_WORKER_NUM => swoole_cpu_num() * 2,
    Constant::OPTION_HTTP_PARSE_COOKIE => false,
    Constant::OPTION_HTTP_PARSE_POST => false,
    Constant::OPTION_DOCUMENT_ROOT => $documentRoot,
    Constant::OPTION_ENABLE_STATIC_HANDLER => true,
    Constant::OPTION_STATIC_HANDLER_LOCATIONS => ['/'],
]);
$proxy = new Proxy('127.0.0.1:9000', $documentRoot);
$server->on('request', function (Request $request, Response $response) use ($proxy) {
    $proxy->pass($request, $response);
});
$server->start();

撇开一些配置项的设置,整个代理的核心提取出来其实就只有这样一句代码

(new Proxy('127.0.0.1:9000', $documentRoot))->pass($request, $response);

而后咱们就能够在浏览器中访问localhost:

图示为本地已搭建好的WordPress站点

5、协程FastCGI客户端的背后

协程FastCGI客户端,咱们能够在 https://github.com/swoole/library 仓库查看它的源码,在README中能够找到现成的Docker构建命令和配套演示程序让咱们快速上手体验它。

此外,经过查看源码咱们不难发现,协程FastCGI客户端是彻底使用PHP代码编写、基于协程Socket实现的,因为FastCGI是高效的二进制协议,咱们使用PHP代码来进行解析也不会有太大的开销(而HTTP1.x这样的文本协议就不行,越是人类友好的协议,对机器来讲就越不友好)。

包括不少Swoole的其它组件如:WaitGroup、全自动链接池、协程Server等等,都是使用PHP编写的,PHP编写的组件具备内存安全、开发高效的特色,而且Swoole内核将这些PHP组件内嵌到了扩展中,开发者是无感知的,安装扩展后就能当即使用这些组件而无需引入额外的包管理。

即便FastCGI客户端是纯PHP编写的,压测性能和nginx仍在一个量级,这也证实了PHP的性能瓶颈并不老是在于PHP代码自己,不少时候是因为同步阻塞的IO模型致使的。

目前PHP编写的组件在Swoole中占比还不高,将来咱们但愿能引入更多PHP编写的内部组件来解决功能性的需求,而只有PHP难以知足的一些高性能的需求(如各类复杂协议的处理)才考虑使用C++实现。

相关文章
相关标签/搜索