一、gRPC介紹
gRPC 是在 HTTP/2 之上實(shí)現(xiàn)的 RPC 框架,HTTP/2 是第 7 層(應(yīng)用層)協(xié)議,它運(yùn)行在 TCP(第 4 層 - 傳輸層)協(xié)議之上,相比于傳統(tǒng)的 REST/JSON 機(jī)制有諸多的優(yōu)點(diǎn):
- 基于 HTTP/2 之上的二進(jìn)制協(xié)議(Protobuf 序列化機(jī)制);
- 一個(gè)連接上可以多路復(fù)用,并發(fā)處理多個(gè)請求和響應(yīng);
- 多種語言的類庫實(shí)現(xiàn);
- 服務(wù)定義文件和自動(dòng)代碼生成(.proto 文件和 Protobuf 編譯工具)。
此外,gRPC 還提供了很多擴(kuò)展點(diǎn),用于對框架進(jìn)行功能定制和擴(kuò)展,例如,通過開放負(fù)載均衡接口可以無縫的與第三方組件進(jìn)行集成對接(Zookeeper、域名解析服務(wù)、SLB 服務(wù)等)。
二、gRPC服務(wù)調(diào)用原理
一個(gè)完整的 RPC 調(diào)用流程示例如下:

RPC 請求消息發(fā)送流程
gRPC 默認(rèn)基于 Netty HTTP/2 + PB 進(jìn)行 RPC 調(diào)用,請求消息發(fā)送流程如下所示:

RPC 響應(yīng)接收和處理流程
gRPC 客戶端響應(yīng)消息的接收入口是 NettyClientHandler,它的處理流程如下所示:

并行調(diào)用和異步調(diào)用
要解決串行調(diào)用效率低的問題,有兩個(gè)解決對策:
- 并行服務(wù)調(diào)用,一次 I/O 操作,可以發(fā)起批量調(diào)用,然后同步等待響應(yīng);
- 異步服務(wù)調(diào)用,在同一個(gè)業(yè)務(wù)線程中異步執(zhí)行多個(gè)服務(wù)調(diào)用,不阻塞業(yè)務(wù)線程。
采用并行服務(wù)調(diào)用的偽代碼示例:
ParallelFuture future = ParallelService.invoke(serviceName [], methodName[], args []);
List<Object> results = future.get(timeout);// 同步阻塞式獲取批量服務(wù)調(diào)用的響應(yīng)列表
并行服務(wù)調(diào)用的一種實(shí)現(xiàn)策略如下所示:

異步服務(wù)調(diào)用的工作原理如下:

異步服務(wù)調(diào)用相比于同步服務(wù)調(diào)用有兩個(gè)優(yōu)點(diǎn):
- 化串行為并行,提升服務(wù)調(diào)用效率,減少業(yè)務(wù)線程阻塞時(shí)間。
- 化同步為異步,避免業(yè)務(wù)線程阻塞。
基于 Future-Listener 的純異步服務(wù)調(diào)用代碼示例如下:
xxxService1.xxxMethod(Req);
Future f1 = RpcContext.getContext().getFuture();
Listener l = new xxxListener();
f1.addListener(l);
class xxxListener{
public void operationComplete(F future)
{ // 判斷是否執(zhí)行成功,執(zhí)行后續(xù)業(yè)務(wù)流程}
}
理解誤區(qū)
1、異步服務(wù)就是異步嗎?
實(shí)際上,通信框架基于 NIO 實(shí)現(xiàn),并不意味著服務(wù)框架就支持異步服務(wù)調(diào)用了,兩者本質(zhì)上不是同一個(gè)層面的事情。在 RPC/ 微服務(wù)框架中,引入 NIO 帶來的好處是顯而易見的:
- 所有的 I/O 操作都是非阻塞的,避免有限的 I/O 線程因?yàn)榫W(wǎng)絡(luò)、對方處理慢等原因被阻塞;
- 多路復(fù)用的 Reactor 線程模型:基于 Linux 的 epoll 和 Selector,一個(gè) I/O 線程可以并行處理成百上千條鏈路,解決了傳統(tǒng)同步 I/O 通信線程膨脹的問題。
NIO 只解決了通信層面的異步問題,跟服務(wù)調(diào)用的異步?jīng)]有必然關(guān)系,也就是說,即便采用傳統(tǒng)的 BIO 通信,依然可以實(shí)現(xiàn)異步服務(wù)調(diào)用,只不過通信效率和可靠性比較差而已。
對異步服務(wù)調(diào)用和通信框架的關(guān)系進(jìn)行說明:

