• PHP实现RPC(简版)


    概述

    RPC这个东西是什么? 第一次听说他, 还要在它的前边加个G, 当时我以为GRPC是一项技术, 后来才知道, 并不是这样. GRPC只是RPC的谷歌实现.

    谷歌搜了一下, RPC就是一种: 远程函数调用, 看到这里, 我已经等不及了, 不往下看了, 先自己实现一个. 如果只给你这样一个概念, 如何实现调用远程函数的功能呢?

    自己实现

    自己尝试实现一个粗糙的PHP版本. (不想看可以跳过的)

    思路

    远程调用, 只需要解决下面问题:

    1. 通信问题
    2. 定义传输的数据格式
    3. 如何封装后可以达到像调用本地函数一样的效果

    先来解决通信问题, 直接粗暴的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. 网络通信
    2. 信息格式
    3. 对象状态保存

    1.网络通信

    说到底, 网络通信不过两种: tcp udp.

    有没有使用udp实现的RPC呢? 貌似也有.

    使用tcp协议实现的RPC也有, 当然, 不光传输层协议, 也有直接通过应用层协议: httpwebsocket等等建立连接的. 当然, 如果需要频繁调用, 可以不断开tcp连接, 在一段时间内一直保持连接, 避免频繁握手.

    2.信息格式

    信息格式就有很多选择了, jsonxml等等, 也可以自己定制, 只要发送端和接收端统一信息格式就行了.

    3.对象状态保存

    对于一个类的调用, 通常都会有类状态修改的操作, 比如调用setName方法, 如何保存对象的信息呢? 当然, 可以服务端将对象在内存中的信息直接序列化发回去, 当客户端下次调用时携带序列化信息, 服务端接收后反序列化还原对象继续操作.

    过程

    个人理解的RPC调用过程:

    1. 客户端创建RPC对象
    2. 客户端调用方法
    3. RPC解析方法并将对象及参数做序列化
    4. RPC通过网络连接发送方法调用
    5. 服务端接收到方法调用, 解析对象及参数反序列化
    6. 服务端执行方法并将结果序列化返回
    7. 客户端接收到结果并进行解析, 返回给本地调用者
    8. 拿到最终结果

    RPC适用于内部网络不同项目之间的通信, 如果是对外暴露的, 个人感觉还是通过接口的形式吧.

    使用RPC显然会丧失一部分性能, 毕竟调用要走网络IO, 尽管是内网, 仍然要比本地调用慢上一些, 但带来了更好的可扩展性和可维护性, 感觉还是不错的.

    之后如果用到的话, 拉个框架看看源码.

    个人理解, 以上...

  • 相关阅读:
    判断是否是移动端
    html上传文件类型限制accept的全部属性值
    前端cropper裁剪图像大小(原创)
    eslint加不加分号
    手机浏览器使用rem 自适应html宽度大小
    DOMContentLoaded与load的区别
    ES6语法find查找匹配数组
    React的JSX语法
    React的基本使用
    React简介
  • 原文地址:https://www.cnblogs.com/hujingnb/p/12541447.html
Copyright © 2020-2023  润新知