4
RPC (Remote Procedure Call) is a familiar and unfamiliar word. As long as communication is involved, some kind of network protocol is necessary. We probably have used HTTP, so what is the difference between RPC and HTTP? What are the characteristics of RPC, and what are the common selections?

1. What is RPC

RPC can be divided into two parts: user call interface + specific network protocol. The former is what developers need to care about, and the latter is implemented by the framework.

For example, we define a function, and we hope that if the input of the function is "Hello World", the output is "OK", then this function is a local call. If a remote service receives "Hello World" and can return an "OK" to us, then this is a remote call. We will agree with the service the name of the function to be called remotely. Therefore, our user interface is: input, output, and remote function names. For example SRPC develop, the code on the client side will look like this:

int main()
{
    Example::SRPCClient client(IP, PORT);
    EchoRequest req; // 用户自定义的请求结构
    EchoResponse resp; // 用户自定义的回复结构

    req.set_message("Hello World");
    client.Echo(&req, &resp, NULL); // 调用远程函数名为Echo
    return 0;
}

The specific network protocol is realized by the framework, and the content that the developer wants to send and receive is packaged with a certain application layer protocol for network transmission and reception. Here can be an obvious comparison with HTTP:

  • HTTP is also a network protocol, but the content of the packet is fixed and must be: request line + request header + request body;
  • RPC is a custom network protocol, which is determined by the specific framework. For example, the RPC protocols supported in SRPC are: SRPC/thrift/BRPC/tRPC

These RPC protocols are parallel to HTTP and are application layer protocols. Let's think further, HTTP only contains specific network protocols, and it can also return, for example, our common HTTP/1.1 200 OK, but it seems that there is no user calling interface. Why?

Here we need to figure out, what is the function of the user interface? There are two most important functions:

  • Locate the service to be called;
  • Make our messages forward/backward compatible;

Let's use a table to see how HTTP and RPC are solved respectively:

Locate the service to be calledMessage compatibility
HTTPURLThe developer solves it in the message body by himself
RPCSpecify Service and Method nameHand over to specific IDL

Therefore, the HTTP call reduces the user's call interface function, but sacrifices a part of the freedom of message forward/backward compatibility. However, developers can choose technology according to their own habits, because most of the protocols between RPC and HTTP are interoperable! Isn’t it amazing? Next, we look at the hierarchical structure of RPC to understand why different RPC frameworks and how RPC and HTTP protocols are interoperable.

2. What is RPC

We can see from the architectural level of SRPC, what are the layers of the RPC framework, and what are the functions currently supported by SRPC horizontally:

  • user code (client sending function/server function realization)
  • IDL serialization (protobuf/thrift serialization)
  • data organization (protobuf/thrift/json)
  • compression (none/gzip/zlib/snappy/lz4)
  • Protocol (Sogou-std/Baidu-std/Thrift-framed/TRPC)
  • communication (TCP/HTTP)

We first focus on the following three levels:

From left to right as shown in the figure, it is the level where users touch the most to the least. The IDL layer will generate code according to the request/reply structure defined by the developer. At present, protobuf and thrift are the most commonly used by friends, and the user interface and compatibility issues just mentioned are all solved by the IDL layer. SRPC implements the user interface of these two IDLs as follows:

  • thrift: IDL is parsed by hand, users do not need to chain thrift libraries to use srpc! ! !
  • protobuf: The definition part of the service is parsed manually

The middle column is the specific network protocol, and each RPC can communicate with each other because everyone has realized the "language" of the other side, so they can communicate with each other.

RPC is a parallel level with HTTP. The second column and the third column can theoretically be combined in twos. It is only necessary for the specific RPC protocol in the second column to specialize HTTP-related content when sending, and do not follow your own RPC and HTTP can be communicated by sending in the form required by HTTP.

3. The life cycle of RPC

So far we can take a look at SRPC, how to send the request through the method and process the response and then come back:

According to the above figure, you can see the various levels just mentioned more clearly. Among them, the compression layer, the serialization layer, and the protocol layer are actually decoupled from each other. The SRPC code is implemented very uniformly, and any kind of compression is added horizontally. Algorithms, IDLs, or protocols do not need and should not change the existing code, it is a beautiful architecture~

We have been talking about generating code, what is the use? As can be seen in the figure, the generated code is a bridge connecting the user call interface and the framework code. Here is the simplest protobuf custom protocol as an example: example.proto

