概述
RPC这个东西是什么? 第一次听说他, 还要在它的前边加个G, 当时我以为GRPC是一项技术, 后来才知道, 并不是这样. GRPC只是RPC的谷歌实现.
谷歌搜了一下, RPC就是一种: 远程函数调用, 看到这里, 我已经等不及了, 不往下看了, 先自己实现一个. 如果只给你这样一个概念, 如何实现调用远程函数的功能呢?
自己实现
自己尝试实现一个粗糙的PHP版本. (不想看可以跳过的)
思路
远程调用, 只需要解决下面问题:
- 通信问题
- 定义传输的数据格式
- 如何封装后可以达到像调用本地函数一样的效果
先来解决通信问题, 直接粗暴的tcp socket
传输的数据格式, 直接用json进行传输
调用本地函数?? 这就要借助一下PHP的魔术函数了, __call()
这个函数是一个类调用不存在的方法时会跑到这里来, 所以, 我们返回一个类, 在call
方法中进行远程调用, 这样, 在本地看来就只是在调用一个方法.
开始实现
PHP中进行socket连接十分简单, 直接调用系统函数. 通信问题解决了, 剩下的就是传输数据了, so easy
经过一番摸索, 看下结果
服务器内容:
<?php
class RpcServer{
private $port = 0; // 监听端口号
private $host = ''; // IP
public function __construct($host, $port){
$this->host = $host;
$this->port = $port;
}
/**
* 运行, 监听端口并处理
*/
public function run(){
// 创建socket
$server = stream_socket_server("tcp://{$this->host}:{$this->port}");
if(empty($server)) throw new Exception('创建套接字失败');
// 监听
while (true){
$client = stream_socket_accept($server);
if(empty($client)) continue;
// 处理请求
$this->disposeClient($client);
fclose($client);
}
}
private function disposeClient($client){
$buf = fread($client, 4096);
$array = json_decode($buf, true);
// 创建对象并调用方法
$class = $array['class'] ?? '';
$method = $array['method'] ?? '';
$params = $array['params'] ?? [];
$instance = new $class();
$result = $instance->$method(...$params);
fwrite($client, json_encode($result));
}
}
// 测试调用类
class Test{
public function tt(){
return 'return_tt';
}
public function add($a, $b){
return $a + $b;
}
}
(new RpcServer('127.0.0.1', 8888))
->run();
调用方:
<?php
class RpcClient{
private $urlInfo = null;
private $className = '';
private function __construct($url, $className){
$this->urlInfo = parse_url($url);
$this->className = $className;
}
public static function getInstance($className){
return new RpcClient('127.0.0.1:8888', $className);
}
public function __call($name, $arguments){
// 创建客户端
$client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}");
if(empty($client)) return null;
// 发送数据
fwrite($client, json_encode([
'class' => $this->className,
'method' => $name,
'params' => $arguments,
]));
// 接收返回
$data = fread($client, 4096);
// 关闭客户端
fclose($client);
return json_decode($data, true);
}
}
$test = RpcClient::getInstance('Test');
echo $test->tt(), PHP_EOL;
echo $test->add(4, 6);
结果:
嗯, 还阔以. 当然, 问题还是有很多的, 比如不能实现保存对象的修改状态等等.
其实对象可以通过序列化和反序列化来传输, 额, Java中, 不知道PHP有没有这种技术.
当然, 一个RPC中必然大量使用反射
、序列化
、动态加载
、代理
、网络请求
等等, 这只是一个超级超级粗糙的示例.
继续
nice, 自己做完了, 对RPC是个什么东西有了一个基本的概念.
WHAT
RPC是什么? 简单说, 就是远程函数调用. 字面意思, 很好理解.
WHY
看到一个技术, 一定会问的一个问题就是: 为什么? 一个技术基本不会平白无故出现, 都是为了解决某些问题, 那么RPC解决了什么问题呢? 字面含义: 远程函数调用
为什么要进行远程函数调用, 把函数拿过来本地调用不就好了? 还不用走网络IO, 速度更快一些. 很好, 现在假设, 你真的这样做了, 当项目变得庞大, 你想要进行拆分, 拆分后的有: 项目A, 项目B…, 这时, 你发现这些拆分的项目部分逻辑是重叠的, 比如用户信息相关, 怎么办? 如果不抽出来, 以后的维护成本会变得很高, 一处改处处改. 如果抽出来, 跨项目如何进行调用? 哎, 走过路过不要错过, RPC推荐给你.
HOW
那么如何实现RPC呢?
在刚才使用PHP简单实现中, 已经发现了. 需要解决的问题如下:
- 网络通信
- 信息格式
- 对象状态保存
1.网络通信
说到底, 网络通信不过两种: tcp udp.
有没有使用udp
实现的RPC呢? 貌似也有.
使用tcp
协议实现的RPC也有, 当然, 不光传输层协议, 也有直接通过应用层协议: http
、websocket
等等建立连接的. 当然, 如果需要频繁调用, 可以不断开tcp
连接, 在一段时间内一直保持连接, 避免频繁握手.
2.信息格式
信息格式就有很多选择了, json、xml等等, 也可以自己定制, 只要发送端和接收端统一信息格式就行了.
3.对象状态保存
对于一个类的调用, 通常都会有类状态修改的操作, 比如调用setName
方法, 如何保存对象的信息呢? 当然, 可以服务端将对象在内存中的信息直接序列化发回去, 当客户端下次调用时携带序列化信息, 服务端接收后反序列化还原对象继续操作.
过程
个人理解的RPC
调用过程:
- 客户端创建RPC对象
- 客户端调用方法
- RPC解析方法并将对象及参数做序列化
- RPC通过网络连接发送方法调用
- 服务端接收到方法调用, 解析对象及参数反序列化
- 服务端执行方法并将结果序列化返回
- 客户端接收到结果并进行解析, 返回给本地调用者
- 拿到最终结果
RPC适用于内部网络不同项目之间的通信, 如果是对外暴露的, 个人感觉还是通过接口的形式吧.
使用RPC显然会丧失一部分性能, 毕竟调用要走网络IO, 尽管是内网, 仍然要比本地调用慢上一些, 但带来了更好的可扩展性和可维护性, 感觉还是不错的.
之后如果用到的话, 拉个框架看看源码.
个人理解, 以上…