基於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);

參考

版權聲明:本文博客原創文章,博客,未經同意,不得轉載。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM