• 基于protobuf的RPC实现


    可以比较使用google protobuf RPC实现echo service可见。述。

    google protobuf仅仅负责消息的打包和解包。并不包括RPC的实现。但其包括了RPC的定义。如果有以下的RPC定义:

    service MyService {
            rpc Echo(EchoReqMsg) returns(EchoRespMsg) 
        }

    那么要实现这个RPC须要最少做哪些事?总结起来须要完毕下面几步:

    client

    RPCclient须要实现google::protobuf::RpcChannel。主要实现RpcChannel::CallMethod接口。client调用不论什么一个RPC接口,终于都是调用到CallMethod。这个函数的典型实现就是将RPC调用參数序列化,然后投递给网络模块进行发送。

    void CallMethod(const ::google::protobuf::MethodDescriptor* method,
                      ::google::protobuf::RpcController* controller,
                      const ::google::protobuf::Message* request,
                      ::google::protobuf::Message* response,
                      ::google::protobuf::Closure* done) {
            ...
            DataBufferOutputStream outputStream(...) // 取决于你使用的网络实现
            request->SerializeToZeroCopyStream(&outputStream);
            _connection->postData(outputStream.getData(), ...
            ...
        }

    服务端

    服务端首先须要实现RPC接口。直接实现MyService中定义的接口:

    class MyServiceImpl : public MyService {
            virtual void Echo(::google::protobuf::RpcController* controller,
                const EchoReqMsg* request,
                EchoRespMsg* response,
                ::google::protobuf::Closure* done) {
                ...
                done->Run();
            }
        }

    标示service&method

    基于以上,能够看出服务端根本不知道client想要调用哪一个RPC接口。

    从server接收到网络消息。到调用到MyServiceImpl::Echo还有非常大一段距离。

    解决方法就是在网络消息中带上RPC接口标识。

    这个标识能够直接带上service name和method name,但这样的实现导致网络消息太大。还有一种实现是基于service name和method name生成一个哈希值,由于接口不会太多,所以较easy找到基本不冲突的字符串哈希算法。

    不管哪种方法,server是肯定须要建立RPC接口标识到protobuf service对象的映射的。

    这里提供第三种方法:基于option的方法。

    protobuf中option机制类似于这样一种机制:service&method被视为一个对象,其有非常多属性,属性包括内置的,以及用户扩展的。用户扩展的就是option。每个属性有一个值。protobuf提供訪问service&method这些属性的接口。

    首先扩展service&method的属性。下面定义这些属性的key:

    extend google.protobuf.ServiceOptions {
          required uint32 global_service_id = 1000; 
        }
        extend google.protobuf.MethodOptions {
          required uint32 local_method_id = 1000;
        }

    应用层定义service&method时能够指定以上key的值:

    service MyService
        {
            option (arpc.global_service_id) = 2302; 
    
            rpc Echo(EchoReqMsg) returns(EchoRespMsg) 
            {
                option (arpc.local_method_id) = 1;
            }
            rpc Echo_2(EchoReqMsg) returns(EchoRespMsg) 
            {
                option (arpc.local_method_id) = 2;
            }
            ...
        }

    以上相当于在整个应用中。每一个service都被赋予了唯一的id,单个service中的method也有唯一的id。

    然后能够通过protobuf取出以上属性值:

    void CallMethod(const ::google::protobuf::MethodDescriptor* method,
                      ::google::protobuf::RpcController* controller,
                      const ::google::protobuf::Message* request,
                      ::google::protobuf::Message* response,
                      ::google::protobuf::Closure* done) {
            ...
            google::protobuf::ServiceDescriptor *service = method->service();
            uint32_t serviceId = (uint32_t)(service->options().GetExtension(global_service_id));
            uint32_t methodId = (uint32_t)(method->options().GetExtension(local_method_id));
            ...
        }

    考虑到serviceId methodId的范围,能够直接打包到一个32位整数里:

    uint32_t ret = (serviceId << 16) | methodId;
    

    然后就能够把这个值作为网络消息头的一部分发送。

    当然server端是须要建立这个标识值到service的映射的:

    bool MyRPCServer::registerService(google::protobuf::Service *rpcService) {
            const google::protobuf::ServiceDescriptor = rpcService->GetDescriptor();
            int methodCnt = pSerDes->method_count();
    
            for (int i = 0; i < methodCnt; i++) {
                google::protobuf::MethodDescriptor *pMethodDes = pSerDes->method(i);
                uint32_t rpcCode = PacketCodeBuilder()(pMethodDes); // 计算出映射值
                _rpcCallMap[rpcCode] = make_pair(rpcService, pMethodDes); // 建立映射
            }
            return true;
        }

    服务端收到RPC调用后,取出这个标识值,然后再从_rpcCallMap中取出相应的service和method,最后进行调用:

    google::protobuf::Message* response = _pService->GetResponsePrototype(_pMethodDes).New();
        // 用于回应的closure
        RPCServerClosure *pClosure = new (nothrow) RPCServerClosure( 
                _channelId, _pConnection, _pReqMsg, pResMsg, _messageCodec, _version);
        RPCController *pController = pClosure->GetRpcController();
        ...
        // protobuf 生成的CallMethod,会自己主动调用到Echo接口
        _pService->CallMethod(_pMethodDes, pController, _pReqMsg, pResMsg, pClosure);

    參考

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    fetch的使用--当无法判断后台返回数据为什么类型时如何操作
    单页面与多页面间的区别及优缺点
    关于倒计时在关屏后不准确的问题
    前端分页仿百度分页效果
    pc端的弹性布局适配方案
    前端性能优化方向
    居民身份证号码组成规则
    axios简单介绍
    es6 promise 简单总结
    js原型链和原型链的继承
  • 原文地址:https://www.cnblogs.com/mengfanrong/p/4724731.html
Copyright © 2020-2023  润新知