知乎: 淺談 RPC 和 REST: SOAP, gRPC, REST
目前知道的三種主流的Web服務實現方案為:
- REST:表象化狀態轉變 (軟件架構風格)
- SOAP:簡單對象訪問協議
- XML-RPC:遠程過程調用協議
1. RPC
XML-RPC:一個遠程過程調用(remote procedure call,RPC) 的分布式計算協議,通過XML將調用函數封裝,並使用HTTP協議作為傳送機制。后來在新的功能不斷被引入下,這個標准慢慢演變成為今日的SOAP協定。XML-RPC協定是已登記的專利項目。XML-RPC透過向裝置了這個協定的服務器發出HTTP請求。發出請求的用戶端一般都是需要向遠端系統要求呼叫的軟件。
RPC的目的,是讓用戶在本地調用遠程的方法。對用戶來說這個調用是透明的,並不需要關心這個調用的方法是部署哪里。
1.1. 通訊原理
怎么實現遠程調用像本地調用一樣呢?RPC 模式分為三層,RPCRuntime 負責最底層的網絡傳輸,Stub 處理客戶端和服務端約定好的語法、語義的封裝和解封裝,這些調用遠程的細節都被這兩層搞定了,用戶和服務器這層就只要負責處理業務邏輯,調用本地 Stub 就可以調用遠程。
- 遠程服務之間建立通訊協議
- 尋址:服務器(如主機或IP地址)以及特定的端口,方法的名稱名稱是什么
- 通過序列化和反序列化進行數據傳遞
- 將傳遞過來的數據通過java反射原理定位接口方法和參數
- 暴露服務:用map將尋址的信息暴露給遠方服務(提供一個endpoint URI或者一個前端展示頁面)
- 多線程並發請求業務
1.2. 設計模式:ServerProxy
使用代理模式解決,生成一個代理對象,而這個代理對象的內部,就是通過httpClient來實現RPC遠程過程調用的。以此方式來屏蔽對http的操作,而外觀上看上去就像是本地功能調用類似。
1.3. 多種實現方案
RPC是一種模式,http也是RPC實現的一種方式。
論復雜度,dubbo/hessian用起來是超級簡單的。
- 調用簡單,真正提供了類似於調用本地方法一樣調用接口的功能 。
- 參數返回值簡單明了 參數和返回值都是直接定義在jar包里的,不需要二次解析。
- 輕量,沒有多余的信息。
- 便於管理,基於dubbo的注冊中心。
所以,常常有人抱怨,XML-RPC的效率太低,XML書寫或轉換都太復雜,性能不行。但實際上,單就性能而論,有JSON-RPC,它比XML-RPC輕。且RPC可以直接使用socket通訊而不是http,性能反而更優。
2. SOAP(不推薦)
1998 年 XML 1.0 發布,被 W3C (World Wide Web Consortium) 推薦為標准的描述語言。同年,SOAP 也完成了初版設計。SOAP (Simple Object Access Protocol) 簡單對象訪問協議,在 1998 年因為微軟 XML-RPC 的原因,還沒有公之於眾,一直到 2003 年 6 月的 SOAP 1.2 版本發布,才被 W3C 推薦。
SOAP 是基於文本 XML 的 一種應用協議。隨着當年 SOA (Service Oriented Architecture) 的走紅,提倡將一個大的軟件拆分成多個不同的小的服務,SOAP 在服務之間的遠程調用大有用武之地。
2.1. 協議約定
SOAP 的協議約定用的是 WSDL (Web Service Description Language) ,這是一種 Web 服務描述語言,在服務的客戶端和服務端開發者不用面對面交流,只要用的是 WSDL 定義的格式,客戶端知道了 WSDL 文件,就知道怎么去封裝請求,調用服務。
<wsdl:types>
<xsd:schema targetNamespace="http://www.task.io/management">
<xsd:complexType name="task">
<xsd:element name="name" type="xsd:string"></xsd:element>
<xsd:element name="type" type="xsd:string"></xsd:element>
<xsd:element name="priority" type="xsd:int"></xsd:element>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
這只是一個對象類型的定義,一套完整的 WSDL 還會有消息結構體的定義,然后將信息綁定到 SOAP 請求的 body 里,然后編寫成 service,具體這里就不展開了。
WSDL 有可以自動生成 Stub 的工具,客戶端可以直接通過自動生成的 Stub 去調用服務端。
2.2. 傳輸協議
SOAP 是用 HTTP 進行傳輸的,有個信封的概念,信息就像是一封信,有 Header 和 Body,SOAP 的請求和回復都放在信封里,進行傳遞。
POST /addTask HTTP/1.1
Host: www.task.io
Content-Type: application/soap+xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Header>
<m:Trans xmlns:m="http://www.w3schools.com/transaction/"
soap:mustUnderstand="1">1234
</m:Trans>
</soap:Header>
<soap:Body xmlns:m="http://www.task.io/management">
<m:addTask>
<task>
<name>Write a blog article</name>
<type>Writing</type>
<priority>1</priority>
</task>
</m:addTask>
</soap:Body>
</soap:Envelope>
2.3. 服務發現
SOAP 的服務發現用的是 UDDI(Universal Description, Discovery, Integration) 統一描述發現集成,相當於一個注冊中心,服務提供方將 WSDL 文件發布到注冊中心,使用方可以到這個注冊中心查找。
SOAP 在當時也是風靡一時,主要有這些優點:
- 協議約定面向對象,更貼合業務邏輯的應用場景。
- 服務定義清楚,在 WSDL 能清楚了解到所有服務。
- 格式不用完全一致,比如上面那個請求里 name, type, priority 的順序不用完全跟服務端的 WSDL 對應。版本更新上,客戶端可以先增加新的項,服務端可以之后再更新。
- 使用 WS Security 所為安全標准,安全性較高。
- SOAP 是 面向動作 的,支持比較復雜的動作,比如
ADD
,MINUS
當然,現在新的軟件開發,用到 SOAP 的越來越少了,可見 SOAP 也有很多不足:
-
遠程調用速度慢,效率低。
因為以 XML 作為數據格式,除了主要傳輸的數據之外,有較多冗余用在定義格式上,占用帶寬,並且對於 XML 的序列化和解析的速度也比較慢。
-
協議約定 WSDL 比較復雜,要經過好幾個環節才能搞定。
-
SOAP 多數用的是POST,通常是 POST 加上動作,比如 POST CreateTask, POST DeleteTask。而多數用 POST 的原因是 GET 請求最大長度限制較多,而 SOAP 需要把數據加上 SOAP 標准化的格式,請求數據比較大,超過 GET 的限制。
-
SOAP 的業務狀態大多是維護在服務端的,比如說分頁,服務端會記住用戶在哪個頁面上,在企業軟件中,客戶端和服務端比較平衡的情況下是沒有問題的,但是在失衡情況下,比如說客戶端請求大大超過服務端時,服務端維護所有狀態的成本太高,影響並發量。
可以說,SOAP是基於 XML-RPC
進行的二次封裝。所以,XML-RPC 的缺點全部被SOAP繼承了(且無法修改),反而,本意的優勢(簡化服務端開發所做的封裝),隨着技術的更新,越來越被新的技術替代掉了。
3. gRPC
像 SOAP 這類基於文本類的 RPC 框架,速度上都是有先天不足的。為了有比較好的性能,還是得用二進制的方式進行遠程調用。gRPC 是現在最流行的二進制 RPC 框架之一。2015 年由 Google 開源,在發布后迅速得到廣泛關注。
3.1. 協議約定
gRPC 的協議是 Protocol Buffers,是一種壓縮率極高的序列化協議,效率甩 XML,JSON 好幾條街。Google 在 2008 年開源了 Protocol Buffers,支持多種編程語言,所以 gRPC 支持客戶端與服務端可以用不同語言實現。
3.2. 傳輸協議
在 JAVA 技術棧中,gRPC 的數據傳輸用的是 Netty Channel(注意不是http), Netty 是一個高效的基於異步 IO 的網絡傳輸架構。Netty Channel 中,每個 gRPC 請求封裝成 HTTP 2.0 的 Stream。
基於 HTTP 2.0 是 gRPC 一個很大的優勢,可以定義四種不同的服務方法:單向 RPC,客戶端流式 RPC,服務端流式 RPC,雙向流式 RPC。
3.3. 服務發現
gRPC 本身沒有提供服務發現的機制,需要通過其他組件。一個比較高性能的服務發現和負載均衡器是 Envoy,可以靈活配置轉發規則,有興趣的可以去了解下。
4. RESTful
gRPC 更多的是用在微服務集群內部,服務與服務之間的通信,服務與客戶端之間的通信,REST 可以說是現在的主流。
REST:表征狀態轉移(Representational State Transfer),采用Web服務使用標准的 HTTP 方法 (GET/PUT/POST/DELETE) 將所有 Web 系統的服務抽象為資源,REST從資源的角度來觀察整個網絡,分布在各處的資源由URI確定,而客戶端的應用通過URI來獲取資源的表征。
Http協議所抽象的get,post,put,delete就好比數據庫中最基本的增刪改查,而互聯網上的各種資源就好比數據庫中的記錄(可能這么比喻不是很好),對於各種資源的操作最后總是能抽象成為這四種基本操作,在定義了定位資源的規則以后,對於資源的操作通過標准的Http協議就可以實現,開發者也會受益於這種輕量級的協議。
REST是一種軟件架構風格而非協議也非規范,是一種針對網絡應用的開發方式,可以降低開發的復雜性,提高系統的可伸縮性。
4.1. 常見的設計錯誤
4.1.1. 最常見的一種設計錯誤,就是URI包含動詞
因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。
最常見的一種設計錯誤,就是URI包含動詞。因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。
舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然后用GET方法表示show。
如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:
POST /accounts/1/transfer/500/to/2
正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
4.1.2. 另一個設計誤區,就是在URI中加入版本號
http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo
因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該采用同一個URI。版本號可以在HTTP請求頭信息的Accept字段中進行區分:
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
4.1.3. 避免多級 URL
常見的情況是,資源需要多級分類,因此很容易寫出多級的 URL,比如獲取某個作者的某一類文章。
GET /authors/12/categories/2
這種 URL 不利於擴展,語義也不明確,往往要想一會,才能明白含義。更好的做法是,除了第一級,其他級別都用查詢字符串表達。
GET /authors/12?categories=2
下面是另一個例子,查詢已發布的文章。你可能會設計成下面的 URL。
GET /articles/published
查詢字符串的寫法明顯更好。
GET /articles?published=true
4.2. 傳輸協議
REST 是基於 HTTP 的文本類傳輸方式,與 SOAP 的 XML 相比,REST 用的是 Json,格式更加簡單易懂。
4.3. 服務發現
RESTful API 的服務發現有很多組件,比如說 Eureka,可以作為服務注冊中心,也能用來做負載均衡和容錯。