上一節我們講了 RPC 的經典模型和設計要點,并用最早期的 ONC RPC 為例子,詳述了具體的實作,
ONC RPC 存在哪些問題?
ONC RPC 將客戶端要發送的引數,以及服務端要發送的回復,都壓縮為一個二進制串,這樣固然能夠解決雙方的協議約定問題,但是存在一定的不方便,
首先,需要雙方的壓縮格式完全一致,一點都不能差,一旦有少許的差錯,多一位,少一位或者錯一位,都可能造成無法解壓縮,當然,我們可以用傳輸層的可靠性以及加入校驗值等方式,來減少傳輸程序中的差錯,
其次,協議修改不靈活,如果不是傳輸程序中造成的差錯,而是客戶端因為業務邏輯的改變,添加或者洗掉了欄位,或者服務端添加或者洗掉了欄位,而雙方沒有及時通知,或者線上系統沒有及時升級,就會造成解壓縮不成功,
因而,當業務發生改變,需要多傳輸一些引數或者少傳輸一些引數的時候,都需要及時通知對方,并且根據約定好的協議檔案重新生成雙方的 Stub 程式,自然,這樣靈活性比較差,
如果僅僅是溝通的問題也還好解決,其實更難弄的還有版本的問題,比如在服務端提供一個服務,引數的格式是版本一的,已經有 50 個客戶端在線上呼叫了,現在有一個客戶端有個需求,要加一個欄位,怎么辦呢?這可是一個大工程,所有的客戶端都要適配這個,需要重新寫程式,加上這個欄位,但是傳輸值是 0,不需要這個欄位的客戶端很“冤”,本來沒我啥事兒,為啥讓我也忙活?
最后,ONC RPC 的設計明顯是面向函式的,而非面向物件,而當前面向物件的業務邏輯設計與實作方式已經成為主流,
這一切的根源就在于壓縮,這就像平時我們愛用縮略語,如果是籃球愛好者,你直接說 NBA,他馬上就知道什么意思,但是如果你給一個大媽說 NBA,她可能就不知所云,
所以,這種 RPC 框架只能用于客戶端和服務端全由一撥人開發的場景,或者至少客戶端和服務端的開發人員要密切溝通,相互合作,有大量的共同語言,才能按照既定的協議順暢地進行作業,
XML 與 SOAP
但是,一般情況下,我們做一個服務,都是要提供給陌生人用的,你和客戶不會經常溝通,也沒有什么共同語言,就像你給別人介紹 NBA,你要說美國職業籃球賽,這樣不管他是干啥的,都能聽得懂,
放到我們的場景中,對應的就是用文本類的方式進行傳輸,無論哪個客戶端獲得這個文本,都能夠知道它的意義,
一種常見的文本類格式是 XML,我們這里舉個例子來看,
<?xml version="1.0" encoding="UTF-8"?>
<geek:purchaseOrder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:geek="http://www.example.com/geek">
<order>
<date>2018-07-01</date>
<className>趣談網路協議</className>
<Author>劉超</Author>
<price>68</price>
</order>
</geek:purchaseOrder>
我這里不準備詳細講述 XML 的語法規則,但是你相信我,看完下面的內容,即便你沒有學過 XML,也能一看就懂,這段 XML 描述的是什么,不像全面的二進制,你看到的都是 010101,不知所云,
有了這個,剛才我們說的那幾個問題就都不是問題了,
首先,格式沒必要完全一致,比如如果我們把 price 和 author 換個位置,并不影響客戶端和服務端決議這個文本,也根本不會誤會,說這個作者的名字叫 68,
如果有的客戶端想增加一個欄位,例如添加一個推薦人欄位,只需要在上面的檔案中加一行:
<recommended> Gary </recommended>
對于不需要這個欄位的客戶端,只要不決議這一行就是了,只要用簡單的處理,就不會出現錯誤,
另外,這種表述方式顯然是描述一個訂單物件的,是一種面向物件的、更加接近用戶場景的表示方式,
既然 XML 這么好,接下來我們來看看怎么把它用在 RPC 中,
傳輸協議問題
我們先解決第一個,傳輸協議的問題,
基于 XML 的最著名的通信協議就是 SOAP 了,全稱簡單物件訪問協議(Simple Object Access Protocol),它使用 XML 撰寫簡單的請求和回復訊息,并用 HTTP 協議進行傳輸,
SOAP 將請求和回復放在一個信封里面,就像傳遞一個郵件一樣,信封里面的信分抬頭和正文,
POST /purchaseOrder HTTP/1.1 Host: www.geektime.com 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.geektime.com/perchaseOrder">
<m:purchaseOrder">
<order>
<date>2018-07-01</date>
<className>趣談網路協議</className>
<Author>劉超</Author>
<price>68</price>
</order>
</m:purchaseOrder>
</soap:Body>
</soap:Envelope>
HTTP 協議我們學過,這個請求使用 POST 方法,發送一個格式為 application/soap + xml 的 XML 正文給 www.geektime.com,從而下一個單,這個訂單封裝在 SOAP 的信封里面,并且表明這是一筆交易(transaction),而且訂單的詳情都已經寫明了,
協議約定問題
接下來我們解決第二個問題,就是雙方的協議約定是什么樣的?
因為服務開發出來是給陌生人用的,就像上面下單的那個 XML 檔案,對于客戶端來說,它如何知道應該拼裝成上面的格式呢?這就需要對于服務進行描述,因為呼叫的人不認識你,所以沒辦法找到你,問你的服務應該如何呼叫,
當然你可以寫檔案,然后放在官方網站上,但是你的檔案不一定更新得那么及時,而且你也寫的檔案也不一定那么嚴謹,所以常常會有除錯不成功的情況,因而,我們需要一種相對比較嚴謹的 Web 服務描述語言,WSDL(Web Service Description Languages),它也是一個 XML 檔案,
在這個檔案中,要定義一個型別 order,與上面的 XML 對應起來,
<wsdl:types>
<xsd:schema targetNamespace="http://www.example.org/geektime">
<xsd:complexType name="order">
<xsd:element name="date" type="xsd:string"></xsd:element>
<xsd:element name="className" type="xsd:string"></xsd:element>
<xsd:element name="Author" type="xsd:string"></xsd:element>
<xsd:element name="price" type="xsd:int"></xsd:element>
</xsd:complexType>
</xsd:schema>
</wsdl:types>
接下來,需要定義一個 message 的結構,
<wsdl:message name="purchase"> <wsdl:part name="purchaseOrder" element="tns:order"></wsdl:part> </wsdl:message>
接下來,應該暴露一個埠,
<wsdl:portType name="PurchaseOrderService"> <wsdl:operation name="purchase"> <wsdl:input message="tns:purchase"></wsdl:input> <wsdl:output message="......"></wsdl:output> </wsdl:operation> </wsdl:portType>
然后,我們來撰寫一個 binding,將上面定義的資訊系結到 SOAP 請求的 body 里面,
<wsdl:binding name="purchaseOrderServiceSOAP" type="tns:PurchaseOrderService">
<soap:binding style="rpc"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="purchase">
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
最后,我們需要撰寫 service,
<wsdl:service name="PurchaseOrderServiceImplService"> <wsdl:port binding="tns:purchaseOrderServiceSOAP" name="PurchaseOrderServiceImplPort"> <soap:address location="http://www.geektime.com:8080/purchaseOrder" /> </wsdl:port> </wsdl:service>
WSDL 還是有些復雜的,不過好在有工具可以生成,
對于某個服務,哪怕是一個陌生人,都可以通過在服務地址后面加上“?wsdl”來獲取到這個檔案,但是這個檔案還是比較復雜,比較難以看懂,不過好在也有工具可以根據 WSDL 生成客戶端 Stub,讓客戶端通過 Stub 進行遠程呼叫,就跟呼叫本地的方法一樣,
服務發現問題
最后解決第三個問題,服務發現問題,
這里有一個 UDDI(Universal Description, Discovery, and Integration),也即統一描述、發現和集成協議,它其實是一個注冊中心,服務提供方可以將上面的 WSDL 描述檔案,發布到這個注冊中心,注冊完畢后,服務使用方可以查找到服務的描述,封裝為本地的客戶端進行呼叫,
小結
- 原來的二進制 RPC 有很多缺點,格式要求嚴格,修改過于復雜,不面向物件,于是產生了基于文本的呼叫方式——基于 XML 的 SOAP,
- SOAP 有三大要素:協議約定用 WSDL、傳輸協議用 HTTP、服務發現用 UDDL,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/208646.html
標籤:其他
