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 yarEnable Msgpack Supports [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/m ods-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 ../../m ods-available/msgpack.ini 20 -msgpack.ini$sudo ln -s ../../m ods-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 { 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 (); ?>
这样, 所有的请求会一次发出, 只要有任何一个请求完成, 回调函数”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 { 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 function callback ($retval , $callinfo ) { error_log (time ().':callinfo:' .json_encode ($callinfo ).PHP_EOL, 3 , 't.log' ); if ($callinfo == NULL ) { 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 ); } $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" );
t.log:
1 2 3 4 5 6 1472832899 :callinfo :null 1472832899 :send req success1472832900 :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 ; } }
实际项目里,Server端里为避免每次实例化当前类,可以写个父类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php class YarAction { public function __construct ( ) { if (!extension_loaded ('yar' )) die ('yar not support' ); $server = new Yar_Server ($this ); $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' ); 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 { public function __construct ( ) { if (method_exists ($this , '_initialize' )) { $this ->_initialize (); } if (!extension_loaded ('yar' )) { throw new \Exception ('not support yar' ); } $server = new \Yar_Server ($this ); $server ->handle (); } public function __call ($method , $args ) {}}
遇到的问题 php版本一定要 7.0以上的 PHP Fatal error: Yar_Client::__call()
: unsupported packager msgpack 这个问题应该是编译 ar的时候没有加 --enable-msgpack
这个在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 Yar_Server ::__construct () Yar_Server ::handle () Yar_Client Yar_Client ::__call () Yar_Client ::__construct () Yar_Client ::setOpt () eg: $client ->SetOpt (YAR_OPT_CONNECT_TIMEOUT, 1000 );$client ->SetOpt (YAR_OPT_PACKAGER, "json" );Yar_Concurrent_Client Yar_Concurrent_Client ::call () Yar_Concurrent_Client ::loop () Yar_Concurrent_Client ::reset () eg: Yar_Concurrent_Client ::call ("http://host/api/" , "some_method" , array ("parameters" ), "callback" );Yar_Concurrent_Client ::call ("http://host/api/" , "some_method" , array ("parameters" ));Yar_Concurrent_Client ::call ("http://host/api/" , "some_method" , array ("parameters" ), "callback" , NULL , array (YAR_OPT_PACKAGER => "json" ));Yar_Concurrent_Client ::call ("http://host/api/" , "some_method" , array ("parameters" ), "callback" , NULL , array (YAR_OPT_TIMEOUT=>1 ));Yar_Concurrent_Client ::loop ("callback" , "error_callback" );Yar_Server_Exception Yar_Server_Exception ::getType () Yar_Client_Exception Yar_Client_Exception ::getType ()
具体使用方法可以参考这篇文章点击查看