欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Swoole 是 PHP 中的 Node.js?

程序员文章站 2022-03-21 17:32:37
一想到那些可以使用 Node 的同事,一些 PHP 开发者的脸都嫉妒绿了。异步 Node 系统可以在不同协议间共享代码库,并在代码之外提供服务。这真的想让一个人转 Node 开发。实际上 PHP 中也有类似于 Node 的存在,并被列入了 PHP 拓展,叫做 Swoole。 PHP 中的 Node ......

一想到那些可以使用 node 的同事,一些 php 开发者的脸都嫉妒绿了。异步 node 系统可以在不同协议间共享代码库,并在代码之外提供服务。这真的想让一个人转 node 开发。实际上 php 中也有类似于 node 的存在,并被列入了 php 拓展,叫做 swoole。

 

php 中的 node ?swoole 到底是什么?

我先从 官方文档 中引用下 swoole 的定义:

swoole:面向生产环境的 php 异步网络通信引擎。
使 php 开发人员可以编写高性能、可拓展的异步并发 tcp、udp、unix socket、http,websocket 服务,而无需深入了解非阻塞 i/o 编程和初级 linux 内核。

swoole 使用 c 语言编写,作为 php 的 基本扩展 存在。听起来可还行,是吧?用 php 来运行 http 服务?用 php 实现 websockets ?还有其他的可能性,是不是很风骚?而且所有的这些都会保持极高的性能,我们来看看吧!

 

如何让它运行?

不同平台的安装方法有差异。

对于 linux 来说,只需要运行一条 pecl 命令:

pecl install swoole

macos 用户可以使用 brew 命令:

brew install swoole
brew install homebrew/php/php72-swoole
译者注:截止翻译时,brew 官方已经移除了所有 php 扩展,请使用 pecl 安装。

暂时不支持在 windows 上的安装,但是可以使用 docker 的方式。

 

使用 docker 运行 swoole

毫无疑问,运行 php + swoole 的最佳方案便是 docker。让我们来看看如何创建一个包含 swoole 的容器。首先,我们需要创建一个 dockerfile。

from php:latest\
run pecl install swoole\
add php.ini /usr/local/etc/php\
run usermod -u 1000 www-data

这看起来十分直接。基于 php 官方 docker 镜像,使用 pecl 安装 swoole,接着复制 php.ini 到镜像内 —— 搞定。最后一行是 macos 的 docker 一个常规的权限修复命令。

至于被复制的 php.ini 配置文件,它只需一行:

extension=swoole.so

 

swoole 可以做什么?

swoole 有许多功能,大部分是异步执行。以下是其中最让人感兴趣的部分(其他的可以在 swoole 官方文档 中找到):

  • tcp/udp 服务端与客户端,
  • http 服务端与客户端,
  • websocket 服务端与客户端,
  • 基于 redis 协议的服务端与客户端,
  • mysql 客户端,
  • 原子性,
  • 文件系统。

我们来看下其中的 http 服务、websocket 服务、文件系统怎么使用。在我看来这是最重要的几个功能。

 

基于 swoole 实现 http 服务

基于 swoole 仅需少量代码即可实现一个简易的异步 http 服务。以下是一份示例代码,该例子使用异步文件系统来读取 index.html 文件并作为响应返回给它处理的每条请求。

<?php
chdir(__dir__);
$http = new swoole_http_server('php', 8080);
$http->on('start', function ($server) {
    echo "server has been started!\n";
});
$http->on('request', function ($request, $response) {
    swoole_async_readfile('index.html', function($filename, $content) use ($response) {
        $response->header('content-type', 'text/html');
        $response->end($content);
    });
});
$http->start();

 

如你所见,这段代码看起来有点像 node.js 的风格。

首先,我们创建一个类似 http 服务的 swoole_http_server 对象。接着,绑定两个异步回调函数到以下事件:一个用于启动,将会在服务启动时被调用;另一个用于请求,将会在收到每次请求时被调用,它带有 $request 和 $response 两个参数。

$request 对象包含了所有与请求相关的数据:请求路径(path)、头信息(headers)等等。而 $response 被用来提供输出、设置响应头等。值得一提的是,以上两个对象都不符合 psr 标准,而是 swoole 自定义的。
在请求事件中,异步请求文件系统用于从文件加载数据。 一旦数据可用,就会在数据加载完成后触发回调。然后将此数据加载到响应体并关闭比此次响应。 这将会把数据有效地发送回浏览器。