用戶發(fā)起遠(yuǎn)程服務(wù)調(diào)用之后,經(jīng)歷層層業(yè)務(wù)邏輯處理、消息編碼,最終序列化后的消息會(huì)被放入到通信框架的消息隊(duì)列中。業(yè)務(wù)線程可以選擇同步等待、也可以選擇直接返回,通過消息隊(duì)列的方式實(shí)現(xiàn)業(yè)務(wù)層和通信層的分離是比較成熟、典型的做法,目前主流的 RPC 框架或者 Web 服務(wù)器很少直接使用業(yè)務(wù)線程進(jìn)行網(wǎng)絡(luò)讀寫。
通過上圖可以看出,采用 NIO 還是 BIO 對上層的業(yè)務(wù)是不可見的,雙方的匯聚點(diǎn)就是消息隊(duì)列,在 Java 實(shí)現(xiàn)中它通常就是個(gè) Queue。業(yè)務(wù)線程將消息放入到發(fā)送隊(duì)列中,可以選擇主動(dòng)等待或者立即返回,跟通信框架是否是 NIO 沒有任何關(guān)系。因此不能認(rèn)為 I/O 異步就代表服務(wù)調(diào)用也是異步的。
2、異步服務(wù)調(diào)用性能肯定更高嗎?
對于 I/O 密集型,資源不是瓶頸,大部分時(shí)間都在同步等應(yīng)答的場景,異步服務(wù)調(diào)用會(huì)帶來巨大的吞吐量提升,資源使用率也可以提高,更加充分的利用硬件資源提升性能。
另外,對于時(shí)延不穩(wěn)定的接口,例如依賴第三方服務(wù)的響應(yīng)速度、數(shù)據(jù)庫操作類等,通常異步服務(wù)調(diào)用也會(huì)帶來性能提升。
但是,如果接口調(diào)用時(shí)延本身都非常?。ɡ绾撩爰墸?,內(nèi)存計(jì)算型,不依賴第三方服務(wù),內(nèi)部也沒有 I/O 操作,則異步服務(wù)調(diào)用并不會(huì)提升性能。能否提升性能,主要取決于業(yè)務(wù)的應(yīng)用場景。
普通 RPC 調(diào)用
普通的 RPC 調(diào)用提供了三種實(shí)現(xiàn)方式:
- 同步阻塞式服務(wù)調(diào)用,通常實(shí)現(xiàn)類是 xxxBlockingStub(基于 proto 定義生成)。
- 異步非阻塞調(diào)用,基于 Future-Listener 機(jī)制,通常實(shí)現(xiàn)類是 xxxFutureStub。
- 異步非阻塞調(diào)用,基于 Reactive 的響應(yīng)式編程模式,通常實(shí)現(xiàn)類是 xxxStub。
Streaming 模式服務(wù)調(diào)用
gRPC 服務(wù)調(diào)用支持同步和異步方式,同時(shí)也支持普通的 RPC 和 streaming 模式,可以最大程度滿足業(yè)務(wù)的需求。
對于 streaming 模式,可以充分利用 HTTP/2.0 協(xié)議的多路復(fù)用功能,實(shí)現(xiàn)在一條 HTTP 鏈路上并行雙向傳輸數(shù)據(jù),有效的解決了 HTTP/1.X 的數(shù)據(jù)單向傳輸問題,在大幅減少 HTTP 連接的情況下,充分利用單條鏈路的性能,可以媲美傳統(tǒng)的 RPC 私有長連接協(xié)議:更少的鏈路、更高的性能:

gRPC 的網(wǎng)絡(luò) I/O 通信基于 Netty 構(gòu)建,服務(wù)調(diào)用底層統(tǒng)一使用異步方式,同步調(diào)用是在異步的基礎(chǔ)上做了上層封裝。因此,gRPC 的異步化是比較徹底的,對于提升 I/O 密集型業(yè)務(wù)的吞吐量和可靠性有很大的幫助。
三、gRPC線程模型
影響 RPC 框架性能的三個(gè)核心要素如下:
- I/O 模型:用什么樣的通道將數(shù)據(jù)發(fā)送給對方,BIO、NIO 或者 AIO,IO 模型在很大程度上決定了框架的性能;
- 協(xié)議:采用什么樣的通信協(xié)議,Rest+ JSON 或者基于 TCP 的私有二進(jìn)制協(xié)議,協(xié)議的選擇不同,性能模型也不同,相比于公有協(xié)議,內(nèi)部私有二進(jìn)制協(xié)議的性能通??梢员辉O(shè)計(jì)的更優(yōu);
- 線程:數(shù)據(jù)報(bào)如何讀???讀取之后的編解碼在哪個(gè)線程進(jìn)行,編解碼后的消息如何派發(fā),通信線程模型的不同,對性能的影響也非常大。

gRPC 線程模型
消息的序列化和反序列化均由 gRPC 線程負(fù)責(zé),而沒有在 Netty 的 Handler 中做 CodeC,原因如下:Netty4 優(yōu)化了線程模型,所有業(yè)務(wù) Handler 都由 Netty 的 I/O 線程負(fù)責(zé),通過串行化的方式消除鎖競爭,原理如下所示:

