Yar的安装及使用

目录
  1. 1. 安装
    1. 1.1. 安装Yar扩展
    2. 1.2. 安装 yar
    3. 1.3. PHP扩展配置
    4. 1.4. 添加扩展配置文件
    5. 1.5. 添加软链接
  2. 2. 使用
  3. 3. 示例
  4. 4. thinkphp使用yar
    1. 4.1. thinkphp5.0版本
    2. 4.2. thinphp5.1 框架已经移除yar,需自己添加”)thinphp5.1 框架已经移除yar,需自己添加
  5. 5. 遇到的问题
    1. 5.1. php版本一定要 7.0以上的
    2. 5.2. PHP Fatal error: Yar_Client::__call(): unsupported packager msgpack
    3. 5.3. 报错: (php_msgpack_unserialize) Extra bytes in xxx
    4. 5.4. 超时问题
    5. 5.5. 多次请求问题
  6. 6. 扩展知识
    1. 6.1. yar运行时的默认配置
    2. 6.2. yar常量
    3. 6.3. 基本方法

Yar 是一个轻量级, 高效的RPC框架, 它提供了一种简单方法来让PHP项目之间可以互相远程调用对方的本地方法. 并且Yar也提供了并行调用的能力. 可以支持同时调用多个远程服务的方法.

安装

安装Yar扩展

确认 phpize 是否安装
$sudo find / -name phpize
//如果没有安装,可以通过开发版来安装该扩展
$sudo apt-get install php-dev
安装 msgpack
$sudo pecl install msgpack

安装成功,提示如下,要求配置php.ini,这里先放一下

configuration option “php_ini” is not set to php.ini location
You should add “extension=msgpack.so” to php.ini

安装 yar

1
2
$sudo pecl install yar
Enable Msgpack Supports [no] : //填yes or no

注意:如果填no,则默认的打包协议是php(serialize)。如果填yes,则默认的打包传输协议为msgpack。

错误提示如下:

configure: error: Please reinstall the libcurl distribution - easy.h should be in /include/curl/
ERROR: /tmp/pear/temp/yar/configure –enable-msgpack=yes failed

可以通过以下命令解决这个问题:
$sudo apt-get install libcurl4-gnutls-dev

注:在MacOS环境下,这个错绕不过去,brew reinstall curl不行,所以使用编译的方式安装,主要是指定curl的路径。具体步骤:
1.git clone https://github.com/laruence/yar.git
2.cd yar && phpize
3../configure --with-php-config=/usr/local/opt/php/bin/php-config --enable-msgpack=no --with-curl=/usr/local/opt/curl
4.make && make install

安装成功,最后提示:

configuration option “php_ini” is not set to php.ini location
You should add “extension=yar.so” to php.ini

浏览器访问127.0.0.1/test.php,查看php信息并没有看到msgpack和yar,需要进一步配置

PHP扩展配置

1
2
3
4
$sudo find / -name msgpack.so
/usr/lib/php/20190902/msgpack.so
$sudo find / -name yar.so
/usr/lib/php/20190902/yar.so

说明msgpack和yar都已经安装成功

1
2
cd /etc/php/7.4/mods-available
//可以看到扩展的配置文件

添加扩展配置文件

1
2
3
4
5
6
7
8
9
$sudo vi msgpack.ini
; configuration for php msgpack module
; priority=20
extension=msgpack.so

$sudo vi yar.ini
; configuration for php yar module
; priority=40
extension=yar.so

注:因为yar依赖于json和msgpack,所以yar的priority的值要大一点

添加软链接

1
2
cd /etc/php/7.4/fpm/conf.d
ls -all

可以看到该目录下都是软链接,在该目录下添加上面两个配置文件的软链接

1
2
$sudo ln -s ../../mods-available/msgpack.ini 20-msgpack.ini
$sudo ln -s ../../mods-available/yar.ini 40-yar.ini

重启Apache,浏览器访问127.0.0.1/test.php查看phpinfo,可以看到已有yar和msgpack,说明配置成功

使用

Server端示例(不需要常驻进程):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class API {
/**
* the doc info will be generated automatically into service info page.
* @params
* @return
*/
public function api($parameter, $option = "foo") {
}

protected function client_can_not_see() {
}
}

$service = new Yar_Server(new API());
$service->handle();
?>

Yar为了方便开发, 把文档和接口绑定到了一起, 对于上面的例子, 如果我们是简单的GET请求这个接口地址的话, 我们就会看到如下的信息页面:

