銜接上文[解讀REST] 4.基於網路應用的架構風格,上文總結了一些適用於基於網路應用的架構風格,以及其評估結果。在前文的基礎上,本文介紹一下Web架構的需求,以及在對Web的關鍵協議進行設計和改進的過程中遇到的問題;以及在對基於網路應用的架構風格進行評估的過程中的領悟;結合Web的需求進而推導出R ...
銜接上文[解讀REST] 4.基於網路應用的架構風格,上文總結了一些適用於基於網路應用的架構風格,以及其評估結果。在前文的基礎上,本文介紹一下Web架構的需求,以及在對Web的關鍵協議進行設計和改進的過程中遇到的問題;以及在對基於網路應用的架構風格進行評估的過程中的領悟;結合Web的需求進而推導出REST這種架構風格,隨後使用REST來指導Web架構的設計和改進工作。
1 Web的需求
在本系列博客的第一篇博客[解讀REST] 1.REST的起源中,Web之父Berners-Lee在世界上第一個網站寫下的第一句話:“The WorldWideWeb (W3) is a wide-area hypermedia information retrieval initiative aiming to give universal access to a large universe of documents.”,闡述了創建Web的目的在於形成一種鏈接眾多文檔的廣域的超媒體信息檢索系統,使得人類和機器都可以通過它來進行溝通和交流。這個系統最初的目標用戶是分散在世界各地的、通過互聯網鏈接的各個大學和政府的高能物理研究實驗室。他們的機器是不同類型的終端、工作站、伺服器和超級電腦的大雜燴,所以他們的操作系統和文件格式也是一個大雜燴。構建一個這樣的系統所面臨的挑戰是為這些信息文檔提供統一的介面,使得這些信息可以在眾多的平臺上進行交流通信,以及當新的設備接入到這個系統時,可以進行增量的部署。
1.1 低門檻
參與為Web創建信息是自願的,對於信息的創作者、閱讀者以及應用開發者而已,Web都應該是“低門檻”,以方便Web的各類使用者接入Web系統。
選擇超媒體(比如最常見的超媒體HTML)作為用戶界面(UI),是因為其簡單性和通用性。首先無論信息來源於何處,都能使用相同的界面進行呈現;其次超媒體的關係(鏈接)允許對其進行無限的鏈接構造,從而形成一個巨大的“網狀結構”;對這個網狀結構的文檔的直接操作可以引導用戶瀏覽整個應用。
對於創作者而言,超媒體的創作語言也必須是簡單的,能夠使用現有的編輯工具來進行創建,無論是否鏈接到網路,都可以使用此超媒體格式來保存創作的內容。因此所有的協議都被定義為文本格式,以方便對通信進行觀察和測試。
1.2 可擴展性
即使可以創建一個完美匹配用戶需求的軟體系統,那些需求也會隨著時間而發生變化(唯一不變的事物就是變化其本身),如果一個系統想要像Web那樣長壽,它就必須做好應對變化的準備。因此可擴展性可以使我們避免陷入已部署的系統的局限之中,避免受到遺留系統的束縛。
1.3 分散式超媒體
超媒體是由應用控制信息來定義的,這些信息內嵌在信息的表述之中。分散式超媒體系統允許在遠程地點存儲表達控制信息,因此分散式超媒體系統中的用戶操作需要將大量的數據從其存儲地移交到其使用地,所以Web的架構必須支持大粒度的數據移交。超媒體交互的可用性很容易影響到用戶感知的性能(比如用戶選擇了一個鏈接,到鏈接的界面呈現之間的時間),因為Web的交互的信息是跨域整個互聯網的,則Web的架構必須儘量的減少網路交互的次數以改善用戶感知的性能。
1.4 互聯網規模
Web的旨在形成一個互聯網規模的分散式超媒體系統,這意味著它是不但是跨越地理上的分佈,而且是跨越組織邊界的(互聯網是跨域組織邊界的信息網路。於此相對的是可控的區域網,比如企業內部的私有網路)。信息服務的提供商必須滿足無法控制的可伸縮性和獨立部署這兩方面的要求。
大多數我們接觸到的系統都存在一個隱含的假設,那就是這個系統是完全可控的。當一個系統在互聯網上允許時,則無法滿足這樣的假設。無法控制的可伸縮性指的是架構元素可能會於其組織邊界之外的元素進行通信,當它們遇到如下的情況時仍能正常運行:未曾預料到的負載、收到錯誤的數據或者惡意的數據等等。這一點適用於所有的架構元素,不能期望用戶保持所有的服務的信息,也不能期望服務保持跨越多個請求的狀態信息。架構元素在跨越多個組織邊界進行通信時,其安全性也是不能忽視的,應該允許中間應用(比如防火牆)來檢查其通信,並阻止安全策略不允許的交互。系統的參與者都應該假設其接收到的信息是不可信的,那麼就需要架構能夠提供認證和授權的機制,然而認證會降低可伸縮性,那麼架構的預設操作應該是限制在一組定義好的安全操作中(這裡的安全操作指的是不會對伺服器造成危害的操作,因此並不需要進行認證)。
多個組織邊界也意味著系統應該可以應對新舊組件的共存,而不妨礙新組件使用它們的新功能。同時現有的架構元素在設計的時候需要考慮到以後會添加新功能,舊的實現也必須能夠方便的識別出來,從而把這些遺留的行為封裝起來,不會對新元素造成不利影響。對於Web這樣的系統來說,強制要求架構中的所有組件都整齊劃一的來部署是不現實的事情。
1.5 Web急速發展帶來的問題
自從1990年Berners-Lee發佈了第一個網站一來,到1993年末。Web的目標用戶已經遠遠的超出最初計劃的目標群體(高能物理研究實驗室)了。延伸到了學校、個人主頁和校園信息系統等等,Web迎來了指數級的增長。最早的HTTP0.9是一個非常簡單的協議,是為單個請求響應設計的,新的站點越來越多的採用了圖片作為網頁的一部分,導致出現了不同的瀏覽模式。此時的Web架構已經無法滿足這樣的需求了,隨後在IETF形成了三個工作小組HTTP,URI和HTML。這些工作組的主要任務是定義現有架構性通信的子集(早期Web中普遍的一致的實現),然後指定一組規範來解決這些問題。這些工作帶來的挑戰是如何把一組新功能引入到一個已經被廣泛部署的系統中;以及如何確保新功能的引入不會對那些使得Web成功的架構屬性帶來不利的影響甚至是毀滅性的影響。
1.6 解決之道
早期的Web基於一些可靠的設計原則:分離關註點、簡單性、通用性,但是缺乏對於架構的描述和理論基礎。可以使用一種架構風格來定義Web架構背後的設計原則,那麼這些設計原則對於未來的架構而言就是可見的了。如同在之前的博客中解釋道的那樣,一種架構風格是一組已命名的架構元素之上的架構約束,由它會產生一組所期待的架構屬性。而這組期望架構屬性,則正是Web所期待的需求的體現。
這個解決之道的第一步就是識別出來現有的(http1.0和http1.1開發之前)Web架構中的架構約束,這些架構約束負責產生出所期待的架構屬性。
第二步則是識別出在互聯網規模的分散式超媒體系統中所期待的架構屬性,然後選擇會產生這些架構屬性的架構約束,把這些架構約束添加到現有的架構風格之上,形成一種新的風格。
第三步是使用新的架構風格作為指導,對修改和擴展Web架構的提議進行評估,看其是否存在衝突,如果存在衝突則表明這個提議違反了一個或多個Web背後的設計原則。
修正後的協議規範是根據"新的架構風格"的指導來編寫的,最後通過修訂後規範,開發實現它,然後進行部署。這些解決之道是源自於Fielding博士直接參与了Apache Http伺服器的項目和libwww-perl客戶端庫,以及為網景的Navigator、Lynx和微軟的IE的開發者提供建議得到的經驗。
2 推導REST
上一小節提到的“新的架構風格”就是專門為分散式超媒體系統設計的REST(Representational State Transfer=表述性狀態移交),它由上一篇博客中描述的幾中架構風格([解讀REST] 4.基於網路應用的架構風格)衍生而來,添加了一些額外的架構約束。Web架構的設計理論,是由一組應用於架構元素之上的架構約束組成的,當逐步將每個架構約束添加到這個架構風格上時,會對架構風格產生一些影響,通過檢查這些影響,可以識別出來其產生的架構屬性。這裡就從一個”空風格“開始,它代表一個空的架構約束集合(即是一個沒有明顯邊界的系統),而這也正是推導REST的起點。
2.1 客戶端-伺服器
首先添加一個客戶端-伺服器風格:其背後的原則是分離關註點。通過分離分離用戶界面和數據存儲兩個關註點,可以改善用戶界面的可移植性;同時可以簡化伺服器組件,改善系統的可伸縮性。不過對於Web來說,最重要的則是這種分離使得組件可以獨立部署,從而支持跨域多個組織邊界的互聯網規模的需求。
2.2 無狀態
接下來在添加一個架構約束:通信必須在本質上是無狀態的。也就是說從客戶端到伺服器的每個請求都必須包含理解該請求所必須的所有信息,不能利用伺服器存儲會話的上下文信息,會話狀態全部保存在客戶端。這一約束可以改善可見性(監視系統不必為了確定一個請求的全部性質而去查看請求之外的其他請求);改善可靠性(減輕了從局部故障中恢復的任務量);改善可伸縮性(服務端不必在多個請求直接保存狀態,從而允許伺服器迅速釋放資源)。但是無狀態也有相應的缺點,由於伺服器不能保持會話狀態數據,則會造成在每一次請求中發送大量重覆的數據,可能會降低網路性能。
2.3 緩存
為了改善網路效率,添加了緩存這個架構約束。它要求一個請求的響應中的數據被隱式或者顯式的標記為可緩存或不可緩存,如果可緩存,則客戶端可以為以後相同的請求重用這個響應的數據。緩存的好處在於可以消除一部分網路交互,從而提高效率、可伸縮性和用戶感知的性能。但是代價則是緩存中如果存在過期的舊數據,則會降低可靠性。
早期的Web架構(1994年之前),是通過客戶端-緩存-無狀態-伺服器這組架構約束來定義的。如下圖所示:
也就是說,1994年之前的Web架構設計基礎理論聚焦的是在互聯網上交換靜態文檔的無狀態的客戶端-伺服器風格,通信信息僅包含了對非共用緩存初步支持,但是並沒有限介面要求所有的資源提供一組一致的語義。相反,Web依賴一個公共的客戶端-伺服器的實現庫(CERN的libwww)來維持Web應用的一致性。然而Web的實現者則早已超越了這組設計,除了靜態的文檔之外,還要求識別動態生成的響應,也以代理和共用緩存的形式開展了對中間件的開發工作,但是必須對現有的協議進行擴展,這樣中間件才能可靠的通信。以下三個架構約束(統一介面,分層系統,按需代碼)則是早期Web架構的擴充,以便用來對新的Web架構的擴展和改進加以指導。
2.4 統一介面
REST區別於其他的基於網路的架構風格的核心特征是:強調組件之間要有一個統一的介面。通過在組件介面上應用通用性的原則,簡化了整體的系統架構,也改善了交互的可見性。實現和它們所提供的服務是解耦的,這也促進了組件的獨立可進化性。當然得到這些好處也是要付出代價的:統一介面降低了效率,因為信息都是使用標準的形式來移交的,而不是特定於應用需求的形式。REST介面被設計為可以高效的移交大粒度的超媒體數據,並對Web的場景情況做了優化,但是這也導致該介面對於其他形式的架構交互而言並不是最優的。
為了獲得統一介面,需要多個架構約束來指導組件的行為,REST由四個介面架構約束來定義:
- 資源的識別;
- 通過表述來操作資源;
- 自描述的信息;
- 超媒體作為應用程式狀態的引擎(HATEOAS)。
2.5 分層系統
為了進一步滿足互聯網規模這個需求,添加了分層系統這個架構約束。分層系統通過限制組件的行為(每個組件只能看到與其交互的相鄰層),將架構分解成若幹層級。通過將組件對整個系統的認知限制在單一的層級內,為整個系統的複雜性劃分了邊界,並且可以提高底層的獨立性,也可以通過層級來封裝遺留的舊組件,以免新的組件受到到舊的影響。中間件還可以支持負載均衡來改善系統的可伸縮性。然而,分層系統會增加數據處理的開銷和延遲,因此降低用戶感知的性能。不過對於一個支持緩存的架構來說,則可以通過在中間層使用共用緩存來彌補這一缺點。此外還可以通過這些中間層實施安全策略(比如防火牆)。
分層系統和統一結構相結合後,產生了類似統一管道和過濾器相似的架構屬性。在REST中,中間件能夠主動的轉換消息的內容,因為這些消息是自描述的,並且其語義對於中間件而言是可見的。
2.6 按需代碼
為REST添加的最後一個架構約束是按需代碼。REST允許下載並執行applet(如今最廣泛的是js腳本)代碼,對客戶端的功能進行擴展。這樣則可以減少預先實現的功能的數目,簡化客戶端的開發,也改善了系統的可擴展性。但是這樣做降低了可見性(REST的連接器和組件並無法理解這些腳本),因此按需代碼只是REST的一個可選的架構約束。
2.7 推導小結
REST是由一組經過選擇的架構約束組成的架構風格,通過這些架構約束產生了期待的架構屬性。完整的REST的推導流程圖如下:
3 REST的架構元素
REST是對分散式超媒體系統中的架構元素的一種抽象,REST忽略了組件的實現以及協議語法的細節(比如html,http的具體協議細節),以便聚焦於一下幾個方面:組件的角色、組件之間的交互、組件對於重要數據元素的解釋。
3.1 數據元素
在分散式對象風格中,所有的數據都被封裝和隱藏在數據的處理組件中。於分散式對象不同的是,REST的關鍵特征在於其架構的數據元素的形式和狀態。在分散式超媒體的特性中,當一個用戶選擇了一個鏈接後,該鏈接所指向的信息需要從其存儲地移動到其使用地。對於一個分散式超媒體系統的架構師而言,他只能在三種選項中做出選擇:
- 在數據所在地對數據進行呈現,並向接收者發送一個固定格式的鏡像;
- 把數據和呈現引擎封裝起來,一起發送給接收者;
- 發送原始數據可一些描述數據類型的元數據,讓接收者自己去呈現。
每一個選項都有其優缺點,第1個選項對應於傳統的客戶端-伺服器風格,它把數據的呈現結果發送給接收者,這樣可以簡化其他組件對數據結構做出假設,並且簡化了客戶端的實現,但是也嚴重限制了接收者的功能,並且把絕大部分的負擔都放在了發送者這一邊,從來導致伸縮性的問題。第2個選項對應可移動對象風格,它支持對於信息的隱藏,同時還可以通過唯一的呈現引擎支持對數據的處理,但是這將會把接收者的功能限制在呈現引擎的範圍之內,也會大量的增加需要移交的數據量。第3個選項允許發送者保持簡單性和可伸縮性,但是它喪失了信息隱藏的優點,並且要求發送者和接收者都必須理解相同的數據類型。
REST聚焦於分享對於數據類型的理解,但是對其標準的操作介面做了限制。通過這樣的方式,REST所採用的是這三個選項的一個混合體。REST通過一種數據格式來移交資源的表述來進行通信,這可以基於接收者的能力以及其所期待的格式以及內容中動態的選擇所使用的數據格式。至於表述是否資源的原始格式相同,則被隱藏在了介面的背後。通過發送一個表述,可以獲得近似移動對象風格的好處;這個表述由一個標準的數據格式的執行組成(供呈現引擎使用),因此獲得了客戶端-伺服器風格的分離關註點的好處,而且不存在伺服器的可伸縮性問題;表述允許通過一個通用的介面來隱藏信息,從而支持封裝和服務的進化,並且可以通過按需代碼來擴展功能。REST的數據元素如下:
數據元素 | 實例 |
資源 | 一個超文本引用所指向的概念性目標 |
資源標識符 | URL,URN |
表述 | HTML,圖片,音視頻 |
表述元數據 | 媒體類型,修改時間等 |
資源元數據 | source link,alterbates |
控制數據 | cache-control等 |
REST對於信息的核心抽象是資源:任何可以被命名的信息都能夠作為一個資源,比如一個文檔,一個圖片,今天的天氣情況等等。資源標識符則是對一個資源的唯一標識,由命名權威來為資源分配標識符,映射的語義同樣由命名權威來負責。REST使用表述來表述資源的當前狀態或者預期狀態,隨後在各個組件之間移交該表述,通過這種方式在資源上執行各自操作,表述通常由數據以及描述數據的元數據組成。控制數據則是定義在組件之間交互的數據的用途以及其行為,比如控制緩存行為等。表述的數據格式成為媒體類型(media type),發送者可以把一個表述包含在一個響應之中,移交給接收者,接收者收到響應之後,根據消息中的控制數據和媒體類型的性質,對消息進行處理(比如呈現一個jpg的圖片,執行一個js腳本等)。媒體類型的設計會之間影響到用戶感知的性能,比如HTML支持增量呈現的話,瀏覽器就可以一邊接收html,一遍呈現接收到的部分內容,而不必等到其完全接收完畢。
由這些數據元素可以組成一個操作資源的通用介面,而無需關係其成員函數或者其處理軟體是何種類型的。
3.2 連接器
REST使用多種不同類型的連接器來對資源和其表述進行封裝,連接器代表了一個組件通信的抽象介面。
連接器 | 實例 |
客戶 | libwww、libwww-perl、httpclient |
伺服器 | libwww、apache api |
緩存 | 瀏覽器緩存、CDN |
解析器 | DNS |
隧道 | SSL |
所有的REST的交互都是無狀態的。這就使得每個請求都包含理解該請求的全部信息,而不必查找其關聯的請求。這一個約束得到了一下4點好處:
- 連接器無需在請求之間保持應用狀態,改善可伸縮性;
- 允許對交互進行並行處理;
- 允許中間件查看和理解單獨的一個請求,並對其進行動態的安排(比如負載均衡);
- 強制了每個請求都包含可能會影響緩存的信息。
主要的連接器是客戶端和伺服器,客戶端發起請求,伺服器監聽請求並做出響應。其次是緩存連接器,它位於客戶端和伺服器介面處,用來複用可緩存的響應,比如瀏覽器緩存,和共用的CDN緩存。解析器負責把部分或者完整的資源標識符翻譯成具體的網路地址信息,比如DNS把一個功能變數名稱翻譯成一個IP地址。隧道是一種簡單的跨域連接邊界的一種機制,比如SSL。
3.3 組件
REST的組件可以根據它們在整個應用中的角色來分類,比如:
組件 | 實例 |
來源伺服器 | Apache,Ngnix,IIS |
網關 | CGI,反向代理,SMTP網關 |
代理 | CERN代理,Fiddler |
用戶代理 | chrome,firefox,ie,httpclient |
來源伺服器是資源的命名權威所在地。網關則是前置在來源伺服器的一個組件,用來執行數據轉換(HTTP到SMTP的網關),安全增強、性能增強(負載均衡)等。代理和網關的差異在於這是客戶選擇的組件,必須調試http的時候使用的fiddler。用戶代理是使用客戶端連接器發起請求,並作為響應的最終接收者,一般而言是Web瀏覽器,或者網路爬蟲。
4 REST的架構視圖
第3小節孤立的瞭解了REST的架構元素,這一小節則把這些元素組合起來,形成一個架構。
4.1 過程視圖
過程視圖的主要作用是展示數據在系統中的流動路徑,得出組件之間的交互關係。下麵是一個典型的REST的過程視圖:
一個用戶代理處理三個並行的交互,用戶代理的客戶端連接器的緩存無法滿足請求,則它根據每個資源標識符的屬性和客戶端的連接配置,把每個請求路由到資源的來源伺服器:
- 請求a被髮送到一個本地代理,然後代理通過DNS查找到了一個網關,該網關把這個請求發送到了一個能滿足該請求的來源伺服器。
- 請求b被之間發送到了一個來源伺服器。
- 請求c被髮送到了一個SMTP的代理,這個代理之間把請求轉換為了一個SMTP的請求,發給了一個郵件伺服器。
4.2 連接器視圖
REST的連接器視圖聚焦於組件之間的通信機制。客戶端連接器檢查資源標識符,以便為每一個請求選擇一個合適的通信機制,比如標識符如果是一個本地資源,則鏈接到一個處理本地資源的代理組件。比如是一個GFW的資源時,鏈接到一個科學上網的代理(你懂的...)。REST並不限制通信的協議,比如上面的請求c把一個http的請求,轉換為了郵件協議。
4.3 數據視圖
數據視圖展示的時信息在組件之間流動時的應用狀態。REST把所有的控制狀態都集中在表述之中,目的在於使伺服器無需維護當前請求之外的客戶端狀態,從而改善伺服器的可伸縮性。應用的下一個控制狀態位於第一個請求的響應的表述之中,從一個表述遷移到下一個表述,因此這樣的一系列表述可以構造出一個資源的有限狀態機,即超媒體作為應用程式的狀態引擎(HATEOAS)。
5 總結
本篇博客在前幾篇的基礎上,介紹了Web誕生的目標,以及在早期發展中遇到的問題,進而推導出了其解決之道,即專門為分散式超媒體系統設計的REST(表述性狀態移交)架構風格。REST強調組件交互的可伸縮性、介面的通用性、組件的獨立部署、以及減少交互延遲、增強安全性、封裝遺留系統的組件等。
REST架構風格由客戶端-伺服器、無狀態、緩存、統一介面、分層系統和按需代碼這6個架構約束構成,同時統一介面這個架構約束由資源的標識、通過表述操作資源、自描述的消息和HATEOAS這4個介面約束構成。其中REST的核心特征在於統一介面,而統一介面的核心在於HATEOAS。前面說過,REST是Web的架構風格,Web由HTTP,HTML,URI和MIME這四個核心部分構成,那麼我們看一下這四部分是如何體現REST的6個架構約束以及4個介面約束的。
- 客戶端-伺服器:HTTP的請求響應方式體現了客戶端-伺服器這個約束。
- 無狀態:HTTP的無狀態性。
- 緩存:HTTP有完整的豐富的緩存控制功能。
- 分層系統:客戶端代理,網關,反向代理,SSL,安全防火牆這些中間件體現了分層系統(得益於統一介面、HTTP的自描述性和可見性,使得這些組件可以透明的存在於整個Web系統中)。
- 按需代碼:最常見的Javascript腳本。
- 統一介面:這是最為核心的約束,其由眾多部分組成
- 資源的標識:URI;
- 通過表述操作資源:通過HTML操作資源。
- 自描述的消息:HTTP中包含有資源的元數據,表述的元數據的header,URL中包含資源的標識。
- HATEOAS:超媒體作為應用程式狀態的引擎,體現在HTML中的具體例子是a,form,link,img,input等這些超媒體控制項構成的HTML表單,可以由這個表單(作為資源的表述)操作資源,而且a元素提供了當前表述中所允許的後續操作集合,這樣鏈接起來形成的一個資源狀態遷移流程,可以認為是一個資源的有限狀態機,既是應用程式狀態的引擎,也就是HTML這個超媒體是如今Web的應用狀態的引擎。
上面說到的這些都是匹配REST的部分,其實現實中也有很多不匹配的部分,下麵一篇博客則描述下把REST落實到Web的架構設計,協議規範的制定,以及部署的過程中的經驗和教訓。
參考
世界上誕生的第一個網站:http://info.cern.ch/
世界上誕生的第一個網站-模擬器:http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html
Web的生日:http://home.cern/topics/birth-web
理解本真的REST:http://www.infoq.com/cn/articles/understanding-restful-style/
架構風格與基於網路的軟體架構設計-導讀:http://www.infoq.com/cn/articles/doctor-fielding-article-review
架構風格與基於網路的軟體架構設計:http://www.infoq.com/cn/minibooks/web-based-apps-archit-design
Architectural Styles and the Design of Network-based Software Architectures:https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm
Roy T. Fielding: Understanding the REST Style:https://www.youtube.com/watch?v=w5j2KwzzB-0
Roy T. Fielding: REST APIs must be hypertext-driven: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
Evolution of HTTP:https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Evolution_of_HTTP
REST is not about APIs , Part 1:https://www.nirmata.com/2013/10/01/rest-apis-part-1/
REST is not about APIs , Part 2:https://www.nirmata.com/2013/11/12/rest-apis-part-2/