一文帶你搞懂 RPC 究竟是個啥

RPC(Remote Procedure Call),是一個你們既熟悉又陌生的詞,只要涉及到通訊,必然須要某種網絡協議。咱們極可能用過HTTP,那麼RPC又和HTTP有什麼區別呢?RPC還有什麼特色,常見的選型有哪些?

1. RPC是什麼

RPC能夠分爲兩部分:用戶調用接口 + 具體網絡協議。前者爲開發者須要關心的,後者由框架來實現。linux

舉個例子,咱們定義一個函數,咱們但願函數若是輸入爲「Hello World」的話,輸出給一個「OK」,那麼這個函數是個本地調用。若是一個遠程服務收到「Hello World」能夠給咱們返回一個「OK」,那麼這是一個遠程調用。咱們會和服務約定好遠程調用的函數名。所以,咱們的用戶接口就是:輸入、輸出、遠程函數名,好比用 SRPC 開發的話,client端的代碼會長這樣:c++

int main()
{
    Example::SRPCClient client(IP, PORT);
    EchoRequest req; // 用戶自定義的請求結構
    EchoResponse resp; // 用戶自定義的回覆結構

    req.set_message("Hello World");
    client.Echo(&req, &resp, NULL); // 調用遠程函數名爲Echo
    return 0;
}

具體網絡協議,是框架來實現的,把開發者要發出和接收的內容以某種應用層協議打包進行網絡收發。這裏能夠和HTTP進行一個明顯的對比:git

  • HTTP也是一種網絡協議,但包的內容是固定的,必須是:請求行 + 請求頭 + 請求體;
  • RPC是一種自定義網絡協議,由具體框架來定,好比SRPC裏支持的RPC協議有:SRPC/thrift/BRPC/tRPC

這些RPC協議都和HTTP平行,是應用層協議。咱們再進一步思考,HTTP只包含具體網絡協議,也能夠返回好比咱們常見的HTTP/1.1 200 OK,但彷彿沒有用戶調用接口,這是爲何呢?github

這裏須要搞清楚,用戶接口的功能是什麼?最重要的功能有兩個:算法

  • 定位要調用的服務;
  • 讓咱們的消息向前/向後兼容;

咱們用一個表格來看一下HTTP和RPC分別是怎麼解決的:shell

定位要調用的服務 消息先後兼容
HTTP URL 開發者自行在消息體裏解決
RPC 指定Service和Method名 交給具體IDL

所以,HTTP的調用減小了用戶調用接口的函數,可是犧牲了一部分消息向前/向後兼容的自由度。可是,開發者能夠根據本身的習慣進行技術選型,由於RPC和HTTP之間大部分都是協議互通的!是否是很神奇?接下來咱們看一下RPC的層次架構,就能夠明白爲何不一樣RPC框架之間、以及RPC和HTTP協議是如何作到互通的。json

2. RPC有什麼

咱們能夠從SRPC的架構層次上來看,RPC框架有哪些層,以及SRPC目前所橫向支持的功能是什麼:網絡

  • 用戶代碼(client的發送函數/server的函數實現)
  • IDL序列化(protobuf/thrift serialization)
  • 數據組織 (protobuf/thrift/json)
  • 壓縮(none/gzip/zlib/snappy/lz4)
  • 協議 (Sogou-std/Baidu-std/Thrift-framed/TRPC)
  • 通訊 (TCP/HTTP)

咱們先關注如下三個層級:架構

如圖從左到右,是用戶接觸得最多到最少的層次。IDL層會根據開發者定義的請求/回覆結構進行代碼生成,目前小夥伴們用得比較多的是protobuf和thrift,而剛纔說到的用戶接口和先後兼容問題,都是IDL層來解決的。SRPC對於這兩個IDL的用戶接口實現方式是:app

  • thrift:IDL純手工解析,用戶使用srpc是不須要鏈thrift的庫的 !!!
  • protobuf:service的定義部分純手工解析

中間那列是具體的網絡協議,而各RPC能互通,就是由於你們實現了對方的「語言」,所以能夠協議互通。

而RPC做爲和HTTP並列的層次,第二列和第三列理論上是能夠兩兩結合的,只須要第二列的具體RPC協議在發送時,把HTTP相關的內容進行特化,不要按照本身的協議去發,而按照HTTP須要的形式去發,就能夠實現RPC與HTTP互通。

3. RPC的生命週期

到此咱們能夠經過SRPC看一下,把request經過method發送出去並處理response再回來的整件事情是怎麼作的:

根據上圖,

能夠更清楚地看到剛纔說起的各個層級,其中壓縮層、序列化層、協議層實際上是互相解耦打通的,在SRPC代碼上實現得很是統一,橫向增長任何一種壓縮算法或IDL或協議都不須要也不該該改動現有的代碼,纔是一個精美的架構~

咱們一直在說生成代碼,到底有什麼用呢?圖中能夠得知,生成代碼是銜接用戶調用接口和框架代碼的橋樑,這裏以一個最簡單的protobuf自定義協議爲例:example.proto

syntax = "proto3";

message EchoRequest
{
    optional string message = 1;
};

message EchoResponse
{
    optional string message = 1;
};

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

咱們定義好了請求、回覆、遠程服務的函數名,經過如下命令就能夠生成出接口代碼example.srpc.h

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

咱們一窺究竟,看看生成代碼到底能夠實現什麼功能:

// 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);
};

做爲一個高性能RPC框架,SRPC生成的client代碼中包括了:同步、半同步、異步接口,文章開頭展現的是一個同步接口的作法。

而server的接口就更簡單了,做爲一個服務端,咱們要作的就是收到請求->處理邏輯->返回回覆,而這個時候,框架已經把剛纔提到的網絡收發、解壓縮、反序列化等都給作好了,而後經過生成代碼調用到用戶實現的派生service類的函數邏輯中。

因爲一種協議定義了一種client/server,所以其實咱們一樣能夠獲得的server類型有第二部分提到過的若干種:

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

4. 一個完整的server例子

最後咱們用一個完整的server例子,來看一下用戶調用接口的使用方式,以及如何跨協議使用HTTP做爲client進行調用:

#include <stdio.h>
#include <signal.h>
#include "example.srpc.h"  // include生成代碼頭文件

using namespace srpc;

class ExamplePBServiceImpl : public ::example::ExamplePB::Service
{
public:
    void Echo(::example::EchoRequest *request, ::example::EchoResponse *response,
              srpc::RPCContext *ctx) override
    {
        response->set_message("OK");
    }  
};

int main()
{
    // 1. 定義一個server,因爲咱們要和HTTP通訊,所以咱們定義SRPCHTTPServer
    SRPCHTTPServer server;

    // 2. 定義一個service,並加到server中
    ExamplePBServiceImpl examplepb_impl;
    server.add_service(&examplepb_impl);

    // 3. 把server啓動起來
    server.start(80);
    pause();
    server.stop();
    return 0;
}

只要安裝了srpc,linux下便可經過如下命令編譯出可執行文件:

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

接下來是激動人心的時刻了,咱們用人手一個的curl來發起一個HTTP請求:

$ 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. 總結

今天咱們基於 C++ 實現的開源項目 SRPC 深刻分析了 RPC 的基本原理。SRPC 總體代碼風格簡潔、架構層次精巧,總體約1萬行代碼,若是你使用 C++,那可能很是適合你用來學習 RPC 架構。

經過這篇文章,相信咱們能夠清晰地瞭解到 RPC 是什麼,接口長什麼樣,也能夠經過與HTTP協議互通來理解協議層次,更重要的是能夠知道具體縱向的每一個層次,及橫向對比咱們常見的每種使用模式都有哪些。若是小夥伴對更多功能感興趣,也能夠經過閱讀 SRPC 源碼進行進一步瞭解。

6. 項目地址

https://github.com/sogou/srpc

歡迎使用並 star 支持一下做者的開源精神!

go-zero 系列文章見『微服務實踐』公衆號
相關文章
相關標籤/搜索