C++ RPC Library – Based on Protobuf, and Core Library Independent of Network
gayrpc
跨平台全双工双向(异步)RPC系统,也即通信两端都可以同时作为RPC服务方和客户端.
Build Status
Windows : Linux:
动机
目前的RPC系统大多用于互联网行业后端系统,但他们之间更像一个单向图(不存在两个服务彼此依赖/互相调用),但游戏等行业中则两节点之间可能相互调用。 因此我们需要一个全双工RPC,在一个”链接”的两端均可开启服务和客户端,当然这里的”链接”是一个虚拟概念,它不一定基于TCP,也即”链接”的两端可以只存在逻辑链接而没有网络直连。
设计准则
- RPC支持拦截器,能够对Request或Response做一些处理(比如监控、认证、加解密、分布式跟踪)
- RPC核心不依赖网络和网络传输协议,即:我们可以开发任何网络应用和逻辑来开启RPC两端,将”收到”的消息丢给RPC核心,并通过某个出站拦截器来实现/决定把Request或Response以何种方式传递给谁。
- 此RPC是基于异步回调的,我认为这是目前C++里比较安全和靠谱的方式,除了回调地狱让人恶心……
- RPC系统核心(以及接口)是线程安全的,可以在任意线程调用RPC;且可以在任意线程使用XXXReply::PTR对象返回Response。
- RPC是并行的,也即:客户端可以随意发送Request而不必等待之前的完成。 且允许先收到后发出的Request的Response。
- RPC系统会为每一个”链接”生成一个XXXService对象,这样可以让不同的”链接”绑定/持有各自的业务对象(有状态)(而不是像grpc等系统那样,一个服务只存在一个service对象,这类RPC调用类似短链接:收到请求返回数据即可)
- 支持HTTP API(同理2,此功能通过具体通信协议和拦截器进行支持,RPC核心本身与此无关).
依赖
Windows下可使用 vcpkg 进行安装以下依赖库.
请注意,当使用Windows时,务必使用 vcpkg install brynet --head
安装brynet.
且务必根据自身系统中的protoc版本对gayrpc_meta.proto和gayrpc_option.proto预先生成代码,请在 src目录里执行:
protoc --cpp_out=. ./gayrpc/core/gayrpc_meta.proto ./gayrpc/core/gayrpc_option.proto
代码生成工具
地址: https://github.com/IronsDu/protoc-gen-gayrpc
,由 liuhan 编写完成。
首先将插件程序放到系统 PATH路径下(比如Linux下的/usr/bin),然后执行代码生成,比如(在具体的服务目录里,比如 gayrpc/examples/echo/pb
):
protoc -I. -I../../../src --cpp_out=. echo_service.proto protoc -I. -I../../../src --gayrpc_out=. echo_service.proto
Example
https://github.com/IronsDu/gayrpc/tree/master/examples
Benchmark
Latency(single threaded):
connection num:1000 took 13285ms, for 3000000 requests throughput (TPS):230769 mean:3 ms ,3920200 ns median:3 ms ,3050227 ns max:40 ms ,40070708 ns min:0 ms ,15601 ns p99:3 ms ,3757835 ns
Throughput(two threaded):
Ubuntu 18.04 (i5 CPU)下,echo 300k QPS,当并发echo时 1000K QPS.
协议
目前实现的RPC通信协议底层采用两层协议.(注意!RPC核心库并不依赖具体通信协议!) 第一层采用二进制协议,且字节序统一为大端. 通信格式如下:
[data_len | op | data] 字段解释: data_len : uint64_t; op : uint32_t; data : char[data_len];
当 op
值为1时表示RPC消息,此为第二层协议!这时第一层协议中的data的内存布局则为:
[meta_size | data_size | meta | data] 字段解释: meta_size : uint32_t; data_size : uint64_t; meta : char[meta_size]; data : char[data_size];
其中 meta
为 RpcMata
的binary. data
为某业务上的Protobuf Request或Response类型对象的binary或JSON.
RpcMata
的proto定义如下:
syntax = "proto3"; package gayrpc.core; message RpcMeta { enum Type { REQUEST = 0; RESPONSE = 1; }; enum DataEncodingType { BINARY = 0; JSON = 1; }; message Request { // 请求的服务函数 uint64 method = 1; // 请求方是否期待服务方返回response bool expect_response = 2; // 请求方的序号ID uint64 sequence_id = 3; }; message Response { // 请求方的序号ID uint64 sequence_id = 1; // 执行是否成功 bool failed = 2; // (当failed为true)错误码 int32 error_code = 3; // (当failed为true)错误原因 string reason = 4; }; // Rpc类型(请求、回应) Type type = 1; // RpcData的编码方式 DataEncodingType encoding = 2; // 请求元信息 Request request_info = 3; // 回应元信息 Response response_info = 4; }
服务描述文件范例
以下面的服务定义为例:
syntax = "proto3"; package dodo.test; message EchoRequest { string message = 1; } message EchoResponse { string message = 1; } service EchoServer { rpc Echo(EchoRequest) returns(EchoResponse){ option (gayrpc.core.message_id)= 1 ;//设定消息ID,也就是rpc协议中request_info的method }; }
处理请求或Response的实现原理
- 编写第一层通信协议的编解码
- 将第一层中的
data
作为第二层协议数据,反序列化其中的meta
作为RpcMeta
对象 - 判断
RpcMata
中的type
- 如果为
REQUEST
则根据request_info
中的元信息调用method
所对应的服务函数. 此时第二层协议中的data
则为服务函数的请求请求对象(比如EchoRequest
). - 如果为
RESPONSE
则根据response_info
中的元信息调用sequence_id
对应的回调函数. 此时第二层协议中的data
则为服务方返回的Response(比如EchoResponse
)
- 如果为
发送请求的实现原理
以 client->echo(echoRequest, responseCallback)
为例 参考代码: GayRpcClient.h
data meta
发送Response的实现原理
以 replyObj->reply(echoResponse)
为例 参考代码: GayRpcReply.h
data meta
编解码参考
https://github.com/IronsDu/gayrpc/tree/master/src/gayrpc/protocol
注意点
- RPC核心并不依赖通信采用的协议,而且网络传输可以是TCP、UDP、WebSocket等等,抑或消息队列等等。
- RPC服务方的reply顺序与客户端的调用顺序无关,也就是可能后发起的请求先得到返回.