原文:《使用Swoole协程一键代理PHP-FPM服务》php
原创做者:陈曹奇昊 [学而思网校技术团队] 1周前 [2020.4.17]前端
在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服务进行交互,但这有什么用呢?浏览器
在一个Swoole的异步/协程服务中,咱们没法容忍任何阻塞的存在,只要有一处调用阻塞,那么整个服务程序都会退化为阻塞程序,而此时若是咱们又没有太多的资源去重构老项目,咱们一般会选择使用Task进程来解决。安全
Task进程是Swoole异步服务器中专门设计用来执行同步阻塞程序的工做进程,咱们能够很方便地调用$server->task方法去投递一个同步阻塞任务给Task进程并当即返回,Task进程在完成后再通知Worker进程接收结果,这样就构成了一个半异步半同步的服务器。服务器
咱们须要大量的task进程来处理少许的同步阻塞任务,但只须要少许的Worker就能够处理大量的异步非阻塞任务,这就是多路IO复用技术带来的好处。swoole
虽然这样看起来已经很是方便了,但仍是有一些不足,如:不少项目不单是同步阻塞,还只能运行在PHP-FPM语境下;此外,若是是协程服务器或是本身用socket写的服务器,就没法使用task功能。那么这时候协程版本的FastCGI就能够一展身手了。
首先咱们本地得有一个正在运行的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项目饱受性能困扰又因积重难返而没法快速重构,咱们仍是能够借助它来更平滑地将旧业务迁移到新的异步/协程服务器中。
最强大的是协程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站点
协程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++实现。