前面需要了解的两个概念
什么是RPC
RPC (Remote Procedure Call)是一个计算机通信协议,该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。RPC是一个分布式计算的CS模式,总是由Client向Server发出一个执行若干过程请求,Server接受请求,使用客户端提供的参数,计算完成之后将结果返回给客户端。RPC的协议有很多,比如最早的CORBA,Java RMI,Web Service的RPC风格,Hessian,Thrift,甚至Rest API。
什么是 Yar
是一个轻量级的框架, 支持多种打包协议(msgpack, json, php), 并且最重要的一个特点是, 它是可并行化执行
使用 PRC 的场景举例
简单概述下我当前RPC 使用的场景,给客户端提供的 api 逻辑保持不变,但是后端业务策略改动频繁。可能这句话表达的太过于抽象,为了说的更加具体,所以允许我唠叨下工作的需求:
当用户的 feed 收到点赞时:收到1个赞时,发放10金币;收到5个赞时,发放10金币;收到10个赞时,发放10金币;从10~50个赞每多10个赞,发放10金币;50个赞以后每多50个赞,发放10金币。
当用户评论时,每天的第一次评论送2金币,第二次评论送5金币;
当用户添加 feed 时,每天的第一次添加图片 feed 送5金币,添加视频 feed 送10金币;
当用户 feed 被系统推荐时,送20金币。
如果将这一些运营业务都写在 api 里面,是不是显得很乱呢?一方面导致代码越堆越多,逻辑越来越乱;另一方面,每一次的改动都会涉及 api 的频繁上线,提心吊胆。
我们可以明显的可以看出,这一套运营部门激励用户的需求都是和金币直接相关的,应该解耦出来单独成为了一个金币服务。
揭开 Yar 神秘面纱
RPC 采用客户端/服务器模式。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
这和我们外网 api 的原理不都一个样么?那么我们一起看看高大上的 Yar 是怎么在玩。
Yar 功能演示
客户端代码,假设该服务设在局域网10.211.55.4
上
<?php class RpcClient { // RPC 服务地址映射表 public static $rpcConfig = array( "RewardScoreService" => "http://10.211.55.4/yar/server/RewardScoreService.class.php", ); public static function init($server){ if (array_key_exists($server, self::$rpcConfig)) { $uri = self::$rpcConfig[$server]; return new Yar_Client($uri); } } } $RewardScoreService = RpcClient::init("RewardScoreService"); var_dump($RewardScoreService->support(1, 2));
服务器端代码
<?php class RewardScoreService { /** * $uid 给 $feedId 点赞 * @param $feedId interge * @param $uid interge * @return void */ public function support($uid,$feedId){ return "uid = ".$uid.", feedId = ".$feedId; } } $yar_server = new Yar_server(new RewardScoreService()); $yar_server->handle();
访问结果如下
uid = 1, feedId = 2
Yar 远程调用的实现原理与 Java 版的实现
实际呢,yar client 是通过__call
这个魔术方法来实现远程调用的,在Yar_client
类里面并没有任何方法,当我们在调用一个不存在的方式的时候,就会执行__call
方法,这个在框架中非常常见。
下面要实现 yar java client 的话,也要实现类似的功能,由于 java 是静态编译的,不存在类似于 php 里的__call
方法的方式来实现远程调用。
我们知道在 Spring 的一大特性就是 AOP。百度百科的解释,面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。一直不太理解,今天通过解决这个问题有所领悟。AOP 其核心就是动态代理的实现,也就是完成在 Java 里实现 PHP 里的__call
功能。
为了方便读者参考和编译,我整理精简了下代码,放入这一个文件,如下:
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Created by zhoumengkang on 5/12/15. */ /** * 点赞的积分服务接口 */ interface RewardScoreService{ String support(int uid,int feedId); } public class SupportService { public static void main(String[] args) { add(1,2); } /** * uid 给 feedId 点赞 * @param uid * @param feedId * @return */ public static String add(int uid, int feedId){ YarClient yarClient = new YarClient(); RewardScoreService rewardScoreService = (RewardScoreService) yarClient.proxy(RewardScoreService.class); return rewardScoreService.support(uid, feedId); } } class YarClient { public final Object proxy(Class type) { YarClientInvocationHandler handler = new YarClientInvocationHandler(); return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler); } } final class YarClientInvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("这里的动态调用实现了 php 的 __call 方法"); System.out.println("method : " + method.getName()); for (int i = 0; i < args.length; i++) { System.out.println("args["+ i +"] : " + args[i]); } return null; } }
编译执行,结果如下:
这里的动态调用实现了 php 的 __call 方法 method : support args[0] : 1 args[1] : 2
这样就实现了 java 的动态代理,而实际上面的RewardScoreService
的support
的方法并没有被实现,但我们却实际调用了该support
方法,在 RPC 中,该方法就是在本地不存在,通过在动态代理绑定的handler
里面的invoke
方法里通过 method 的值和各个参数的值,做网络请求,把请求的结果拿回到本地使用。
Yar 协议的学习和 Java 版本的实现
根据 yar 的协议实际可以通过下面的方式来模拟 yar 客户端
在 yar 中规定的传输协议如下图所示,请求体为82个字节的yar_header_t
和8字节的打包名称和请求实体yar_request_t
,在yar_header_t
里面用body_len
记录8字节的打包名称+请求实体的长度;返回体类似,只是实体内容的结构体稍微不同,在reval
里面才是实际最后客户端需要的结果。
整个传输以二进制流的形式传送。
由于自己学习的篇幅比较长,所以选择重新开篇再具体记录中间遇到的问题和解决的办法
Java 和 C 中字符串的一点不同 | http://mengkang.net/589.html |
Java 和 C 中 Char 的区别 | http://mengkang.net/591.html |
Java YarPackager 工厂和单例的实现 | http://mengkang.net/590.html |
传输协议的学习与具体实现(较多) | http://mengkang.net/592.html |
Yar 传输源码的学习
根据鸟哥的代码整理的图,自己瞎画的,原来C也可以像面向对象那样写,不能局限为过程化代码。
最后我经过两周断断续续的学习和折腾终于实现了一个简陋版本的 YarJavaClient
https://github.com/zhoumengkang/notes/tree/1.0.0/java/yartest
https://github.com/zhoumengkang/notes/tree/1.0.1/java/yartest
添加并行请求的支持
https://github.com/zhoumengkang/notes/tree/1.0.2/java/yartest
wireshark截图:https://i.loli.net/2018/04/27/5ae2929186d5e.png
但是用wireshark抓包的时候yar_header里大部分都是00,但是之后的Packagers_name的MSGPACK又有,就很迷。