如果大量的 Handler 都在 Netty I/O 線程中執(zhí)行,一旦某些 Handler 執(zhí)行比較耗時(shí),則可能會(huì)反向影響 I/O 操作的執(zhí)行,像序列化和反序列化操作,都是 CPU 密集型操作,更適合在業(yè)務(wù)應(yīng)用線程池中執(zhí)行,提升并發(fā)處理能力。因此,gRPC 并沒有在 I/O 線程中做消息的序列化和反序列化。
改進(jìn)點(diǎn)思考
1、時(shí)間可控的接口調(diào)用直接在 I/O 線程上處理
gRPC 采用的是網(wǎng)絡(luò) I/O 線程和業(yè)務(wù)調(diào)用線程分離的策略,大部分場景下該策略是最優(yōu)的。但是,對于那些接口邏輯非常簡單,執(zhí)行時(shí)間很短,不需要與外部網(wǎng)元交互、訪問數(shù)據(jù)庫和磁盤,也不需要等待其它資源的,則建議接口調(diào)用直接在 Netty /O 線程中執(zhí)行,不需要再投遞到后端的服務(wù)線程池。避免線程上下文切換,同時(shí)也消除了線程并發(fā)問題。
例如提供配置項(xiàng)或者接口,系統(tǒng)默認(rèn)將消息投遞到后端服務(wù)調(diào)度線程,但是也支持短路策略,直接在 Netty 的 NioEventLoop 中執(zhí)行消息的序列化和反序列化、以及服務(wù)接口調(diào)用。
2、減少鎖競爭
當(dāng)前 gRPC 的線程切換策略如下:

優(yōu)化之后的 gRPC 線程切換策略:

通過線程綁定技術(shù)(例如采用一致性 hash 做映射), 將 Netty 的 I/O 線程與后端的服務(wù)調(diào)度線程做綁定,1 個(gè) I/O 線程綁定一個(gè)或者多個(gè)服務(wù)調(diào)用線程,降低鎖競爭,提升性能。
四、gRPC 安全性設(shè)計(jì)
RPC 調(diào)用安全主要涉及如下三點(diǎn):
- 個(gè)人 / 企業(yè)敏感數(shù)據(jù)加密:例如針對個(gè)人的賬號(hào)、密碼、手機(jī)號(hào)等敏感信息進(jìn)行加密傳輸,打印接口日志時(shí)需要做數(shù)據(jù)模糊化處理等,不能明文打印;
- 對調(diào)用方的身份認(rèn)證:調(diào)用來源是否合法,是否有訪問某個(gè)資源的權(quán)限,防止越權(quán)訪問;
- 數(shù)據(jù)防篡改和完整性:通過對請求參數(shù)、消息頭和消息體做簽名,防止請求消息在傳輸過程中被非法篡改。
敏感數(shù)據(jù)加密傳輸
1. 基于 SSL/TLS 的通道加密
2. 針對敏感數(shù)據(jù)的單獨(dú)加密
有些 RPC 調(diào)用并不涉及敏感數(shù)據(jù)的傳輸,或者敏感字段占比較低,為了最大程度的提升吞吐量,降低調(diào)用時(shí)延,通常會(huì)采用 HTTP/TCP + 敏感字段單獨(dú)加密的方式,既保障了敏感信息的傳輸安全,同時(shí)也降低了采用 SSL/TLS 加密通道帶來的性能損耗,對于 JDK 原生的 SSL 類庫,這種性能提升尤其明顯。
它的工作原理如下所示:

通常使用 Handler 攔截機(jī)制,對請求和響應(yīng)消息進(jìn)行統(tǒng)一攔截,根據(jù)注解或者加解密標(biāo)識(shí)對敏感字段進(jìn)行加解密,這樣可以避免侵入業(yè)務(wù)。
認(rèn)證和鑒權(quán)
1. 身份認(rèn)證

2. 權(quán)限管控
在 RPC 調(diào)用領(lǐng)域比較流行的是基于 OAuth2.0 的權(quán)限認(rèn)證機(jī)制,它的工作原理如下:

數(shù)據(jù)完整性和一致性
利用消息摘要可以保障數(shù)據(jù)的完整性和一致性,它的特點(diǎn)如下:
- 單向 Hash 算法,從明文到密文的不可逆過程,即只能加密而不能解密;
- 無論消息大小,經(jīng)過消息摘要算法加密之后得到的密文長度都是固定的;
- 輸入相同,則輸出一定相同。
目前常用的消息摘要算法是 SHA-1、MD5 和 MAC,MD5 可產(chǎn)生一個(gè) 128 位的散列值。 SHA-1 則是以 MD5 為原型設(shè)計(jì)的安全散列算法,可產(chǎn)生一個(gè) 160 位的散列值,安全性更高一些。MAC 除了能夠保證消息的完整性,還能夠保證來源的真實(shí)性。