这样看起来很简洁,最重要的是 --- 能运行起来。 来看下它的性能如何呢?

 

http server 标准

为了使用 swoole 测试 http 服务器的性能,我在 node 中创建了一个应用程序 --- 它可以与 swoole 中的应用程序完全相同 - 还有一个 服务器,它将提供 index.html 作为静态文件。 全部运行在 3 个独立的容器中。

然后,我用 wrk 工具给这些容器进行压力测试。 结果令人震惊。

 

 

Swoole 是 PHP 中的 Node.js?

 

 

swoole 的工作性能要比预期的好很多!

这令人惊讶。 我没想到 swoole 会超越 nginx ,但它确实做到了!这也远远超过了 node 。 这个扩展的原始功能确实令人印象深刻,但它在请求中完成了更多工作后逐渐消失。 不幸的是, swoole 有两个小缺点,使这些缺点和原始标准有些偏差。 我们稍后会找到他们。

 

在 websocket 服务中使用 swoole

如前所述, swoole 提供了一种创建 websocket 服务器的方法。 它以异步方式来进行工作,遵循与 http 协议并和 swoole 部分方法功能相同。 在我看来,它是最重要的 swoole 组件之一。 来吧,在 php 运行中的 websockets 会是怎么样。让我们看看它的结果。

<?php
$server = new swoole_websocket_server('php', 9501);
$server->on('start', function (swoole_websocket_server $server) {
    echo "server has been started!\n";
});
$server->on('open', function (swoole_websocket_server $server, $request) {
    echo "websocket: new connection, id: {$request->fd}\n";
});
$server->on('message', function (swoole_websocket_server $server, $frame) {
    echo "websocket: {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "replying, you sent " . $frame->data);
});
$server->on('close', function (swoole_websocket_server $server, $fd) {
    echo "websocket: connection with id {$fd} has been closed\n";
});
$server->start();

 

看起来类似于 http 服务器的示例。

首先,我们创建类似于 websocket 服务器的 swoole_websocket_server 对象。 然后,我们将 4 个匿名函数绑定到 4 个事件。 第一个启动事件,它将像 http 服务器的启动事件一样工作。 第二个运行事件,它会在连接另一个 websocket 后执行。 第三个消息事件将在 websocket 向服务器发送消息时执行。最后 --- 关闭时间会在 websocket 断开连接时执行。

id 是作为 websocket 连接到服务器的唯一标识,该 id 随每个新的 websockets 进行递增。

 

使用 swoole 时遇到的问题

到目前为止,这一切都运行良好,但在使用 swoole 测试某些解决方案时遇到了两个问题。 我将它列出来:

  • http 服务器中没有真正的支持 https,
  • 脚本中不支持全局变量。

第一问题个很容易解决。 我们只需要使用 nginx 或任何负载均衡设备设置反向代理,就完成了。 但通过这样做,我们就失去了 swoole 提供的极端性能。

第二个问题更棘手。 swoole 生成用于处理 http 请求的工作进程,这意味着如果我们创建一个全局变量,它的值在线程之间是独立的,并且它不能工作。下面这段代码是显示问题所在之处。

<?php
$server = new swoole_websocket_server('php', 9501);
$server->on('start', function (swoole_websocket_server $server) {
    echo "server has been started!\n";
});
$server->on('open', function (swoole_websocket_server $server, $request) {
    echo "websocket: new connection, id: {$request->fd}\n";
});
$server->on('message', function (swoole_websocket_server $server, $frame) {
    echo "websocket: {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
    $server->push($frame->fd, "replying, you sent " . $frame->data);
});
$server->on('close', function (swoole_websocket_server $server, $fd) {
    echo "websocket: connection with id {$fd} has been closed\n";
});
$server->start();

 

预期中响应的信息将返回 0 ,然后返回 1, 2 , 3 等等,但它总是返回 0 。

我找到了 swoole 的作者来检查它是否是一个 bug ,但事实并非如此。 为了获得我们期望的行为,我们可以在配置中设置 worker_num = 1 ,但这会降低部分性能。

 

结论

总的来说,swoole 有明亮的侧面也有黑暗的角落。我认为将异步编程引入 php 仍然是一个好主意。 它可用于各种情况,包括快速设计原型,简洁且责任单一的微服务,低延迟游戏服务器以及作为大型框架的后端服务器。 确实有前途。

 推荐阅读:

php 当swoole 遇上 thinkphp5