上一節我們講了基于 XML 的 SOAP 協議,SOAP 的 S 是啥意思來著?是 Simple,但是好像一點兒都不簡單啊!
你會發現,對于 SOAP 來講,無論 XML 中呼叫的是什么函式,多是通過 HTTP 的 POST 方法發送的,但是咱們原來學 HTTP 的時候,我們知道 HTTP 除了 POST,還有 PUT、DELETE、GET 等方法,這些也可以代表一個個動作,而且基本滿足增、刪、查、改的需求,比如增是 POST,刪是 DELETE,查是 GET,改是 PUT,
傳輸協議問題
對于 SOAP 來講,比如我創建一個訂單,用 POST,在 XML 里面寫明動作是 CreateOrder;洗掉一個訂單,還是用 POST,在 XML 里面寫明了動作是 DeleteOrder,其實創建訂單完全可以使用 POST 動作,然后在 XML 里面放一個訂單的資訊就可以了,而洗掉用 DELETE 動作,然后在 XML 里面放一個訂單的 ID 就可以了,
于是上面的那個 SOAP 就變成下面這個簡單的模樣,
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/xml; charset=utf-8
Content-Length: nnn
<?xml version="1.0"?>
<order>
<date>2018-07-01</date>
<className>趣談網路協議</className>
<Author>劉超</Author>
<price>68</price>
</order>
而且 XML 的格式也可以改成另外一種簡單的文本化的物件表示格式 JSON,
POST /purchaseOrder HTTP/1.1
Host: www.geektime.com
Content-Type: application/json; charset=utf-8
Content-Length: nnn
{
"order": {
"date": "2018-07-01",
"className": "趣談網路協議",
"Author": "劉超",
"price": "68"
}
}
經常寫 Web 應用的應該已經發現,這就是 RESTful 格式的 API 的樣子,
協議約定問題
然而 RESTful 可不僅僅是指 API,而是一種架構風格,全稱 Representational State Transfer,表述性狀態轉移,來自一篇重要的論文《架構風格與基于網路的軟體架構設計》(Architectural Styles and the Design of Network-based Software Architectures),
這篇文章從深層次,更加抽象地論證了一個互聯網應用應該有的設計要點,而這些設計要點,成為后來我們能看到的所有高并發應用設計都必須要考慮的問題,再加上 REST API 比較簡單直接,所以后來幾乎成為互聯網應用的標準介面,
因此,和 SOAP 不一樣,REST 不是一種嚴格規定的標準,它其實是一種設計風格,如果按這種風格進行設計,RESTful 介面和 SOAP 介面都能做到,只不過后面的架構是 REST 倡導的,而 SOAP 相對比較關注前面的介面,
而且由于能夠通過 WSDL 生成客戶端的 Stub,因而 SOAP 常常被用于類似傳統的 RPC 方式,也即呼叫遠端和呼叫本地是一樣的,
然而本地呼叫和遠程跨網路呼叫畢竟不一樣,這里的不一樣還不僅僅是因為有網路而導致的客戶端和服務端的分離,從而帶來的網路性能問題,更重要的問題是,客戶端和服務端誰來維護狀態,所謂的狀態就是對某個資料當前處理到什么程度了,
這里舉幾個例子,例如,我瀏覽到哪個目錄了,我看到第幾頁了,我要買個東西,需要扣減一下庫存,這些都是狀態,本地呼叫其實沒有人糾結這個問題,因為資料都在本地,誰處理都一樣,而且一邊處理了,另一邊馬上就能看到,
當有了 RPC 之后,我們本來期望對上層透明,就像上一節說的“遠在天邊,盡在眼前”,于是使用 RPC 的時候,對于狀態的問題也沒有太多的考慮,
就像 NFS 一樣,客戶端會告訴服務端,我要進入哪個目錄,服務端必須要為某個客戶端維護一個狀態,就是當前這個客戶端瀏覽到哪個目錄了,例如,客戶端輸入 cd hello,服務端要在某個地方記住,上次瀏覽到 /root/liuchao 了,因而客戶的這次輸入,應該給它顯示 /root/liuchao/hello 下面的檔案串列,而如果有另一個客戶端,同樣輸入 cd hello,服務端也在某個地方記住,上次瀏覽到 /var/lib,因而要給客戶顯示的是 /var/lib/hello,
不光 NFS,如果瀏覽翻頁,我們經常要實作函式 next(),在一個串列中取下一頁,但是這就需要服務端記住,客戶端 A 上次瀏覽到 20~30 頁了,那它呼叫 next(),應該顯示 30~40 頁,而客戶端 B 上次瀏覽到 100~110 頁了,呼叫 next() 應該顯示 110~120 頁,
上面的例子都是在 RPC 場景下,由服務端來維護狀態,很多 SOAP 介面設計的時候,也常常按這種模式,這種模式原來沒有問題,是因為客戶端和服務端之間的比例沒有失衡,因為一般不會同時有太多的客戶端同時連上來,所以 NFS 還能把每個客戶端的狀態都記住,
公司內部使用的 ERP 系統,如果使用 SOAP 的方式實作,并且服務端為每個登錄的用戶維護瀏覽到報表那一頁的狀態,由于一個公司內部的人也不會太多,把 ERP 放在一個強大的物理機上,也能記得過來,
但是互聯網場景下,客戶端和服務端就徹底失衡了,你可以想象“雙十一”,多少人同時來購物,作為服務端,它能記得過來嗎?當然不可能,只好多個服務端同時提供服務,大家分擔一下,但是這就存在一個問題,服務端怎么把自己記住的客戶端狀態告訴另一個服務端呢?或者說,你讓我給你分擔作業,你也要把作業的前因后果給我說清楚啊!
那服務端索性就要想了,既然這么多客戶端,那大家就分分工吧,服務端就只記錄資源的狀態,例如檔案的狀態,報表的狀態,庫存的狀態,而客戶端自己維護自己的狀態,比如,你訪問到哪個目錄了啊,報表的哪一頁了啊,等等,
這樣對于 API 也有影響,也就是說,當客戶端維護了自己的狀態,就不能這樣呼叫服務端了,例如客戶端說,我想訪問當前目錄下的 hello 路徑,服務端說,我怎么知道你的當前路徑,所以客戶端要先看看自己當前路徑是 /root/liuchao,然后告訴服務端說,我想訪問 /root/liuchao/hello 路徑,
再比如,客戶端說我想訪問下一頁,服務端說,我怎么知道你當前訪問到哪一頁了,所以客戶端要先看看自己訪問到了 100~110 頁,然后告訴服務器說,我想訪問 110~120 頁,
這就是服務端的無狀態化,這樣服務端就可以橫向擴展了,一百個人一起服務,不用交接,每個人都能處理,
所謂的無狀態,其實是服務端維護資源的狀態,客戶端維護會話的狀態,對于服務端來講,只有資源的狀態改變了,客戶端才呼叫 POST、PUT、DELETE 方法來找我;如果資源的狀態沒變,只是客戶端的狀態變了,就不用告訴我了,對于我來說都是統一的 GET,
雖然這只改進了 GET,但是已經帶來了很大的進步,因為對于互聯網應用,大多數是讀多寫少的,而且只要服務端的資源狀態不變,就給了我們快取的可能,例如可以將狀態快取到接入層,甚至快取到 CDN 的邊緣節點,這都是資源狀態不變的好處,
按照這種思路,對于 API 的設計,就慢慢變成了以資源為核心,而非以程序為核心,也就是說,客戶端只要告訴服務端你想讓資源狀態最終變成什么樣就可以了,而不用告訴我程序,不用告訴我動作,
還是檔案目錄的例子,客戶端應該訪問哪個絕對路徑,而非一個動作,我就要進入某個路徑,再如,庫存的呼叫,應該查看當前的庫存數目,然后減去購買的數量,得到結果的庫存數,這個時候應該設定為目標庫存數(但是當前庫存數要匹配),而非告知減去多少庫存,
這種 API 的設計需要實作冪等,因為網路不穩定,就會經常出錯,因而需要重試,但是一旦重試,就會存在冪等的問題,也就是同一個呼叫,多次呼叫的結果應該一樣,不能一次支付呼叫,因為呼叫三次變成了支付三次,不能進入 cd a,做了三次,就變成了 cd a/a/a,也不能扣減庫存,呼叫了三次,就扣減三次庫存,
當然按照這種設計模式,無論 RESTful API 還是 SOAP API 都可以將架構實作成無狀態的,面向資源的、冪等的、橫向擴展的、可快取的,
但是 SOAP 的 XML 正文中,是可以放任何動作的,例如 XML 里面可以寫 < ADD >,< MINUS > 等,這就方便使用 SOAP 的人,將大量的動作放在 API 里面,
RESTful 沒這么復雜,也沒給客戶提供這么多的可能性,正文里的 JSON 基本描述的就是資源的狀態,沒辦法描述動作,而且能夠出發的動作只有 CRUD,也即 POST、GET、PUT、DELETE,也就是對于狀態的改變,
所以,從介面角度,就讓你死了這條心,當然也有很多技巧的方法,在使用 RESTful API 的情況下,依然提供基于動作的有狀態請求,這屬于反模式了,
服務發現問題
對于 RESTful API 來講,我們已經解決了傳輸協議的問題——基于 HTTP,協議約定問題——基于 JSON,最后要解決的是服務發現問題,
有個著名的基于 RESTful API 的跨系統呼叫框架叫 Spring Cloud,在 Spring Cloud 中有一個組件叫 Eureka,傳說,阿基米德在洗澡時發現浮力原理,高興得來不及穿上褲子,跑到街上大喊:“Eureka(我找到了)!”所以 Eureka 是用來實作注冊中心的,負責維護注冊的服務串列,
服務分服務提供方,它向 Eureka 做服務注冊、續約和下線等操作,注冊的主要資料包括服務名、機器 IP、埠號、域名等等,
另外一方是服務消費方,向 Eureka 獲取服務提供方的注冊資訊,為了實作負載均衡和容錯,服務提供方可以注冊多個,
當消費方要呼叫服務的時候,會從注冊中心讀出多個服務來,那怎么呼叫呢?當然是 RESTful 方式了,
Spring Cloud 提供一個 RestTemplate 工具,用于將請求物件轉換為 JSON,并發起 Rest 呼叫,RestTemplate 的呼叫也是分 POST、PUT、GET、 DELETE 的,當結果回傳的時候,根據回傳的 JSON 決議成物件,
通過這樣封裝,呼叫起來也很方便,
小結
- SOAP 過于復雜,而且設計是面向動作的,因而往往因為架構問題導致并發量上不去,
- RESTful 不僅僅是一個 API,而且是一種架構模式,主要面向資源,提供無狀態服務,有利于橫向擴展應對高并發,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/211017.html
標籤:其他
上一篇:電腦崩潰?黑客最愛郵件入侵方式,在雙十一也要保護好網路安全!
下一篇:把字串轉換成整數
