RPC(Remote Procedure Call),是一个大家既相熟又生疏的词,只有波及到通信,必然须要某种网络协议。咱们很可能用过HTTP,那么RPC又和HTTP有什么区别呢?RPC还有什么特点,常见的选型有哪些?
1. RPC是什么
RPC能够分为两局部:用户调用接口 + 具体网络协议。前者为开发者须要关怀的,后者由框架来实现。
举个例子,咱们定义一个函数,咱们心愿函数如果输出为“Hello World”的话,输入给一个“OK”,那么这个函数是个本地调用。如果一个近程服务收到“Hello World”能够给咱们返回一个“OK”,那么这是一个近程调用。咱们会和服务约定好近程调用的函数名。因而,咱们的用户接口就是:输出、输入、近程函数名,比方用 SRPC 开发的话,client端的代码会长这样:
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进行一个显著的比照:
- HTTP也是一种网络协议,但包的内容是固定的,必须是:申请行 + 申请头 + 申请体;
- RPC是一种自定义网络协议,由具体框架来定,比方SRPC里反对的RPC协定有:SRPC/thrift/BRPC/tRPC
这些RPC协定都和HTTP平行,是应用层协定。咱们再进一步思考,HTTP只蕴含具体网络协议,也能够返回比方咱们常见的HTTP/1.1 200 OK,但好像没有用户调用接口,这是为什么呢?
这里须要搞清楚,用户接口的性能是什么?最重要的性能有两个:
- 定位要调用的服务;
- 让咱们的音讯向前/向后兼容;
咱们用一个表格来看一下HTTP和RPC别离是怎么解决的:
定位要调用的服务 | 音讯前后兼容 | |
---|---|---|
HTTP | URL | 开发者自行在音讯体里解决 |
RPC | 指定Service和Method名 | 交给具体IDL |
因而,HTTP的调用缩小了用户调用接口的函数,然而就义了一部分音讯向前/向后兼容的自由度。然而,开发者能够依据本人的习惯进行技术选型,因为RPC和HTTP之间大部分都是协定互通的!是不是很神奇?接下来咱们看一下RPC的档次架构,就能够明确为什么不同RPC框架之间、以及RPC和HTTP协定是如何做到互通的。
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的用户接口实现形式是:
- 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 OKSRPC-Status: 1SRPC-Error: 0Content-Type: application/jsonContent-Encoding: identityContent-Length: 16Connection: Keep-Alive{"message":"OK"}
5. 总结
明天咱们基于 C++ 实现的开源我的项目 SRPC 深入分析了 RPC 的基本原理。SRPC 整体代码格调简洁、架构档次精美,整体约1万行代码,如果你应用 C++,那可能非常适合你用来学习 RPC 架构。
通过这篇文章,置信咱们能够清晰地理解到 RPC 是什么,接口长什么样,也能够通过与HTTP协定互通来了解协定档次,更重要的是能够晓得具体纵向的每个档次,及横向比照咱们常见的每种应用模式都有哪些。如果小伙伴对更多功能感兴趣,也能够通过浏览 SRPC 源码进行进一步理解。
6. 我的项目地址
https://github.com/sogou/srpc
欢送应用并 star 反对一下作者的开源精力!
go-zero 系列文章见『微服务实际』公众号