1
2
Yar Server: API
+API::api($parameter, $option = 'foo')

Client端也很简单,有2种:
1)串行:

1
2
3
4
<?php
$client = new Yar_Client("http://host/api/");
$result = $client->api("parameter");
?>

2)并行化调用

1
2
3
4
5
6
7
8
9
10
11
<?php
function callback($retval, $callinfo) {
var_dump($retval);
}

Yar_Concurrent_Client::call("http://host/api/", "api", array("parameters"), "callback");
Yar_Concurrent_Client::call("http://host/api/", "api", array("parameters"), "callback");
Yar_Concurrent_Client::call("http://host/api/", "api", array("parameters"), "callback");
Yar_Concurrent_Client::call("http://host/api/", "api", array("parameters"), "callback");
Yar_Concurrent_Client::loop(); //send
?>

这样, 所有的请求会一次发出, 只要有任何一个请求完成, 回调函数”callback”就会被立即调用.

这里还有一个细节, Yar见缝插针的不会浪费任何时间, 在这些请求发送完成以后, Yar会调用一次callback, 和普通的请求返回回调不同, 这次的调用的$callinfo参数为空.

示例

server端:yar.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

class API {
/**
* the doc info will be generated automatically into service info page.
* @params
* @return
*/
public function test() {
sleep(1);
return 't';

}

public function test2() {
sleep(3);
return 'test2';
}

}

$service = new Yar_Server(new API());
$service->handle();

直接在浏览器打开http://localhost/yar.php会显示API文档。

client端:yar_client.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php

//串行调用
//$client = new Yar_Client("http://localhost/yar.php");
//$client->test();
//$client->test2();

function callback($retval, $callinfo) {
//var_dump($retval);

error_log(time().':callinfo:'.json_encode($callinfo).PHP_EOL, 3, 't.log');

if ($callinfo == NULL) {
//做本地的逻辑
//return TRUE;
error_log(time().':'.'send req success'.PHP_EOL, 3, 't.log');
}else{
error_log(time().':'.$retval.PHP_EOL, 3, 't.log');
}

}

function callback2($retval, $callinfo) {

}

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

//并行调用:
//1、所有请求发送成功,Yar会调用一次callback,其中$callinfo为null
//2、每个请求执行完成,获取到了结果,也会去调用callback,其中$callinfo不为null
$res = Yar_Concurrent_Client::call("http://localhost/yar.php", "test");
$res1 = Yar_Concurrent_Client::call("http://localhost/yar.php", "test2");
$res2 = Yar_Concurrent_Client::loop("callback", "error_callback"); //send

t.log:

1
2
3
4
5
6
1472832899:callinfo:null
1472832899:send req success
1472832900:callinfo:{"sequence":1,"uri":"http:\/\/localhost\/yar.php","method":"test"}
1472832900:t
1472832902:callinfo:{"sequence":2,"uri":"http:\/\/localhost\/yar.php","method":"test2"}
1472832902:test2

log验证了yar的执行过程。那么,实际应用中,我们就可以先发送请求, 请求发送完毕,然后得到第一次回调($callinfo为null), 继续做我们当前进程的工作; 等所有工作结束以后, 再交给Yar去获取并行RPC的响应:

1
2
3
4
5
6
7
8
9
10
<?php
function callback($retval, $callinfo) {
if ($callinfo == NULL) {
//请求发送完毕,会运行到这里
//做本地的逻辑
return TRUE;
}

//RPC请求返回, callback会再次调用。返回值在$retval
}

实际项目里,Server端里为避免每次实例化当前类,可以写个父类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

/**
*Yar控制器类
*/
class YarAction{

/**
* 架构函数
* @access public
*/
public function __construct() {

//判断扩展是否存在
if(!extension_loaded('yar'))
die('yar not support');
//实例化Yar_Server
$server = new Yar_Server($this);
// 启动server
$server->handle();
}
}

thinkphp使用yar

thinkphp5.0版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Server.php

namespace app\index\controller;
use think\controller\Yar;

class Server extends Yar{
public function index(){
return 'hello yar';
}
public function test($age=null){ //传参数 要有默认值
return 'hello yar';
}
}

Client.php

namespace app\index\controller;
use think\controller\Yar;

class Client
{
public function index()
{
$client = new \Yar_Client('http://192.168.113.136:81/server');
$client->SetOpt(YAR_OPT_PACKAGER,'php'); //注意tp5只能用php msgpack以及json报错
echo $client->index();
}
}