syntax = "proto3";

message EchoRequest
{
    string message = 1;
};

message EchoResponse
{
    string message = 1;
};

service Example
{
    rpc Echo(EchoRequest) returns (EchoResponse);
};

We have defined the function names of request, reply, and remote service, and the interface code example.srpc.h can be generated by the following command:

protoc example.proto --cpp_out=./ --proto_path=./
srpc_generator protobuf ./example.proto ./

Let's take a peek and see what functions can be achieved by the generated code:

// SERVER代码
class Service : public srpc::RPCService
{
public:
    // 用户需要自行派生实现这个函数,与刚才pb生成的是对应的
    virtual void Echo(EchoRequest *request, EchoResponse *response,
                      srpc::RPCContext *ctx) = 0;
};

// CLIENT代码
using EchoDone = std::function<void (echoresponse *, srpc::rpccontext *)>;

class SRPCClient : public srpc::SRPCClient 
{
public:
    // 异步接口
    void Echo(const EchoRequest *req, EchoDone done);
    // 同步接口
    void Echo(const EchoRequest *req, EchoResponse *resp, srpc::RPCSyncContext *sync_ctx);
    // 半同步接口
    WFFuture<std::pair<echoresponse, srpc::rpcsynccontext>> async_Echo(const EchoRequest *req);
};

As a high-performance RPC framework, the client code generated by SRPC includes: synchronous, semi-synchronous, and asynchronous interfaces. The beginning of the article shows the practice of a synchronous interface.

The server interface is even simpler. As a server, what we have to do is receives the request -> processing logic -> to return the reply. At this time, the framework has already decompressed and decompressed the network transmission and reception just mentioned , Deserialization, etc. are all done, and then call the function logic of the derived service class implemented by the user through the generated code.

Since a protocol defines a type of client/server, in fact, there are several types of servers mentioned in the second part:

  • SRPCServer
  • SRPCHttpServer
  • BRPCServer
  • TRPCServer
  • ThriftServer
  • ...

4. A complete server example

Finally, we use a complete server example to see how the user calls the interface and how to use HTTP as the client to make calls across protocols. As mentioned earlier, when srpc_generator generates an interface, it will also automatically generate an empty user code. Here we open server.pb_skeleton.cc directly change two lines to run :

#include "example.srpc.h"
#include "workflow/WFFacilities.h"

using namespace srpc;
static WFFacilities::WaitGroup wait_group(1);

void sig_handler(int signo)
{
    wait_group.done();
}

class ExampleServiceImpl : public Example::Service
{
public:

    void Echo(EchoRequest *request, EchoResponse *response, srpc::RPCContext *ctx) override
    {
        response->set_message("OK"); // 具体逻辑在这里添加,我们简单地回复一个OK
    }
};

int main()
{
    unsigned short port = 80; // 因为要启动Http服务
    SRPCHttpServer server; // 我们需要构造一个SRPCHttpServer

    ExampleServiceImpl example_impl;
    server.add_service(&example_impl);

    server.start(port);
    wait_group.wait();
    server.stop();
    return 0;
}

As long as srpc is installed, the executable file can be compiled by the following command under linux:

g++ -o server server.pb_skeleton.cc example.pb.cc -std=c++11 -lsrpc

Next is the exciting moment. We use a curl to initiate an HTTP request:

$ curl -i 127.0.0.1:80/Example/Echo -H 'Content-Type: application/json' -d '{message:"Hello World"}'
HTTP/1.1 200 OK
SRPC-Status: 1
SRPC-Error: 0
Content-Type: application/json
Content-Encoding: identity
Content-Length: 16
Connection: Keep-Alive

{"message":"OK"}

5. Summary

Today, our open source project SRPC based on C++ has deeply analyzed the basic principles of RPC. The overall code style of SRPC is concise and the architecture level is exquisite. The overall code is about 10,000 lines. If you use C++, it may be very suitable for you to learn the RPC architecture.

Through this article, I believe we can clearly understand what RPC is and what the interface looks like. You can also understand the protocol level by interworking with the HTTP protocol. More importantly, we can know the specific vertical levels and compare us horizontally. What are the common usage patterns for each. If you are interested in more functions, you can also read the SRPC source code for further understanding.

6. Project address

https://github.com/sogou/srpc

Welcome to use and star support the author’s open source spirit!


kevinwan
931 声望3.5k 粉丝

go-zero作者