前言

本文是 Yar 源码系列的第一篇文章,主要介绍 Yar 以及服务端、客户端的基本使用,详细的源码分析会放在后续的文章中。

为什么要研究 Yar?

我从 8 月初开始阅读 《PHP 7底层设计与源码实现》这本书,直到前一阵子才看完,算是通读了一遍。看完之后总想着动手实操一番,将书中的理论知识赋予实践,做到理论实践相结合。

在挑选研究项目的时候,有以下几个可选项:

Swoole/Swow 是 PHP 高性能协程的网络通信引擎,从语言上来讲,前者由 C++ 编写,后者主要由 C 语言编写,一部分逻辑用 PHP 实现。从编码风格、代码实现来说,我更喜欢后者,虽然公司主要用的 Swoole 框架…

没有选 Swoole/Swow 的原因是,这两个项目包含了网络编程、并发编程、多进程/线程、进程间通信等多项技术,它们支持的功能特性也非常多,讲清楚这些功能的实现也需要花费不少的功夫,不符合我们小而美的目标。

所以在权衡了一阵之后,最终选择了 Yar。

Yar 介绍

Yar(yet another RPC framework,)是鸟哥(laruence)在 2012 年开发的一个轻量级的并行 RPC 框架,支持多种编码方式(JSON、msgpack、PHP)及 HTTP、TCP 两种数据传输方式,最重要的是支持并行调用,可以让多个数据源并行处理,从而提高系统的性能。

Yar 服务端

下面是一个 Yar 服务端的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class User
{
/**
* 用户登录
* @param string $username 用户名
* @param string $password 密码
* @return string
*/
public function login(string $username, string $password): string
{
return 'success';
}
}

$server = new Yar_Server(new User());
$server->handle();

在上面的例子中,定义了一个 User 类,并简单地实现了一个 login 方法,然后实例化 User 类并传入到 Yar_Server 的构造函数中,随后调用 Server 中的 handle 方法,处理即将到来的请求。

在 handle 方法中,通过判断请求方式执行不同的操作。

当请求方式为 POST 时,将会执行 RPC 远程服务 的逻辑:对请求数据进行解析、校验,拿到被调用的方法名称及参数后,执行该方法并响应结果。

当请求方式为非 POST 方式时,输出 RPC 服务的 API 信息。如果在 php.ini 中设置了 yar.expose_info = 0,表示不公开信息,将会抛出 “不允许访问服务信息” 的异常。

将上述的例子保存为 server.php,然后执行 php -S 127.0.0.1:3000 启动 PHP 自带的 Web 服务。

在浏览器中输入 http://127.0.0.1:3000 ,打开服务端 API 信息页面:

从页面中我们可以看到这个 RPC 服务支持的方法列表及方法名称、参数、注释信息。

Yar 客户端

Yar 客户端支持同步调用、并行调用两种方式。

  • 同步调用也就是串行调用,必须前一个请求执行完才能执行下一个请求,不能充分的利用系统资源。
  • 并行调用顾名思义就是能够同时执行多个请求,不需要等待某个请求结束再执行,请求完成后会运行指定的回调函数。

当然,客户端还支持一些其它的特性,例如:请求持久化、自定义 DNS、HTTP 代理等,这些特性的使用方法可以在官方文档中找到,这里就不赘述了。

同步调用

Yar 的同步调用非常简单,就像调用本地方法一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实例化客户端
$client = new Yar_Client("http://127.0.0.1:3000/server.php");

// 设置超时时间
$client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, 1000);

// 调用 login 方法
$result = $client->login("her-cat", "123456");

var_dump($result);


// 输出:string(7) "success"

并行调用

并行调用的使用方式跟同步调用有一点点区别,需要使用 call 方法,并且传入回调函数,当请求完成时就会运行回调函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function callback($ret, $callInfo) {
var_dump($ret, $callInfo);
}

function error_callback($type, $error, $callInfo) {
error_log($error);
}

$url = 'http://127.0.0.1:3000/server.php';

Yar_Concurrent_Client::call($url, 'login', ['her-cat', '123456'], 'callback', 'error_callback');
Yar_Concurrent_Client::call($url, 'login', ['her-cat', '123456'], 'callback', 'error_callback');
Yar_Concurrent_Client::call($url, 'login', ['her-cat', '123456'], 'callback', 'error_callback', [YAR_OPT_TIMEOUT => 1]);

Yar_Concurrent_Client::loop();

可以看到传入的参数除了 callback 以外,还传入了 error_callback 回调函数,当服务端执行方法的过程中发生了错误,就会运行 error_callback 回调函数。

总结

到这里本文就结束了,后续就会开始从源码的角度去分析这些功能是如何实现的。

Goodbye~