php搭建简单rpc(解决mongodb连接数的问题)

rpc解释

RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的,本质上编写的调用代码基本相同。

某网上解释,具体基础解释可以自行百度,其实我通俗的解释好了就是远程调用方法

现在有两台服务器A和B,这时候两台服务器分别运行不同的php服务。这时候两个服务想要互相调用彼此的代码最原始的方式就是彼此开放api接口进行http协议交互。
然后我们可以使用rpc进行代理直接本地服务器代理运行返回结果,rpc使用的C(client)/S(server)结构

目前支持rpc的框架有很多 swoole 和 workman 不一定需要自己手动构建rpc

流程

  1. server端tcp服务器(steam_socket_server)
  2. client连接tcp服务器(steam_socket_clinet)
  3. server端解析接受的字节
  4. 本地运行结果
  5. server端将运行结果传输回client端

rpc可以解决mongodb无限连接池的问题,mongodb的连接数跟服务器php-fpm挂载数相关系 理论值为mongodb的连接数 = 服务器php-fpm总数 - 2 如果负载的服务器多了 连接数很容易撑不下去。因为我们采用的是api服务器直接去连接mongodb没有架设一层代理层,这就是为什么我要去构建rpc层的主要原因

server层代码

<?php 

        $errno = 0;
        $errstr = '';

        //先握手 判断 端口是否能正常注册
        // creating the socket...
        $socket = stream_socket_server('tcp://127.0.0.1:8850', $errno, $errstr);
        if (!$socket)
        {
            exit("[create socket error]:{$errno}-{$errstr}");
        }
        fclose($socket);

        //receive message
        while (true)
        {
            // disconnected every 1 seconds...
            $this->receiveMessage('127.0.0.1','8850',1);
        }
function receiveMessage($ipServer,$portNumber,$nbSecondsIdle)
    {
        $errno = 0;
        $errstr = '';
        // creating the socket...
        $socket = stream_socket_server('tcp://'.$ipServer.':'.$portNumber, $errno, $errstr);
        //设置堵塞 类似 redis的队列堵塞 直到拿到数据为止
        stream_set_blocking($socket,1);
        if (!$socket)
        {
            $logData = [
                'errno' => $errno,
                'errstr' => $errstr,
                'msg' => 'create socket error'
            ];
            CoobarLog::error($logData,'rpcerror');
            echo "$errstr ($errno)" . PHP_EOL;
        }
        else
        {
            // while there is connection, i'll receive it... if I didn't receive a message within $nbSecondsIdle seconds, the following function will stop.
            while ($conn = @stream_socket_accept($socket,$nbSecondsIdle))
            {
                $message = fread($conn, 2048);
                //1.解析message 进行安全验证
                //2.根据message的参数进行本地代理运行 参考我目前的协议 module class function parmas 通过module+class+function 定位需要代理的方法然后call_user_func_array去调用
                //3.返回本地运行结果
                $result = **********
                fwrite($conn,json_encode($result));
                fclose($conn);
            }
            fclose($socket);
        }
    }

推荐rpc的启动方式使用nohup 后台注册线程方式

nohup php rpcserver.php &

client代码

<?php
/**
 * Created by PhpStorm.
 * User: hls
 * Date: 2017/11/16
 * Time: 下午3:03
 */
namespace phprpc;
class RpcClient
{
    private static $config = [];

    private $requestBody = [];

    private static $init = true;


    const LENGTH_MAX = 4028;

    /**
     * 实例化
     * @param $config
     */
    public static function instance($config)
    {
        if (!isset($config['host']))
            throw new \Exception('no rpc host',100);
        if (!isset($config['port']))
            throw new \Exception('no rpc port',101);

        self::$config = $config;
        self::$init = false;
        return new self();
    }

    /**
     * @param $module 模块名
     * @param $class 类名
     * @param array $params 参数 数组
     * @return array|mixed|\stdClass
     * @throws RpcClientException
     */
    public function sendMessage($module,$class,$function,array $params)
    {
        if (self::$init)
            throw new \Exception('no instance rpc',102);

        $connection = $this->connect();

        $this->setRequestBody($module,$class,$function,$params);

        return  $this->send($connection,json_encode(self::getRequestBody()));
    }

    /**
     * 发送消息
     * @param $connection
     * @param $string
     * @return array|mixed|\stdClass
     * @throws RpcClientException
     */
    private function send($connection,$string)
    {
        fwrite($connection,$string);
        $returnData = fgets($connection,self::LENGTH_MAX);
        fclose($connection);
        if (empty(json_decode($returnData,true)))
            throw new \Exception('rpc server return data not illegal');
        return json_decode($returnData,true);
    }

    /**
     * 连接rpc服务器
     * @param array $config
     * @return bool|null|resource
     * @throws RpcClientException
     */
    private function connect($config = [])
    {
        $errno = '';
        $errstr = '';
        if (empty($config)) {
            $config = self::$config;
        }
        $connection = stream_socket_client("tcp://{$config['host']}:{$config['port']}",$errno,$errstr);
        //重连两次
        for ($i = 0;$i < 2;$i++) {
            if (!$connection) {
                $connection = stream_socket_client("tcp://{$config['host']}:{$config['port']}",$errno,$errstr);
            } else {
                break;
            }
        }

        if (!$connection)
            throw new \Exception($errstr,$errno);
        return $connection;

    }

    /**
     * 获取请求包体
     * @return array
     */
    private function getRequestBody()
    {
        return [
            'username' => $this->requestBody['username'],
            'password' => $this->requestBody['password'],
            'module' => $this->requestBody['module'],
            'class' => $this->requestBody['class'],
            'function' => $this->requestBody['function'],
            'params' => $this->requestBody['params'],
        ];
    }

    /**
     * 设置请求包体
     * @param $module
     * @param $class
     * @param $params
     * @return bool
     */
    public function setRequestBody($module,$class,$function,$params)
    {
        $this->requestBody['username'] = isset(self::$config['username']) ? self::$config['username'] : '';
        $this->requestBody['password'] = isset(self::$config['password']) ? self::$config['password'] : '';
        $this->requestBody['module'] = $module;
        $this->requestBody['class'] = $class;
        $this->requestBody['function'] = $function;
        $this->requestBody['params'] = $params;
        return true;
    }
}

客户端调用

    $config = ['host' => '10.46.231.6','port' => 5200,'username' => 'coobar','password' => '52pAVVvxBAIGDPPj'];
        $result = RpcClient::instance($config)->sendMessage('mongodb','Handle','getConfig',[TurntableConfig::getConfigName(),'172']);

就是比较简单的rpc的原型
还需要在这上面增加优化的地方

  1. rpc-client端需要支持多个rpc-server的轮询支持,防止单台rpc-server服务器压力过大或者服务器崩溃 导致rpc服务器失败
  2. 对rpc-server端进行权限认证
  3. 对rpc-client 进行重连机制,我目前代码使用的默认重连2次
  4. rpc-server端的日志体系,需要记录每次执行的上行参数和下行返回结果,方便排查问题
  5. rpc-server端进行异常监听,这个很重要,很多东西都是通过实践进行优化

最后开心的是

使用rpc调用mongodb后,连接数不涨啦不涨啦!!!但是现在还是需要进行各种的压测和特殊情况实践,如果直接用于生产环境估计够呛的!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章