微服務(wù)RPC框架,一文帶你徹底搞懂 RPC
RPC(Remote Procedure Call),是一個(gè)大家既熟悉又陌生的詞,只要涉及到通信,必然需要某種網(wǎng)絡(luò)協(xié)議。我們很可能用過HTTP,那么RPC又和HTTP有什么區(qū)別呢?RPC還有什么特點(diǎn),常見的選型有哪些?
1. RPC是什么
RPC可以分為兩部分:用戶調(diào)用接口 + 具體網(wǎng)絡(luò)協(xié)議。前者為開發(fā)者需要關(guān)心的,后者由框架來實(shí)現(xiàn)。
舉個(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)這樣:
具體網(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ì)比:
HTTP也是一種網(wǎng)絡(luò)協(xié)議,但包的內(nèi)容是固定的,必須是:請(qǐng)求行 + 請(qǐng)求頭 + 請(qǐng)求體;
RPC是一種自定義網(wǎng)絡(luò)協(xié)議,由具體框架來定,比如SRPC里支持的RPC協(xié)議有:SRPC/thrift/BRPC/tRPC
這些RPC協(xié)議都和HTTP平行,是應(yīng)用層協(xié)議。我們?cè)龠M(jìn)一步思考,HTTP只包含具體網(wǎng)絡(luò)協(xié)議,也可以返回比如我們常見的HTTP/1.1 200 OK,但仿佛沒有用戶調(diào)用接口,這是為什么呢?
這里需要搞清楚,用戶接口的功能是什么?最重要的功能有兩個(gè):
定位要調(diào)用的服務(wù);
讓我們的消息向前/向后兼容;
我們用一個(gè)表格來看一下HTTP和RPC分別是怎么解決的:

因此,HTTP的調(diào)用減少了用戶調(diào)用接口的函數(shù),但是犧牲了一部分消息向前/向后兼容的自由度。但是,開發(fā)者可以根據(jù)自己的習(xí)慣進(jìn)行技術(shù)選型,因?yàn)镽PC和HTTP之間大部分都是協(xié)議互通的!
是不是很神奇?接下來我們看一下RPC的層次架構(gòu),就可以明白為什么不同RPC框架之間、以及RPC和HTTP協(xié)議是如何做到互通的。
2. RPC有什么
我們可以從SRPC的架構(gòu)層次上來看,RPC框架有哪些層,以及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的庫的 !?。?/p>
protobuf:service的定義部分純手工解析
中間那列是具體的網(wǎng)絡(luò)協(xié)議,而各RPC能互通,就是因?yàn)榇蠹覍?shí)現(xiàn)了對(duì)方的“語言”,因此可以協(xié)議互通。
而RPC作為和HTTP并列的層次,第二列和第三列理論上是可以兩兩結(jié)合的,只需要第二列的具體RPC協(xié)議在發(fā)送時(shí),把HTTP相關(guān)的內(nèi)容進(jìn)行特化,不要按照自己的協(xié)議去發(fā),而按照HTTP需要的形式去發(fā),就可以實(shí)現(xiàn)RPC與HTTP互通。
3. 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
我們定義好了請(qǐng)求、回復(fù)、遠(yuǎn)程服務(wù)的函數(shù)名,通過以下命令就可以生成出接口代碼example.srpc.h:
我們一窺究竟,看看生成代碼到底可以實(shí)現(xiàn)什么功能:
作為一個(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
...
4. 一個(gè)完整的server例子
最后我們用一個(gè)完整的 server 例子,來看一下用戶調(diào)用接口的使用方式,以及如何跨協(xié)議使用HTTP作為client進(jìn)行調(diào)用。剛才提到,srpc_generator 在生成接口的同時(shí),也會(huì)自動(dòng)生成空的用戶代碼,我們這里打開 server.pb_skeleton.cc 直接改兩行,即可 run 起來:
只要安裝了srpc,linux下即可通過以下命令編譯出可執(zhí)行文件:
接下來是激動(dòng)人心的時(shí)刻了,我們用人手一個(gè)的curl來發(fā)起一個(gè)HTTP請(qǐng)求:
5. 總結(jié)
今天我們基于 C++ 實(shí)現(xiàn)的開源項(xiàng)目 SRPC 深入分析了 RPC 的基本原理。SRPC 整體代碼風(fēng)格簡(jiǎn)潔、架構(gòu)層次精巧,整體約1萬行代碼,如果你使用 C++,那可能非常適合你用來學(xué)習(xí) RPC 架構(gòu)。
現(xiàn)在C++程序員面臨的競(jìng)爭(zhēng)壓力越來越大。那么,作為一名C++程序員,怎樣努力才能快速成長(zhǎng)為一名高級(jí)的程序員或者架構(gòu)師,或者說一名優(yōu)秀的高級(jí)工程師或架構(gòu)師應(yīng)該有怎樣的技術(shù)知識(shí)體系,這不僅是一個(gè)剛剛踏入職場(chǎng)的初級(jí)程序員,也是工作三五年之后開始迷茫的老程序員,都必須要面對(duì)和想明白的問題。為了幫助大家少走彎路,技術(shù)要做到知其然還要知其所以然。