本篇将介绍RPC原理,以及如何基于bprc编写一个简单的Add服务。

PRC原理

RPC(Remote procedure call)的原理可以简述为下图,Client调用Server上的进程时,Client的调用进程将被挂起,而Server上的被调用进程开始执行,调用方将参数传到被调用过程,得到回传结果。

image-20200412144302504

RPC通常借助stub来实现,stub是一组RPC机制的操作原语,这些原语构成了RPC的实现细节。

image-20200412144503030
  1. Client调用本地stub中的一个过程,开始远程过程调用请求
  2. 这个stub过程把有关的参数组装成一个信息包或一组信息包,形成一条消息。运行此执行过程的远程场点的IP地址和执行该过程的进程ID号也包含在这条消息中。
  3. stub把这条消息发送给对应的RPC runtime(RPC运行库)子程序,由这个子程序将详细发送到远程场点。
  4. Server端的RPC runtime子程序在接收到这条消息时,引用与被调用者对应的stub中的一个子程序,并让它来处理消息。
  5. 与被调用者对应的stub中的这个子程序卸载信息,解析出相关参数,并用本地调用方式执行所指定的过程。
  6. 返回调用结果,调用者对应的stub子程序执行return语句返回到Client,整个RPC过程结束。

brpc

brpc是百度开源的工业级RPC框架,支持多种传输协议,目前只开源了C++版本。

在bprc中,参数传递使用protobuf协议,避免了指针类参数等问题。

如何使用brpc

下面将以Add Service为例,来展示如何使用brpc。

Client的调用进程调用Server中的Add过程后被挂起,直到收到调用结构,参数为一组整数。Server计算这组整数的和,并回传调用结果。

编译brpc

根据快速文档编译brpc,以macOS用户为例:

brew install openssl git gnu-getopt coreutils
brew install gflags protobuf leveldb
git clone https://github.com/apache/incubator-brpc
cd incubator-brpc
sh config_brpc.sh --headers=/usr/local/include --libs=/usr/local/lib --cc=clang --cxx=clang++
make -j10

定义Service

brpc中将远程过程称为Service,采用protobuf协议。

这里Request参数为Client调用远程过程的参数(一组整数),Response参数为Server过程的调用结果(一组整数的和),AddService即是所定义的远程过程。

通过protoc --cpp_out=. --proto_path=. add.proto命令即可生成对应的头文件add.pb.h,包含AddRequest、AddResponse,以及AddService_Stub等相关的定义。

syntax="proto2";
option cc_generic_services = true;

message AddRequest {
    repeated int32 adds = 1;
};

message AddResponse {
    required int32 result = 1;
};

service AddService {
    rpc Add(AddRequest) returns (AddResponse);
};

Client端

#include <brpc/channel.h>
#include "add.pb.h"

int main(int argc, char* argv[]) {
    brpc::Channel channel;

    // Initialize the channel
    brpc::ChannelOptions options;
    options.protocol = "baidu_std";
    options.connection_type = "";
    options.timeout_ms = 100;
    options.max_retry = 3;
    std::string endpoints = "0.0.0.0:8000";
    std::string load_balancer = ""; // single server
    if (channel.Init(endpoints.c_str(), load_balancer.c_str(), &options) != 0) {
        std::cerr << "Fail to initialize channel" << std::endl;
        return -1;
    }

    // Create stub
    AddService_Stub stub(&channel);

    AddRequest request;
    AddResponse response;
    brpc::Controller cntl;
    cntl.request_attachment().append("");

    // Pack params
    for (int i = 0; i < 100; ++i) {
        request.add_adds(i);
    }

    // Start Add service
    stub.Add(&cntl, &request, &response, NULL);

    if (!cntl.Failed()) {
        std::cout << "get: " << response.result() << std::endl;
    } else {
        std::cerr << "get: failed" << std::endl;
    }

    return 0;
}

在bprc中没有Client对应的实体,取而代之的是brpc::Channel,代表和一台或一组服务器的交互通道。在实践中中Client和Channel的角色基本没有差别,可以将Channel视作Client。Channel的创建和初始化并不是线程安全的,初始化结束后可以被多线程共用。

Client端将相关参数打包好后,通过调用本地stub中的Add过程,来执行Server端的AddService。

tub.Add(&cntl, &request, &response, NULL)的最后一个参数表示本次调用为同步调用。

Server端

#include <brpc/server.h>
#include "add.pb.h"

// implementation of AddService
class AddServiceImpl : public AddService {
public:
    AddServiceImpl() {};
    virtual ~AddServiceImpl() {};
    virtual void Add(google::protobuf::RpcController* cntl_base,
                     const AddRequest* request,
                     AddResponse* response,
                     google::protobuf::Closure* done) {
        brpc::ClosureGuard done_guard(done);
        brpc::Controller* cntl = static_cast<brpc::Controller*>(cntl_base);

        std::cout << "request form [" << cntl->remote_side()
                  << "] to [" << cntl->local_side() << "]: ";
        int result = 0;

        // Parse request and calculate call result
        for (int i = 0; i < request->adds_size(); ++i) {
            std::cout << request->adds(i) << ", ";
            result += request->adds(i);
        }
        std::cout << std::endl;

        // Pack result
        response->set_result(result);
    }
};

int main(int argc, char* argv[]) {
    brpc::Server server;
    AddServiceImpl add_service_impl;

    // Add the service into server.
    if (server.AddService(&add_service_impl,
                          brpc::SERVER_DOESNT_OWN_SERVICE) != 0) {
        std::cerr << "Fail to add service" << std::endl;
        return -1;
    }

    // Start the server.
    brpc::ServerOptions options;
    options.idle_timeout_sec = -1;
    if (server.Start(8000, &options) != 0) {
        std::cerr << "Fail to start EchoServer" << std::endl;
        return -1;
    }

    server.RunUntilAskedToQuit();
    return 0;
}

Server端添加Add Service后启动Server,等待被调用。当接收到Client端stub发来的消息时,解析request参数,执行相关过程,最后将结果打包回传。

运行结果

Server端:

» ./server
I0412 15:31:48   775 src/brpc/server.cpp:1045] Server[AddServiceImpl] is serving on port=8000.
I0412 15:31:48   775 src/brpc/server.cpp:1048] Check out http://barrierys-MBP.local:8000 in web browser.
request form [127.0.0.1:51830] to [127.0.0.1:8000]: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99,

Client端:

» ./client
get: 4950