thinphp5.1 框架已经移除yar,需自己添加”)thinphp5.1 框架已经移除yar,需自己添加

添加下面代码,然后在引入即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
abstract class Yar
{
/**
* 构造函数
* @access public
*/
public function __construct()
{
//控制器初始化
if (method_exists($this, '_initialize')) {
$this->_initialize();
}

//判断扩展是否存在
if (!extension_loaded('yar')) {
throw new \Exception('not support yar');
}

//实例化Yar_Server
$server = new \Yar_Server($this);
// 启动server
$server->handle();
}

/**
* 魔术方法 有不存在的操作的时候执行
* @access public
* @param string $method 方法名
* @param array $args 参数
* @return mixed
*/
public function __call($method, $args)
{}
}

遇到的问题

php版本一定要 7.0以上的

PHP Fatal error: Yar_Client::__call(): unsupported packager msgpack

这个问题应该是编译 ar的时候没有加 --enable-msgpack

报错: (php_msgpack_unserialize) Extra bytes in xxx

这个在tp5/tp6使用,就是tp5/tp6打包协议不能用msgpack和json只能选择php(这个问题暂时还没有解决,应该是tp5/tp6框架问题),只是一个warning,不影响使用。

超时问题

yar RPC请求默认超时设置为 5s,因此在客户端任务请求时,若果5s内没有得到响应,请求会持续5s

修改超时时间有两种方式 (时间单位:毫秒)
1.采用ini_set()方法实现对php.ini的动态修改
ini_set("yar.timeout",60000);
2.在yar注册服务方法种进行修改
Yar_Concurrent_Client::call(“http://localhost“, “some_method”, array(“parameters”), “callback”, NULL, array(YAR_OPT_TIMEOUT=>1));
自己测试超时时间设置为低于200毫秒时,有一定机率出现请求失败。

多次请求问题

在用到多次并发请求时,后续请求的结果会包含上一次请求的数据
调用Yar_Concurrent_Client::Reset()清空第一次的请求结果,要求yar版本>=1.2.4

扩展知识

yar运行时的默认配置

1
2
3
4
5
6
7
yar.timeout //默认5000(ms)
yar.connect_timeout //默认1000(ms)
yar.packager //默认“php”,当使用--enable-msgpack构建然后默认为“msgpack”时,它应该是“php”,“json”,“msgpack”之一
yar.debug //默认关闭
yar.expose_info //默认开,是否输出GET请求的API信息
yar.content_type //默认“application / octet-stream”
yar.allow_persistent //默认关闭

注意: yar.connect_time是一个以毫秒为单位的值,在1.2.1及之前以秒为单位进行测量。

yar常量

1
2
3
4
5
6
YAR_VERSION
YAR_OPT_PACKAGER
YAR_OPT_PERSISTENT
YAR_OPT_TIMEOUT
YAR_OPT_CONNECT_TIMEOUT
YAR_OPT_HEADER //从2.0.4开始

基本方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Yar_Server //The Yar_Server class
Yar_Server::__construct() //创建一个HTTP RPC Server
Yar_Server::handle() //启动HTTP RPC Server

Yar_Client //The Yar_Client class
Yar_Client::__call() //调用远程服务
Yar_Client::__construct() //创建一个客户端实例
Yar_Client::setOpt() //设置调用的配置

eg:
//Set timeout to 1s
$client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, 1000);

//Set packager to JSON
$client->SetOpt(YAR_OPT_PACKAGER, "json");

Yar_Concurrent_Client //The Yar_Concurrent_Client class
Yar_Concurrent_Client::call() //注册一个并行的服务调用
Yar_Concurrent_Client::loop() //发送所有注册的并行调用
Yar_Concurrent_Client::reset() //Clean all registered calls

eg:
Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback");

// if the callback is not specificed, callback in loop will be used
Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"));

//this server accept json packager
Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_PACKAGER => "json"));

//custom timeout
Yar_Concurrent_Client::call("http://host/api/", "some_method", array("parameters"), "callback", NULL, array(YAR_OPT_TIMEOUT=>1));

//send the requests, the error_callback is optional
Yar_Concurrent_Client::loop("callback", "error_callback");

Yar_Server_Exception //The Yar_Server_Exception class
Yar_Server_Exception::getType() //获取异常的原始类型

Yar_Client_Exception //The Yar_Client_Exception class
Yar_Client_Exception::getType() //The getType purpose

具体使用方法可以参考这篇文章点击查看