一张图片Go骑在pHP,GO+PHP, 让全宇宙最好的两种语言合体的神器——RoadRunner

数据说话

63fd8ac26c955b2e616340aa635a9470.png

从图中可以看出RoadRunner对比Nginx+FPM,运行效率是有数量级上的提升。

一般PHP服务器

传统CGI协议服务器

客户端访问某个URL地址之后,通过 GET/POST/PUT等方式提交数据,并通过HTTP协议向Web服务器发出请求,服务器端将HTTP请求里描述的信息通过标准输入(stdin)和环境变量(environment variable)传递给新建的CGI进程。处理完成后,进程立即关闭。

Nginx + PHP-FPM模式

现在流行的PHP web程序一般都是运行在Nginx + PHP-FPM模式下的。PHP-FPM就是PHP对FastCGI的实现。

master创建并监听多个worker进程,通过共享内存获取worker的状态,进而通过信号控制worker进程。每一个worker进程就类似一个CGI进程,收到CGI请求后会执行相应的PHP文件,并把请求内容作为PHP进程状态的一部分(_GET, _POST, _SERVER等等)。结束请求后,worker不会立刻结束,而是继续留在worker pool. 这就节省了频繁创建结束子进程的开支。

RoadRunner

为什么

现在很多PHP的企业级框架都要求你加载至少十几个文件,构造多个类并解析一些配置,以便处理简单的用户请求或查询数据库。每个任务完成后,你不得不抛弃这些代码。收到下一个HTTP请求时,PHP-FPM会创建一个新的PHP子进程来处理这个请求,所有的文件都要重新加载一遍,即便文件可以有缓存,所有的代码也要重新运行。

如果我们可以避免对每个请求都重启一次PHP子进程,我们就可以节约很多的资源。

基本原理

RoadRunner可以看作一个升级版的Nginx + PHP-FPM. 它直接把长时运行的PHP进程作为worker, 直接对PHP worker进行监控和维护,每次收到http请求时,就发给php worker来处理。这样,我们就不再需要对每个请求重启一遍PHP了。

224002ab80a85c28dfdde67246dca6b4.png

一些实现细节

整个项目都是开源的,RoadRunner的代码在这里。

RoaderRunner 通过 Socket/Pipe 上的二进制流完成和PHP子进程之间的通信。为此,他们创建了一个轻量的二进制协议,这个协议的包头长这样。

不同的通信方式,创建worker时就会有所区别。RoadRunner实现了两套factory和relay:

当使用pipe时,work是从pipe_factory.go创建的。PHP方面对应的Class是StreamRelay.php

其中读取包头的部分代码是这样的

$prefixBody = fread($this->in, 17);

if ($prefixBody === false) {

throw new Exceptions\PrefixException("unable to read prefix from the stream");

}

$result = unpack("Cflags/Psize/Jrevs", $prefixBody);

if (!is_array($result)) {

throw new Exceptions\PrefixException("invalid prefix");

}

if ($result['size'] != $result['revs']) {

throw new Exceptions\PrefixException("invalid prefix (checksum)");

当使用socket时,对应的PHP Class是SocketRelay.php, 代码是类似的。只不过PHP读取的部分从stdin/out变成了socket.

RoadRunner实现了StaticPool和Worker来对所有的PHP worker进行管理。本质上就是一个进程池。当需要PHP对数据进行处理时,StaticPool的allocateWorker()方法会从进程池的free worker中分配一个进程,把数据发送给这个PHP进程。StaticPool不会在PHP进程返回数据后关闭进程,而是把这个进程放回进程池。如果某个PHP进程意外关闭,staticPool会主动丢弃并在需要的时候创建新的进程。

举个栗子

下面是一个实际应用RoadRunner的例子。我们创建了一个最多拥有4个PHP进程的StaticPool,Golang会把0到9的数字依次传给PHP处理,PHP把数字加1并返回。

Golang部分:

//创建RoadRunner serversrv := roadrunner.NewServer(

&roadrunner.ServerConfig{

Command: "php " + phpScriptName,

Relay: "pipes", // 选择用pipe通信 Pool: &roadrunner.Config{

NumWorkers: 4, // worker的数量是4 AllocateTimeout: time.Second,

DestroyTimeout: time.Second,

},

})

defer srv.Stop()

err := srv.Start()

if err != nil {

panic(err)

}

for i := 0; i < 10; i++ {

// 把i作为payload传给PHP进程 res, err = srv.Exec(&roadrunner.Payload{Body: i})

if err != nil {

panic(err)

}

fmt.Print(res)

}

PHP部分

use Spiral\Goridge;

use Spiral\RoadRunner;

// 创建Worker实例,选择用pipe通信$rr = new RoadRunner\Worker(new Spiral\Goridge\StreamRelay(STDIN, STDOUT));

// 长时运行,阻塞式接收go传来的数据while ($i = $this->rr->receive($context)) {

try {

$i = $i + 1;

$rr->send($i, (string) $context);

} catch (\Throwable $e) {

$rr->error((string) $e);

}

}

为什么要go + php?

“有些人仍然坚持认为 PHP 是一种缓慢,笨重的语言,只能用来编写 WordPress 插件。他们甚至可能会说 PHP 有一个限制:一旦你的应用程序变得比较大,你就必须切换到更“成熟”的语言并取代之前的 PHP 代码。”

基本上php脚本也都可以用go来写,那为什么不直接用100%的Go来开发项目呢?

我会认为这种模式还是有一些存在的意义的:

开发效率高。尤其是PHP对html渲染有很好的支持,这又正是Go的痛点。

用工成本的考虑。让大牛用Go写出框架,每个模块再由php分别实现,也许是个好主意。(这也算PHP的优势吗-。-?)

PHP作为弱类型的脚本语言,用来实现一些小模块,真的很爽~!

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值