本文來自srpc作者李穎欣,在此基礎(chǔ)上略做改動(dòng)。
只要涉及到網(wǎng)絡(luò)通信,必然涉及到網(wǎng)絡(luò)協(xié)議,應(yīng)用層也是一樣。在應(yīng)用層最標(biāo)準(zhǔn)和常用的就是HTTP協(xié)議。但在很多性能要求較高的場(chǎng)景各大企業(yè)內(nèi)部也會(huì)自定義的 RPC 協(xié)議。舉個(gè)例子,就是相當(dāng)于各個(gè)省不但用官方普通話,還都有自己的方言,RPC就相當(dāng)于是一個(gè)方言。
RPC的全稱是Remote Procedure Call,翻譯過來就是遠(yuǎn)程過程調(diào)用。但這個(gè)名字起的一點(diǎn)都不好,過分強(qiáng)調(diào)了和LPC(本地過程調(diào)用)的對(duì)比。沒有突出出來 RPC 本身涉及到的一些技術(shù)特點(diǎn)。
我們今天來從三個(gè)角度和大家聊聊 RPC。
- RPC是什么:通過和HTTP的對(duì)比來幫大家了解RPC
- RPC有什么:介紹了RPC用到的用戶樁代碼、IDL序列化、壓縮、協(xié)議、通信等技術(shù)點(diǎn)
- RPC生命周期:詳細(xì)探討RPC從請(qǐng)求發(fā)出到收到返回的全過程
今天的講解會(huì)結(jié)合基于C++實(shí)現(xiàn)的開源項(xiàng)目SRPC。SRPC整體代碼風(fēng)格簡(jiǎn)潔、架構(gòu)層次精巧,整體約1萬(wàn)行代碼,非常適合用來學(xué)習(xí)RPC架構(gòu):https://github.com/sogou/srpc
一. RPC是什么
RPC可以分為兩部分:用戶調(diào)用接口+具體網(wǎng)絡(luò)協(xié)議。前者為開發(fā)者需要關(guān)心的,后者由框架來實(shí)現(xiàn)。
1. 用戶調(diào)用接口
舉個(gè)例子,我們定義一個(gè)函數(shù),我們希望函數(shù)如果輸入為“Hello World”的話,輸出給一個(gè)“OK”,那么這個(gè)函數(shù)是個(gè)本地調(diào)用。如果一個(gè)遠(yuǎn)程服務(wù)收到“Hello World”可以給我們返回一個(gè)“OK”,那么這是一個(gè)遠(yuǎn)程調(diào)用。我們會(huì)和服務(wù)約定好遠(yuǎn)程調(diào)用的函數(shù)名。因此,我們的用戶接口就是:輸入、輸出、遠(yuǎn)程函數(shù)名,比如用SRPC開發(fā)的話,client端的代碼會(huì)長(zhǎng)這樣:
intmain()
{
Example::SRPCClientclient(IP,PORT);
EchoRequestreq;//用戶自定義的請(qǐng)求結(jié)構(gòu)
EchoResponseresp;//用戶自定義的回復(fù)結(jié)構(gòu)
req.set_message("HelloWorld");
client.Echo(&req,&resp,NULL);//調(diào)用遠(yuǎn)程函數(shù)名為Echo
return0;
}
2. 具體網(wǎng)絡(luò)協(xié)議
這是框架來實(shí)現(xiàn)的,把開發(fā)者要發(fā)出和接收的內(nèi)容以某種應(yīng)用層協(xié)議打包進(jìn)行網(wǎng)絡(luò)收發(fā)。這里可以和HTTP進(jìn)行一個(gè)明顯的對(duì)比:
-
RPC是一種自定義網(wǎng)絡(luò)協(xié)議,由具體框架來定,比如SRPC里支持的RPC協(xié)議有:SRPC / thrift / BRPC / tRPC,并且也是tRPC協(xié)議目前唯一的開源實(shí)現(xiàn),我們拿其中的SogouRPC-std protocol為例給大家看看RPC協(xié)議的大概樣子:
-
HTTP也是一種網(wǎng)絡(luò)協(xié)議,但包的內(nèi)容是固定的,必須是:請(qǐng)求行 + 請(qǐng)求頭 + 請(qǐng)求體;
3. 進(jìn)一步思考
上圖對(duì)應(yīng)的顏色,所實(shí)現(xiàn)的功能是類似的。我們想一想,為什么大家都長(zhǎng)差不多呢?
這里就需要搞清楚,我們想要實(shí)現(xiàn)用戶接口,需要怎么做?最重要需要支持以下三個(gè)功能:
- 定位要調(diào)用的服務(wù);
- 把完整的消息切下來;
- 讓我們的消息向前/向后兼容;
這樣既可以讓消息內(nèi)保證一定的靈活性,又可以方便拿下一塊數(shù)據(jù),去調(diào)用用戶想要的服務(wù)。
我們用一個(gè)表格來看一下HTTP和RPC分別是怎么解決的:
定位要調(diào)用的服務(wù) | 消息長(zhǎng)度 | 消息前后兼容 | |
---|---|---|---|
HTTP | URL | header里Content-Length | body里自己解決 |
RPC | 指定Service和Method名 | 協(xié)議header里自行約定 | 交給具體IDL |
因此,大家都會(huì)需要類似的結(jié)構(gòu)去組裝一條完整的用戶請(qǐng)求,而第三部分的body只要框架支持,RPC協(xié)議和HTTP是可以互通的!因此開發(fā)者完全可以根據(jù)自己的業(yè)務(wù)需求進(jìn)行選型,接下來我們看一下RPC的層次架構(gòu),就可以明白為什么不同RPC框架之間的互通、以及RPC和HTTP協(xié)議又是如何做到互通的。
二、 RPC有什么
我們可以借SRPC的架構(gòu),看一下RPC框架從用戶到系統(tǒng)都有哪些層次,以及SRPC目前所橫向支持的功能是什么:
- 用戶代碼(client的發(fā)送函數(shù)/server的函數(shù)實(shí)現(xiàn))
- IDL序列化(protobuf/thrift serialization)
- 數(shù)據(jù)組織(protobuf/thrift/json)
- 壓縮(none/gzip/zlib/snappy/lz4)
- 協(xié)議(Sogou-std/Baidu-std/Thrift-framed/TRPC)
- 通信(TCP/HTTP)
我們先關(guān)注以下三個(gè)層級(jí):
如圖從左到右,是用戶接觸的最多到最少的層次。IDL層會(huì)根據(jù)開發(fā)者定義的請(qǐng)求/回復(fù)結(jié)構(gòu)進(jìn)行代碼生成,目前小伙伴們用得比較多的是protobuf和thrift,而剛才說到的用戶接口和前后兼容問題,都是IDL層來解決的。SRPC對(duì)于這兩個(gè)IDL的用戶接口實(shí)現(xiàn)方式是:
- thrift:IDL純手工解析,用戶使用srpc是不需要鏈thrift的庫(kù)的 !!!
- protobuf:service的定義部分純手工解析
中間那列是具體的網(wǎng)絡(luò)協(xié)議,而各RPC能互通,就是因?yàn)榇蠹覍?shí)現(xiàn)了對(duì)方的“語(yǔ)言”,因此可以協(xié)議互通。
而RPC作為和HTTP并列的層次,第二列和第三列理論上是可以兩兩結(jié)合的,只需要第二列的具體RPC協(xié)議在發(fā)送時(shí),把HTTP相關(guān)的內(nèi)容進(jìn)行特化,不要按照自己的協(xié)議去發(fā),而按照HTTP需要的形式去發(fā),就可以實(shí)現(xiàn)RPC與HTTP互通。
三、 RPC的生命周期
到此我們可以通過SRPC看一下,把request通過method發(fā)送出去并處理response再回來的整件事情是怎么做的:
根據(jù)上圖,可以更清楚地看到剛才提及的各個(gè)層級(jí),其中壓縮層、序列化層、協(xié)議層其實(shí)是互相解耦打通的,在SRPC代碼上實(shí)現(xiàn)得非常統(tǒng)一,橫向增加任何一種壓縮算法或IDL或協(xié)議都不需要也不應(yīng)該改動(dòng)現(xiàn)有的代碼,才是一個(gè)精美的架構(gòu)~
我們一直在說生成代碼,到底有什么用呢?圖中可以得知,生成代碼是銜接用戶調(diào)用接口和框架代碼的橋梁,這里以一個(gè)最簡(jiǎn)單的protobuf自定義協(xié)議為例:example.proto
syntax="proto3";//這里proto2和proto3都可以
messageEchoRequest
{
stringmessage=1;
};
messageEchoResponse
{
stringmessage=1;
};
serviceExample
{
rpcEcho(EchoRequest)returns(EchoResponse);
};
我們定義好了請(qǐng)求、回復(fù)、遠(yuǎn)程服務(wù)的函數(shù)名,通過以下命令就可以生成出接口代碼example.srpc.h
:
protocexample.proto--cpp_out=./--proto_path=./
srpc_generatorprotobuf./example.proto./
我們會(huì)發(fā)現(xiàn),同時(shí)還會(huì)生成出server.pb_skeleton.cc
和client.pb_skeleton.cc
,這是為了方便開發(fā)者的兩個(gè)空文件。我們繼續(xù)一窺究竟,看看生成代碼到底可以實(shí)現(xiàn)什么功能:
//SERVER代碼
classService:publicsrpc::RPCService
{
public:
//用戶需要自行派生實(shí)現(xiàn)這個(gè)函數(shù),與剛才pb生成的是對(duì)應(yīng)的
virtualvoidEcho(EchoRequest*request,EchoResponse*response,
srpc::RPCContext*ctx)=0;
};
//CLIENT代碼
usingEchoDone=std::function<void(EchoResponse*,srpc::RPCContext*)>;
classSRPCClient:publicsrpc::SRPCClient
{
public:
//異步接口
voidEcho(constEchoRequest*req,EchoDonedone);
//同步接口
voidEcho(constEchoRequest*req,EchoResponse*resp,srpc::RPCSyncContext*sync_ctx);
//半同步接口
WFFuture<std::pair>async_Echo(constEchoRequest*req) ;
};
作為一個(gè)高性能RPC框架,SRPC生成的client代碼中包括了:同步、半同步、異步接口,文章開頭展示的是一個(gè)同步接口的做法。
而server的接口就更簡(jiǎn)單了,作為一個(gè)服務(wù)端,我們要做的就是收到請(qǐng)求
->處理邏輯
->返回回復(fù)
,而這個(gè)時(shí)候,框架已經(jīng)把剛才提到的網(wǎng)絡(luò)收發(fā)、解壓縮、反序列化等都給做好了,然后通過生成代碼調(diào)用到用戶實(shí)現(xiàn)的派生service類的函數(shù)邏輯中。
由于一種協(xié)議定義了一種client/server,因此其實(shí)我們同樣可以得到的server類型有第二部分提到過的若干種:SRPCServer/SRPCHttpServer/BRPCServer/TRPCServer/ThriftServer/...
四、 一個(gè)完整的server例子
最后我們用一個(gè)完整的server例子,來看一下用戶調(diào)用接口的使用方式,以及如何跨協(xié)議使用HTTP作為client進(jìn)行調(diào)用。剛才提到,srpc_generator在生成接口的同時(shí),也會(huì)自動(dòng)生成空的用戶代碼,我們這里打開server.pb_skeleton.cc
直接改兩行,即可run起來:
#include"example.srpc.h"
#include"workflow/WFFacilities.h"
usingnamespacesrpc;
staticWFFacilities::WaitGroupwait_group(1);
voidsig_handler(intsigno)
{
wait_group.done();
}
classExampleServiceImpl:publicExample::Service
{
public:
voidEcho(EchoRequest*request,EchoResponse*response,srpc::RPCContext*ctx)override
{
response->set_message("OK");//具體邏輯在這里添加,我們簡(jiǎn)單地回復(fù)一個(gè)OK
}
};
intmain()
{
unsignedshortport=80;//因?yàn)橐獑?dòng)Http服務(wù)
SRPCHttpServerserver;//我們需要構(gòu)造一個(gè)SRPCHttpServer
ExampleServiceImplexample_impl;
server.add_service(&example_impl);
server.start(port);
wait_group.wait();
server.stop();
return0;
}
只要安裝了srpc和workflow,linux下即可通過以下命令編譯出可執(zhí)行文件:
g++-oserverserver.pb_skeleton.ccexample.pb.cc-std=c++11-lsrpc
接下來是激動(dòng)人心的時(shí)刻了,我們用人手一個(gè)的curl
來發(fā)起一個(gè)HTTP請(qǐng)求:
curl-i127.0.0.1:80/Example/Echo-H'Content-Type:application/json'-d'{message:"HelloWorld"}'
五、 解鎖更多
通過這篇文章,相信我們可以清晰地了解到RPC的接口長(zhǎng)什么樣,也可以通過與HTTP協(xié)議互通來理解協(xié)議層次,更重要的是可以知道具體縱向的每個(gè)層次及橫向?qū)Ρ任覀兂R姷拿糠N使用模式都有哪些。但其實(shí),RPC還可以做的事情還有很多,包括內(nèi)部各層次的解耦合設(shè)計(jì)、框架層的功能埋點(diǎn)、外部服務(wù)集群的對(duì)接等等:
如果小伙伴對(duì)更多功能感興趣,歡迎點(diǎn)擊閱讀原文,到Github圍觀,進(jìn)一步了解。
原文標(biāo)題:一文搞懂 RPC 的基本原理和層次架構(gòu)
文章出處:【微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
-
網(wǎng)絡(luò)協(xié)議
+關(guān)注
關(guān)注
3文章
267瀏覽量
21534 -
RPC
+關(guān)注
關(guān)注
0文章
111瀏覽量
11529 -
C++
+關(guān)注
關(guān)注
22文章
2108瀏覽量
73618
原文標(biāo)題:一文搞懂 RPC 的基本原理和層次架構(gòu)
文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論