在网络应用场景下,PHP并没有向Golang那样去实现http网络库,而是实现了FastCGI协议,然后通过web服务器配合实现了http的处理。web服务器来处理http请求,然后将解析的结果再通过FastCGI协议转发给处理程序,处理程序处理完成后将结果返回给web服务器,web服务器再返回给客户。
PHP-FPM是经典的多进程并发模型,即Master/Worker模型。Master进程与Worker进程之间不会直接进行通信,Master进程只负责Fork和管理子进程,网络请求由子进程处理,一个Worker进程同时只能处理一个请求。Master通过共享内存获取Worker进程的信息,比如Worker进程当前状态,已处理请求数等,当Master进程要杀掉一个Worker进程时则通过发送信号的方式通知Worker进程。
PHP-FPM从初始化启动到Worker请求处理大概涉及以下步骤
fpm_init()
Master读取php-fpm.conf文件初始化内存配置变量、创建管道、套接字、启动事件管理fpm_run()
Master进程fork出子进程后阻塞,Worker进程去accept请求,执行php脚本
等待请求
Worker进程阻塞在fagi_accept_request()
等待请求解析请求
fastcgi请求到达后被worker接收,然后开始接收并解析请求数据,直到request数据完全到达请求初始化
执行php_request_startup()
,此阶段会调用每个扩展的PHP_RINIT_FUNCTION()
;编译,执行
由php_execute_script()
完成PHP脚本的编译、执行关闭请求
请求完成后执行php_request_shutdown()
,此阶段会调用每个扩展的PHP_RSHUTDOWN_FUNCTION()
,然后进入步骤1等待下一个请求。生成的语法树和opcode,同一个PHP脚本每次运行的结果都是一样的,在PHP-FPM模式下,每次请求都要处理一遍,是对系统资源极大的浪费
Laravel是fpm社区里面非常受欢迎的一款web框架,结合composer管理开发组件,可以帮助开发者在框架提供的多种优秀组件(ORM
,Router
,Middleware
,Artisan
,....)和解决方案在PSR-4规范下高效的进行Web服务端的开发。
所以有人说,如果Wordpress让PHP焕发了第一春,那么Composer让PHP焕发了第二春。
虽然laravel框架开发起来简单高效,中文社区也非常活跃,生态丰富。但是用laravel开发的应用性能问题却一直被反复提及。造成Laravel性能问题的主要原因有以下几点
所以针对Laravel进行性能优化的方向也就是针对以上几点来进行的:
PHP-OPcache
扩展缓存。php artisan route:cache
,php artisan config:cache
等缓存命令。composer install --optimize-autoloader --no-dev
初始化项目依赖,以便加速Composer
定位指定类对应的加载文件,同时不安装开发环境使用的依赖。Swoole 是一个使用 C++ 语言编写的基于异步事件驱动和协程的并行网络通信引擎,为 PHP 提供协程、高性能网络编程支持。提供了多种通信协议的网络服务器和客户端模块,可以方便快速的实现 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等,使 PHP 不再局限于传统的 Web 领域。
Swoole的出现可以打开上述FPM所面临的局限
TCP
、UDP
、HTTP
、WebSocket
、MQTT
SwooleTable
、进程间无锁计数器Atomic
、进程间API等方式保证有状态服务进程间的同步SWOOLE_PROCESS 模式的 Server 所有客户端的 TCP 连接都是和主进程建立的,内部实现比较复杂,用了大量的进程间通信、进程管理机制。适合业务逻辑非常复杂的场景。Swoole 提供了完善的进程管理、内存保护机制。 在业务逻辑非常复杂的情况下,也可以长期稳定运行。
优点:
缺点:
worker
进程需要使用 unixSocket
进行通信SWOOLE_BASE 这种模式就是传统的异步非阻塞 Server。与 Nginx 和 Node.js 等程序是完全一致的。
BASE 模式下没有 Master 进程的角色,只有 Manager 进程的角色。 每个 Worker 进程同时承担了 SWOOLE_PROCESS 模式下 Reactor 线程和 Worker 进程两部分职责。
优点:
缺点:
Hyperf 的出现是为了解决Swoole门槛高,上手难的问题。结合Composer组件,帮助使用者以低成本打造一套高性能的灵活的应用服务。
Mysql
、Redis
、GuzzleHttp
)Hyperf/DI 是框架中管理对象类创建和依赖关系的组件,也是实现 注解 和 AOP 功能的关键。 DI中管理的对象都是用于服务长生命周期的单例对象,短生命周期使用make()方法创建。 依据创建对象的需求不同可分为以下三种注入方式
Hyperf基于AOP和注解实现了诸多框架基础功能,如
@Inject()
@AutoController()
@Middleware(FooMiddleware::class)
@Cacheable(prefix="test", ttl=9000, listener="test-update")
Hyperf的事件机制存在于框架启动时,能够帮助用户更好的和框架配合完成逻辑的解耦,例如DBqueryLisenner
。
在处理每个连接时,会默认创建一个协程去处理,主要体现在 onRequest
、onReceive
、onConnect
事件,所以可以理解为每个请求都是一个协程,由于创建协程也是个常规操作,所以一个请求协程里面可能会包含很多个协程,同一个进程内协程之间是内存共享的,但调度顺序是非顺序的,且协程间本质上是相互独立的没有父子关系,所以对每个协程的状态处理都需要通过协程上下文
来管理。
这次分享不是不是为了比较一个高低,不是说FPM模式下的开发方式和项目就不好,而是想表达fpm可能已经不适合当下追求并发,尽可能榨取服务器CPU性能的要求了。Swoole在php原生语法简单高效的基础上,为php搭建了基于协程(异步非阻塞)的运行模式,再加上Hyperf这样组件化的成熟框架,使得上手Swoole不在困难。让大家了解到这种开发方式的可能性就可以了。
use Swoole\Coroutine\Http\Server;
use function Swoole\Coroutine\run;
run(function() {
$server = new Server('127.0.0.1', 9502, false);
$server->handle('/test', function($request, $response) {
Co::sleep(1);
$response->end("test");
});
$server->handle('/stop', function($request, $response) use ($server) {
$response->end("<h1>Stop</h1>");
$server->shutdown();
});
$server->start();
});
use Swoole\Process;
use Swoole\Coroutine;
use Swoole\Coroutine\Server\Connection;
//多进程管理模块
$pool = new Process\Pool(2);
//让每个 OnWorkerStart 回调都自动创建一个协程
$pool->set(['enable_coroutine' => true]);
$pool->on('workerStart', function($pool, $id) {
//每个进程都监听 9501 端口
$server = new Swoole\Coroutine\Server('127.0.0.1', 9501, false, true);
//收到 15 信号关闭服务
Process::signal(SIGTERM, function() use ($server) {
$server->shutdown();
});
//接收到新的连接请求 并自动创建一个协程
$server->handle(function (Connection $conn) {
while(true) {
//接收数据
$data = $conn->recv(10);
if ($data === '' || $data === false) {
$errCode = swoole_last_error();
$errMsg = socket_strerror($errCode);
echo "ErrCode: {$errCode}, ErrMsg: {$errMsg}\n";
$conn->close();
break();
}
//发送数据
$conn->send('hello');
Coroutine::sleep(1);
}
});
//开始监听端口
$server->start();
});
$pool->start();
$n = 4;
for ($i = 0; $i < $n; $i++) {
go(function() use($i) {
//模拟 IO 等待
Co::sleep(1);
echo microtime(true) . ": hello $i " . PHP_EOL;
});
};
echo "hello main \n";
\Swoole\Event::wait();