在本篇文章中,我們將深入探討gRPC與REST,這兩種現今最常見的API建立方法。
I will start with a short characteristic of both tools — what they are and what they can offer. Then I will compare them according to seven categories, in my opinion, most crucial for modern-day systems.
以下是將要討論的類別:
- 基礎HTTP協議
- 支援的數據格式
- 數據大小
- 吞吐量
- 定義
- 易採用性
- 工具支持
為何選擇
當人們聽到“API”時,很可能立刻想到REST API。然而,REST只是眾多API構建方法之一,並非適用於所有情境的萬能解決方案。還有其他方法,如RPC(遠程過程調用)即是其中之一,而gRPC則是最成功的RPC框架。
儘管gRPC技術相當成熟且高效,但仍被視為新興技術,因此在普及度上不及REST,儘管它在某些用例中非常便利。
撰寫此博文的主要目的在於推廣gRPC,並指出其在哪些情境下能大放異彩。
何謂REST?
REST,或稱表現層狀態轉換,可能是創建任何類型API應用程序最常見的方式。它使用HTTP作為底層通信媒介。正因如此,它能受益於HTTP的所有優勢,例如緩存。
此外,由於其本質上的無狀態性,REST允許客戶端與服務器之間輕鬆分離。客戶端只需了解服務器所暴露的接口即可有效與其通信,且在任何意義上都不依賴於服務器的實現。客戶端與服務器之間的通信基於請求和響應模式,每個請求都是一個典型的HTTP請求。
REST既不是協議也不是工具(在某種程度上):它是一種構建應用程序的架構方法。遵循REST方法的服務稱為RESTful服務。作為一種架構,它對用戶施加了若干約束。特別是:
- 客戶端-服務器通信
- 無狀態通信
- 緩存
- 統一接口
- 分層系統
- 按需代碼
REST的兩個關鍵概念是:
- 端點:代表特定資源的唯一URL(統一資源定位符);可視為訪問互聯網上特定操作或數據元素的一種方式
- 資源:特定URL下可用的特定數據片段
此外,有一個名為Richardson成熟度模型的描述——該模型用於描述REST API的“專業”程度。它根據特定API所具備的特徵,將REST API劃分為三個等級(或四個,取決於你是否計算等級0)。
其中一個特徵是,REST端點應在URL中使用名詞,並使用正確的HTTP請求方法來管理其資源。
- 例如:DELETE user/1 而非 GET user/deleteById/1
至於HTTP方法及其相應動作,如下所示:
- GET — 檢索特定資源或一組資源
- POST — 創建新資源
- PUT — 完整更新資源
- PATCH — 部分更新特定資源
- DELETE — 根據ID刪除特定資源
成熟度模型還規定了更多內容;例如,一個稱為HyperMedia的概念。HyperMedia將數據的展示與客戶端可執行的操作控制相結合。
A full description of the maturity model is out of the scope of this blog — you can read more about it here.
免責聲明:本段提到的許多內容比此處描述的更為細緻。REST是一個相當廣泛的主題,值得用一系列文章來探討。儘管如此,這裡所述均符合公認的最佳REST實踐。
什麼是gRPC?
這是遠程過程調用(RPC)這一相對古老概念的又一實現。由Google的團隊打造,因此名稱中帶有「g」。它可能是最現代且高效的RPC工作工具,同時也是CNCF孵化項目。
gRPC採用Google的Protocol Buffers作為序列化格式,並利用HTTP/2作為傳輸媒介,儘管gRPC也能與JSON作為數據層配合使用。
gRPC的基本構件包括:
- 方法:gRPC的基本構件,每個方法都是一個遠程過程調用,接收輸入並返回輸出。它執行單一操作,並在所選編程語言中進一步實現。目前,gRPC支持四種類型的方法:
- 一元:經典的請求-響應模型,方法接收輸入並返回輸出。
- 服務器流式:方法接受一條消息作為輸入,返回一系列消息作為輸出。gRPC保證在單個RPC調用內的消息順序。
- 客戶端流式:方法接收一系列消息作為輸入,處理它們直至無消息剩餘,然後返回單條消息作為輸出。與上述類似,gRPC保證在單個RPC調用內的消息順序。
- 雙向串流:此方法以串流作為輸入並返回串流作為輸出,有效地使用兩個讀寫串流。兩個串流獨立運作,並在串流層級保持消息順序。
- 服務:代表一組方法——每個方法在其服務內必須有唯一的名字。服務還描述了安全性、超時或重試等功能。
- 消息:代表方法的輸入或輸出的對象。
gRPC API定義以.proto文件形式編寫,其中包含上述所有三個基本構建塊。此外,gRPC提供了一個協議緩衝區編譯器,它從我們的.proto文件生成客戶端和服務代碼。
我們可以按照自己的方式實現服務器端方法。我們必須遵守API的輸入輸出合約。
在客戶端,有一個稱為客戶端(或存根)的對象——類似於HTTP客戶端。它知道服務器上的所有方法,並僅處理調用遠程過程及返回其響應。
比較
底層HTTP協議
這是第一個類別,可能也是最重要的,因為它的影響在其他方面也可能顯而易見。
一般來說,REST是基於請求-響應的,並使用HTTP/1.1作為傳輸媒介。我們必須使用不同的協議,如WebSocket(更多關於它們這裡)或任何類型的串流或更持久的連接。
我們亦可編寫一些臨時的程式碼,使REST看起來像是串流傳輸。此外,使用HTTP/1.1的REST每個請求-回應交換都需要一個連接。這種做法對於長時間運行的請求或當我們的網路能力有限時可能會有問題。
當然,我們可以使用HTTP/2來建立REST風格的API;然而,並非所有伺服器和庫都支援HTTP/2。因此,問題可能會在其他地方出現。
另一方面,gRPC僅使用HTTP/2。它允許通過單一TCP連接發送多個請求-回應對。這種做法對我們的應用程式來說可能是一個相當顯著的性能提升。
- 結果: gRPC略勝一籌
支援的資料格式
假設在默認情況下,REST API使用HTTP/1.1,那麼它可以支援多種格式。
REST通常不對消息格式和風格施加任何限制。基本上,任何可以序列化為普通文本的格式都是有效的。我們可以使用任何在特定情境下最適合的格式。
在REST應用中傳送資料最流行的格式無疑是JSON。XML位居第二,這是因為有大量較舊/遺留的應用程式。
然而,當使用REST與HTTP/2時,則僅支援二進制交換格式。在這種情況下,我們可以使用Protobuf或Avro。當然,這種做法可能會有其缺點,但更多關於這一點將在後續的觀點中討論。
與此同時,gRPC僅支援兩種資料交換格式:
- Protobuf — 默認情況下
- JSON — 當您需要與舊版API整合
若您選擇使用JSON,則gRPC將使用JSON作為訊息的編碼格式,並使用GSON作為訊息格式。此外,使用JSON將需要進行一些額外的配置。以下是gRPC文件關於如何進行此配置的說明。
- 結果:REST在此勝出,因為它支援更多格式。
資料大小
預設情況下,gRPC使用二進制資料交換格式,這極大地減少了透過網路傳送的訊息大小:研究指出,大約減少了40-50%的位元組大小 — 根據我之前一個專案的經驗,甚至減少了50-70%。
上述文章提供了一個相對深入的JSON與Protobuf之間的大小比較。作者還提供了一個工具來生成JSON和二進制文件。因此,您可以重新運行他的實驗並比較結果。
文章中的物件相對簡單。然而,一般規則是 — JSON中嵌入的物件越多,結構越複雜,其大小將比Protobuf更為龐大。Protobuf相比JSON有50%的大小優勢,這是一個良好的基準。
透過使用二進制交換格式進行REST,差異可以被最小化或消除。然而,這並不是最常見也不是最受支持的RESTful API實現方式,因此可能會出現其他問題。
- 結果:在默認情況下,gRPC勝出;在兩者都使用二進制數據格式的情況下,平局。
吞吐量
再次,對於REST,一切取決於底層的HTTP協議和服務器。
在默認情況下,基於HTTP/1.1的REST,即使是最性能的服務器也無法擊敗gRPC的性能,尤其是在使用JSON時添加序列化和反序列化開銷。儘管當我們切換到HTTP/2時,差異似乎有所減少。
至於最大吞吐量,在兩種情況下,HTTP都是傳輸媒介,因此它有潛力擴展到無限。因此,一切取決於我們使用的工具以及我們如何精確地操作我們的應用程序,因為設計上沒有限制。
- 結果:在默認情況下,gRPC;在兩者都使用二進制數據和HTTP/2的情況下,平局或gRPC略勝一籌。
定義
在這一部分,我將描述我們如何在兩種方法中定義我們的消息和服務。
在大多數REST應用程序中,我們只是將我們的請求和響應聲明為類、對象,或任何特定語言支持的結構。然後我們依賴提供的庫來序列化和反序列化JSON/XML/YAML,或我們需要的任何格式。
此外,目前正在努力开发能够根据Swagger的REST API定义生成所选编程语言代码的工具。然而,这些工具似乎仍处于alpha版本,因此可能存在一些错误和小问题,使其难以使用。
对于REST应用程序而言,二进制格式与非二进制格式之间的差异不大,因为两者的规则大体相同。对于二进制格式,我们只需按照特定格式的要求定义所有内容。
另外,我们通过底层库或框架的方法或注释来定义我们的REST服务。该工具还负责将其与其他配置一起暴露给外部世界。
至于gRPC,我们默认使用Protobuf,并且实际上是唯一编写定义的方式。我们必须声明所有内容:消息、服务和方法在.proto文件中,因此事情相当直接。
然后我们使用gRPC提供的工具为我们生成代码,我们只需实现我们的方法。之后,一切应该按照预期工作。
此外,Protobuf支持导入,因此我们可以以相对简单的方式将设置分散到多个文件中。
- 结果:这里没有赢家,只有描述和我的一条建议:选择最适合你的方法。
采用的便利性
在这一部分,我将比较现代编程语言中每种方法的库/框架支持。
一般而言,在我短暫的軟體工程師生涯中接觸到的每種程式語言(如Java、Scala、Python)都至少擁有三個主要的庫/框架用於開發REST風格的應用程式,更不用說還有數量相仿的庫專門用於將JSON解析成物件/類別。
此外,由於REST預設使用人類可讀的格式,對於新手來說,它更容易進行除錯和操作。這也能影響新功能的交付速度,並助你對抗程式碼中出現的錯誤。
簡而言之,對於REST風格應用程式的支援至少是非常好的。
在Scala中,我們甚至有一個名為tapir的工具——我有幸在一段時間內成為其維護者之一。Tapir讓我們能夠抽象化HTTP伺服器,並編寫將在多個伺服器上運作的端點。
gRPC本身為超過8種流行的程式語言提供了客戶端庫。這通常已經足夠,因為這些庫包含了建立gRPC API所需的一切。此外,我還知道有提供更高層次抽象的庫,例如Java通過Spring Boot Starter,以及Scala。
另一點是,REST 如今被視為全球標準,是建立服務的入門點,而 RPC 和特別是 gRPC 雖然已有一定歷史,但仍被視為新奇事物。
- 結果:由於 REST 的廣泛採用及其周圍眾多的庫和框架
工具支援
上述已涵蓋庫、框架和整體市場份額,因此在這部分,我想探討圍繞這兩種風格的工具。這意味著用於測試、性能/壓力測試和文檔的工具。
自動化測試/測試
首先,在 REST 的情況下,用於建立自動化測試的工具內建於不同的庫和框架中,或者是由這個唯一目的建立的單獨工具,如REST-assured。
至於 gRPC,我們可以生成一個存根並用於測試。如果我們想要更嚴格,可以使用生成的客戶端作為單獨的應用程序,並將其作為我們在真實服務上測試的基礎。
至於外部工具對 gRPC 的支持,我所知的有:
- Postman 應用程式支援 gRPC
- JetBrains 的 HTTP 客戶端,用於其 IDE,也可以通過一些最小配置支援 gRPC。
- 結果一: REST取得勝利;然而,情況似乎對gRPC有所改善。
效能測試
在此,REST具有顯著優勢,因為像JMeter或Gatling這樣的工具使得對REST API進行壓力測試相對容易。
不幸的是,gRPC並未獲得同樣的支持。我知道Gatling的開發者已經在當前版本中包含了gRPC插件,因此情況似乎正在好轉。
然而,直到目前為止,我們只有一個非官方的插件和庫,稱為ghz。這些都很好,只是支持程度與REST相比有所不及。
- 結果二: REST再次獲勝;然而,gRPC的情況似乎也在改善,呵呵。
文件
在API文件方面,勝利再次屬於REST,使用OpenAPI和Swagger在業界被廣泛採用並成為實際標準。幾乎所有REST庫都能以最小的努力或直接開箱即用地提供Swagger文件。
不幸的是,gRPC並沒有類似的東西。
然而,問題在於gRPC是否真的需要這樣一個工具。相較於REST,gRPC的設計更為描述性,因此額外的文檔工具可能
一般而言,.proto文件隨著我們API的描述,比起負責編寫REST API代碼的部分更為宣告性和簡潔,或許從gRPC中已不需要更多的文檔。答案留待您決定。
- 結果三:REST勝出;然而,關於gRPC文檔的問題仍是開放的。
總體結果:
A significant victory for REST
總結
最終的比分表如下所示。

兩種風格的得分平分秋色,各有三項勝利,還有一個類別未有明確贏家。
沒有萬能鑰匙:只需考慮哪些類別對您的應用程式最為重要,然後選擇在多數類別中勝出的方法——至少這是我的建議。
至於我的偏好,若條件允許,我會嘗試使用gRPC,因為它在我上一個專案中表現出色。相較於傳統的REST,它可能是更好的選擇。
如果您在選擇REST還是gRPC,或是遇到其他技術問題時需要幫助,請隨時告訴我。我或許能提供協助。
感謝您的閱讀。