架构风格与基于网络的软件架构设计
原文鏈接 https://blog.csdn.net/on_1y/article/details/60358117
架構風格與基于網絡的軟件架構設計
如今許多服務都采用了 RESTful API, 而 REST 這一架構風格,最早即來源于 Roy Thomas Fielding 的博士論文 Architectural Styles and
the Design of Network-based Software Architectures ,本文即是閱讀此文后的總結,并結合論文給出大量實例。通常人們都是因為 RESTful 風格才提到這篇論文,有的人甚至只讀涉及 REST 的那章。但是我讀完論文后,感覺最大的收獲不是 REST 這一架構風格,而是為基于網絡的軟件設計架構時,應該考慮哪些因素,不同的因素會導致哪些不同的結果。因此,本文將重點對基于網絡的軟件架構進行介紹,而非介紹 REST 本身。
背景
首先介紹一下作者,Roy Thomas Fielding。
Fielding 博士是 HTTP/1.1 規范的主要制定者,并且深度參與了 URI, HTML 規范的設計。同時是 Apache HTTP Server 項目的聯合創始人,Apache 軟件基金會的聯合創始人,并在成立前三年擔任軟件基金會主席。Fielding 博士的這篇博士論文,是 Web 發展史上極其重要的文獻,奠定了現代 Web 架構。可以看出,此人不僅理論功底扎實,還親自寫代碼踐行自己的理論。
Fielding 博士之所以寫這篇論文,也與 Web 早期的發展狀況有關。早期的 Web 架構,基于一些基本的原則,例如簡單性,分離關注點,通用性,但是缺少對架構的精確描述。Web 早期只在政府及研究機構部署,但隨著公眾及商業公司的介入,Web 的規模在指數級增長,但早期的 Web 架構已經難以支撐這樣的規模。因此,當時需要現代 Web 架構,來支持 Web 規模的極速增長,同時,也要避免新架構的引入,破壞了早期 Web 架構中一些良好的特性。因此,作者在論文中提出了 REST 架構風格,來指導現代 Web 架構的設計。這就是這篇論文出現的主要背景。
軟件架構
軟件架構元素
我們經常聽別人談起 “軟件架構”,“系統架構”,“架構師”,這樣的名詞。那么,到底什么是軟件架構呢?Fielding 認為,軟件架構主要由三部分元素組成:數據元素,連接元素,處理元素。
- 數據元素是指系統中被使用,被轉化的信息,例如 HTML 文檔,JPEG 圖片等。
- 連接元素是將系統中不同部分連接起來的元素,例如我們常用的一些用于組件和組件之前網絡連接的庫,HttpClient, OkHttp, gRPC 等。
- 處理元素是指對數據進行轉化的元素,例如 HTTP 服務器 (Apache, Nginx), 網關,代理,瀏覽器等。
軟件架構本身是對系統運行時的一種抽象。一方面,系統存在多個層次的抽象,每層都有自己的架構,另一方面,系統又由多個操作階段構成,每個階段都有自己的架構。
那么,什么是多個層次的抽象呢?例如,你的公司有一款面向用戶的移動應用。從上層的抽象來看,這款應用由客戶端及服務端構成。在這一層次上,處理元素有客戶端,服務端,連接元素是用于在客戶端與服務器傳送數據的組件,數據元素即是在客戶端與服務端之間傳遞的信息。
如果你把抽象層次下降一點,就能在整個系統中發現更多的架構元素。例如,把客戶端抽象層次下降一些,會發現客戶端有 Android 和 iOS 之分。
同理,服務端將抽象層次再下降一點,就能識別出更多的處理元素,例如,用于負載均衡的處理元素。
從上面的例子可以看出,從不同的抽象層次觀察,可以得到不同的架構。另一方面,對同一系統而言,在不同操作階段,也有不同的架構,這是什么意思呢?
對一個系統而言,都有初始化,提供服務,停止等階段。在不同階段,都有著不同的數據元素,處理元素。
例如,在初始化階段,如果我們使用了 Spring Framework, 那么 Spring 框架會根據我們的配置,生成各種對象,這時候,數據元素是我們的配置,處理元素是 Spring 框架。但是到了服務階段,各種用于處理的元素已經生成,配置已經不再是此時的數據元素,因為他們已經內化到處理元素中了。此時的數據元素,有從客戶端發來的請求中的數據,有我們服務端的業務數據。此時的處理元素,是服務端的各種模塊,例如用于搜索的模塊,用于渲染的模塊。
軟件架構配置
從上一節我們了解到,軟件架構由數據元素,連接元素,處理元素構成。但是我們描述一個軟件架構時,除了描述軟件架構由什么元素組成,還需要描述這些元素之間的關系。軟件架構配置(Configurations),就是用于描述系統運行時,數據元素,連接元素和處理元素之間關系的概念。那么,這種 “關系” 又是指什么呢?我們可以將其理解為對元素之間的一種約束。
軟件架構屬性
我們設計軟件架構時,在選擇了數據元素,連接元素,處理元素,并且確定了他們之間的關系后,系統就會體現出一系列性質。這些性質,就是軟件架構屬性。
軟件架構屬性包括功能屬性和非功能屬性。功能屬性表示系統是否完成了需要的功能,而非功能屬性則代表了系統除了完成功能之外,還具有的一些性質,例如可擴展性,可重用性等等。
軟件架構風格
一個軟件架構由不同元素構成,元素之間有各種各樣的約束,而軟件架構風格就是指一組協作的架構約束。有了架構風格的概念,我們就可以方便地比較不同的軟件架構。當我們談到某個系統是某種架構風格時,就代表了這個系統的架構背后有一系列架構約束。
說了這么多概念,他們之間到底有什么關系呢?對我們設計系統又有什么用呢?里面有一條重要的邏輯鏈條,就是系統需求,約束與屬性之間的關系。我們設計任何一個系統,都是要滿足特定的需求,除了特定功能需求之外,有的系統需要滿足高性能,有的系統需要滿足高可用,有的系統需要經常進行靈活的擴展。那么,怎么設計系統才能達到這種需求呢? 那就是對系統進行一些特定的約束。特定的約束會導致特定的屬性,特定的屬性又會滿足特定的需求。
因此,架構風格,即一組約束,導致了一組架構屬性,這組架構屬性構成了系統需求的一個超集。
所以,要設計好系統,就要正確地識別出,系統到底需要滿足哪些需求,當需求不能都滿足時,優先滿足哪些。然后要了解如何對系統進行劃分,如何確定組件之間的關系,有哪些約束的手段,這些約束,又會導致哪些屬性。這也是論文后面會仔細探討的問題。
總的來說,從抽象的角度看,架構風格是對架構的一種抽象,一種架構風格對應多種架構。而架構本身是對系統的抽象,同一架構對應著多種不同的系統。而這篇論文,主要探討的,就是基于網絡的應用架構以及架構風格。
基于網絡的應用架構
這一節介紹基于網絡的應用架構,主要解決三個問題:
- 基于網絡的應用架構是指什么
- 如何評估基于網絡的應用架構
- 基于網絡的應用架構中,重要的架構屬性有哪些
什么是基于網絡的應用架構
之前,我們已經介紹了軟件架構的概念,基于網絡的應用架構是在軟件架構的基礎上,施加了一個約束,即組件之間的通信限于消息傳遞。論文中,還特別說明了基于網絡的應用和分布式系統的區別。一般而言,我們在使用分布式系統時,就像使用一個集中式的系統一樣,網絡的存在對用戶來說是透明的。而基于網絡的應用,“網絡”的存在無需表現為對用戶透明的形式,有時候還要用戶感知到“網絡”的存在,特別是“網絡”意味著一定的性能開銷時。
如何評估基于網絡的應用架構
那么,我們如何評估基于網絡的應用架構呢?就是從根本上講,評估一個應用架構,就不看這個架構是否滿足了系統的需求。之前我們講系統屬性的時候說過,系統屬性分功能屬性和非功能屬性。相應地,這里看架構是否滿足系統的需求,也要看是否滿足了功能需求,是否滿足了非功能需求。功能需求和我們的業務相關,而非功能需求,就要使用架構風格來評估。
我們知道,架構風格代表了一組架構約束,用架構風格評估架構,就是看這種約束,是否能使系統的需求得到滿足。
在上圖中,我們的系統有一組架構約束,分別是
- 緩存
- 無狀態
- 統一接口
同時,我們還有一組系統需要的屬性,即系統的需求,分別是
- 性能
- 簡單性
- 可靠性
每種架構約束,會在不同程度上影響某種屬性。這樣,我們知道了系統的架構風格之后,就能評估架構是否滿足了系統的需求。
基于網絡的應用架構中的重要屬性
下面,我們就介紹在基于網絡的應用架構中,重要的屬性有哪些,這也是我們在設計系統時,要思考,我們的系統,最終要滿足哪些屬性。如果我們連有哪些屬性都不知道,又怎么設計一個系統?
這里主要包括的屬性有:性能,可伸縮性,簡單性,可修改性,可見性,可移植性,可靠性。注意,我們這里談論這些屬性性,關注的點,都在 “基于網絡” 的環境下。
性能(Performance)
我們首先要講的一個系統屬性,就是性能。我們常說“性能優化”,那么,我們到底在優化什么?性能本身又由什么決定?
ACM Queue 上有一篇文章,Thinking Clearly about Performance,里面提到,多數時候,我們提到性能時,指的都是計算機軟件完成某種任務所需要的時間,也有的時候,性能是指“吞吐”,即一定時間間隔內,完成的任務數。這和我們常說的,“響應時間”, “QPS” 等是一致的。這里,我們先講性能的決定因素有哪些,然后再講性能度量問題。
性能的決定因素,主要有下面幾個方面:
- 應用需求
- 交互風格
- 架構
- 每個組件的實現
決定性能的,首先是應用的需求。假如,你的應用是用來下載數據的,看視頻的,還是實時交易的?不用類型的應用,能達到的性能水平是不一樣的,比較不同類型應用的性能指標,也是沒有什么意義的。此外,決定性能的還有交互風格,你的系統中,組件之間的通信是基于 TCP 還是 UDP,達到的性能水平也會有所不同。另外,架構的設計也影響系統的性能,例如子系統是如何劃分的,連接元素用的什么,有沒有使用緩存,組件之間數據傳輸的格式是什么,這些都會對系統性能造成影響。最后,組件的實現也會影響性能,即使你什么都設計好了,一個糟糕的實現,還是會對性能造成致使打擊。
接下來,我們講如何度量性能,我們談性能時,通常談到的就是各種性能指標,這些指標就是通過不同形式的度量得到的。在基于網絡的環境下,對性能的度量主要分三個方面:
- 網絡性能
- 用戶可察覺性能
- 網絡使用效率
對網絡性能的度量也分為多種。吞吐是指組件之間信息轉移的速率;負載(overhead) 是指跨網絡傳輸帶來的某種額外的負擔,這種“負擔”可能是時間,也可能是內存,帶寬等等,負載又分初始負載每次交互的負載,在組件之間通訊時,我們要確定,通訊是每次都要建立連接,再傳輸數據么,還是只建立一次連接,后面的多次傳輸可以復用這個連接;帶寬是指特定網絡上最大吞吐量;可用帶寬是指真正可以被系統使用的那部分帶寬。
用戶可察覺的性能,又分為兩個方面,一是延遲,即初次請求到首次響應的時間,二是完成時間,即完成一個應用動作所需要的時間。我們看下 Chrome 對一次網絡請求時間的劃分就能明白這個概念:
從 Queueing 到 Wariting(TTFB) 即是初次請求到首次響應的時間,其中 TTFB 的含義就是 (Time to first byte),即首字節響應時間。而完成時間,要等到 Content Download,即所有內容下載完成后,才結束。區分延遲和完成時間是很重要的,在其它條件都相同的條件下,頁面是等到所有元素下載完成后,用戶才能看到內容并且交互,還是下載部分內容后,用戶就可以看到并交互了。這兩種形式對用戶而言是完全不同的感受,雖然他們的完成時間相同。
關于網絡使用效率,作者給我們的忠告是
最佳的應用性能是通過不使用網絡而獲得的- 1
這就告訴我們,能不使用網絡的時候,就不要使用網絡。知乎上有一個問題,”大公司里怎樣開發和部署前端代碼?”, 其中張云龍講的 前端靜態資源部署方案 就是對如何減少網絡使用最好的解讀。從最初沒有緩存(每次請求都要通過網絡獲取數據),到使用 304 協商緩存(通過網絡詢問內容是否有變化,沒變化就不需要下載資源),再到使用內容摘要精確控制緩存(無需通過網絡詢問資源是否有變化),步步減少對網絡的使用,從而提高了性能,有興趣可以移步張云龍的答案。
另外,在服務端的設計中,我們常會使用 Redis 來作緩存,提高數據訪問速度,但是我們也要想下,如果數據可以放在本地內存中,是不是可以減少對網絡的使用,從而提高系統的性能和穩定性呢?
性能的問題,是個很宏大的問題,在今后的博文中,我們會專門討論這個話題。
可伸縮性(Scalability)
可伸縮性是指架構支持大量組件或者大量的組件之間交互的能力。例如,我們的系統用于處理客戶端的請求。當請求量太大,系統支持不了如此大量的請求時,通常我們會如何做呢?一般通過水平擴展或者垂直擴展的方式,提高系統的處理能力。水平擴展,就是通過增加服務實例個數的方式提升處理能力,垂直擴展通過增加單個實例可以使用的資源提升系統的處理能力,例如增加 CPU 核數,增加內存,增加存儲空間,機械硬盤換 SSD等方式。
系統的可伸縮性就是指,我們是否可以很容易地通過水平擴展或者垂直擴展,增加系統的處理能力。例如,如果我們可以很容易地提高單核 CPU 的性能(垂直擴展),那對我們多核的需求也就沒那么強烈了,現實是單核 CPU 性能很難再提高,所以現在已經是多核時代,人們也越來越重視系統的并發能力(水平擴展)。又例如,現在大量使用的各種 NoSQL 數據庫,其中很重要的原因之一,就是水平擴展性,單個數據庫實例的處理能力畢竟有限,如果能很方便地通過加數據庫實例的方式增加系統的處理能力,那系統的可伸縮性就比較強。
簡單性
簡單性是指系統是否正確地使用了分離關注點的原則,對功能進行了劃分,使得每個組件都足夠簡單。簡單的組件容易被理解和實現,因此簡單性對系統設計而言,是非常重要的。
可修改性
可修改性是指對應用架構所作的修改的難易程度。可修改性進一步可以劃分成可進化性,可擴展性,可定制性,可配置性和可重用性。總的來說,可修改性就是指系統應對變化的能力。當新需求出現,或者現在的系統已經不適合當前的場景時,我們是不是可以通過簡單地調整系統,使得系統滿足當前的需求。
先談可擴展性。現在的 IDE, 無論是 Jetbrains 系列,還是 Eclipse,還是有宇宙第一 IDE 之稱的 Visual Studio,都提供了插件機制,方便地對系統的功能進行擴展(可擴展性)。又比如著名的 Nginx, 其本身提供了一個穩定的核心,同時我們可以按規范為其開發第三方模塊,擴展 Nginx 的能力,本來這種機制就增強了 Nginx 的可擴展性,但是因為 Nginx 第三方模塊需要用 C 編寫的,對很多人而言,開發效率不高,因此 agentzh(章亦春)的 OpenResty 項目受到廣泛的歡迎,OpenResty 把 Nginx 與 LuaJIT 結合,即保留了 Nginx 的高性能,又可以方便地使用 Lua 開發自己的應用,可以說進一步提升了系統的可擴展性。但是這還不夠,春哥準備在 OpenResty 之上,再定義一個小語言 EdgeLang, 這是一種基于規則的語言,用它寫的代碼,經過編譯,可以生成高效的 OpenResty Lua 代碼,可以說進一步降低了對 Nginx 進行擴展的難度。
其次是可定制性。例如 Java 9 中將出現的模塊化功能,Jigsaw。有了模塊化,我們就可以根據自己的需要對 JDK 進行細粒度的定制和裁剪,從而可以按需部署,不必將 JDK/JRE 中不需要的東西也部署到生產環境。
再次是可配置性,系統可以正常運行起來后,一些需要經常修改的東西,是寫死在了代碼里,還是可以很方便地進行配置,配置之后,系統可以自動檢測到變化,并加載生效,還是需要重啟系統?這些都反映可配置性的高低。
最后是可重用性。例如我們寫了個負載均衡器,這個負載均衡器是否與上下游的組件耦合在一起了?是不是可以很方便地用到其它上下游組件的環境中?如果可以在很多其它場景中使用,那重用性就好,如果和特定的上下游組件有強耦合,那重用性就差。
可見性
可見性是指一個組件對于其它兩個組件之間的交互進行監視或者仲裁的能力。例如在一個分布式的系統中,可能有大量組件存在,如果設計架構時,沒考慮可見性,那系統一旦出了問題,查起來就比較困難。
例如,Google 的論文 Dapper 出現后,就出現一系列用于分布式系統跟蹤的開源項目。例如 Zipkin, Pinpoint。
從上圖可以看出,用 Pinpoint 我們可以觀察分布式系統中,組件之間的交互情況,這就使得系統的可見性大大增強。
增強系統可見性,不僅能在系統出現問題時,快速定位系統瓶頸。也能在問題出現之初就感知到故障,而不是等到某一天突然發現某個系統已經不能正常服務,而我們卻不知道。
可移植性
如果軟件能在不同的環境下運行,那軟件就是可移植的。
例如,Java 平臺的應用,由虛擬機屏蔽了底層系統的細節,因此可以比較容易地在不同系統之間移植。這樣你就不必在多個平臺,編寫多套代碼了。
在設計系統時,我們就需要考慮系統的可移植性,例如移動端應用,是不是可以方便地在 iOS 和 Android 之間移植,服務端應用,是不是可以方便地移植到云端環境? 如果系統的實現,對特定環境的依賴比較重,那移植起來就不大容易。
可靠性
可靠性是指當組件,連接器或數據中出現部分故障時,一個架構容易受到系統層面故障影響的程度。
例如,系統中如果一個組件出現了故障,整個系統都被拖垮了,那這個系統的可靠性就比較差,如果系統有良好的降級策略,在部分組件故障時,仍然可以保證系統核心部分正常運轉,那系統的可靠性就比較好。這提示我們設計組件時,要避免由于自身故障,對外界造成大的影響,同時也要減少其它組件出現故障時,自己受到影響的程度,盡力提高整個系統的可靠性。
又例如,如果系統設計成一個單點,能服務一旦掛了,就無法正常提供服務了,這樣可靠性就比較差。如果提供了足夠的冗余,一個節點掛掉,其它節點還能提供服務,這樣可靠性就比較好。
基于網絡的架構風格
這一章主要解決的問題有
- 什么是架構風格
- 不同架構風格對系統屬性有什么影響
什么是架構風格
架構風格就是將一組架構約束組合起來,再給它一個名字。向一種架構風格添加一個約束,就形成了一種新的架構風格。
上圖是各種架構上的約束,組合起來就組成了架構風格。
我們學習架構風格,其實就是要了解各種架構約束,了解他們會帶來什么,他們組合起來又意味著什么。
基于網絡的架構風格
其中網絡的架構風格,主要有下面這些,他們都是各種架構約束的組合。
- 數據流風格
- 復制風格
- 分層風格
- 移動代碼風格
- 點對點風格
每種風格又可以細分出一些風格。
數據流風格
數據流風格分為下面兩個風格:
- 管道和過濾器(Pipe and Filter, PF)
- 統一管道和過濾器(Uniform Pipe and Filter, UPF)
管道和過濾器風格,在輸入端讀取數據流,在輸出端產生數據流,架構上的約束為組件之間要零耦合,不能共享狀態。統一管道和過濾器風格在此基礎上,增加了一個約束,即所有組件使用統一的接口。
我們可以看到很多數據流風格的例子。例如,UNIX/LINUX 中的 shell, 命令和命令之間可以任意組合,原因就是命令之間的接口是統一的標準輸入輸出,數據就可以在任意組合的命令之間流動,從而根據我們的需求,隨時組合成各種各樣的功能。
又例如,著名的編譯器工具鏈 LLVM。
源代碼經過編譯器前端,優化器,編譯器后端,最終形成機器碼。LLVM 有一套自己的代碼表示方式,稱為 LLVM IR。在 LLVM 內部,組件之間以 LLVM IR 作為接口統一的方式。編譯器前端將源代碼生成 LLVM IR, 優化器將 LLVM IR 轉化成優化后的 LLVM IR,編譯器后端將 LLVM IR 轉化成機器碼。
這樣做編譯器前端的人,可以專注于將各種語言轉化成 LLVM IR,不必關心怎么優化,怎么生成各類平臺上的機器碼。做編譯器優化的人,可以專注于優化 LLVM IR,不必關心前端和后端,多個優化器之間也可以組合。做編譯器后端的人可以專注由將 LLVM 轉化成各類平臺的機器碼。
通過這些例子,我們可以明白數據流風格的好處,這種風格簡化了系統的設計,各個組件之間以統一的方式通信,同時可擴展性,可進化性很好,想加個功能,或者替換某個功能,只需要新加一個組件,或者優化其中一個組件就可以了,其它組件不受到影響。每個組件在這個過程中,可重用性也得到極大提高。
但數據流風格也有缺點,例如組件太多,會影響整個系統性能,要統一接口,需要轉化數據格式,也會影響系統性能。
復制風格
復制風格又分為下面兩種風格:
- 復制倉庫(Replicated Repository, RR)
- 緩存(Cache, $)
所謂復制倉庫風格,就是通過多個進程提供相同的服務。我們對這種方式應該很熟悉,通過這種方式,整個系統可以處理更多請求,可伸縮性得到提高;每個進程壓力的減少,也可以降低響應時間;同時因為存在冗余,系統也變得更可靠。
緩存風格,就是復制之前請求的結果,下次再有請求時,直接返回之前緩存的結果。這也是比較常見的設計,例如瀏覽器的緩存,文件系統的緩存。采用緩存風格,對性能有改善,但是改善程度也受到緩存命中率的影響,因而改善程度不如復制倉庫風格;對網絡的使用效率也有提升,因為結果直接緩存了,不必穿過網絡,經過各種計算生成;
分層風格
分層風格有很多變種,但最基本的就是 客戶-服務器 風格,在這種風格的基礎上,再逐漸施加其它約束,形成新的風格。
分層風格分為下面的幾個風格:
- 客戶-服務器(CS)
- 分層與分層-客戶-服務器(LCS)
- 客戶-無狀態-服務器(CSS)
- 客戶-緩存-無狀態-服務器(C$SS)
- 分層-客戶-緩存-無狀態-服務器(LC$SS)
- 遠程會話(RS)
客戶-服務器風格中,客戶端通過連接器將請求發到服務器要求提供服務,而服務器提供一組服務,并監聽對這些服務的請求。客戶-服務器風格背后的原則是分離關注點,客戶端關注用戶接口,服務端關注業務邏輯的處理,數據的生成。兩端分離后,可以各自獨立地演化。我們可以關注一下前后端分離的一些實踐,來理解分離關注點原則對互聯網開發模式的改變。
在分層風格中,系統按層次來組織,每一層為上層提供服務,并使用下層的服務。典型的例子就是 TCP/IP 協議棧。
我們可以看出,分層之后,每一層只和其上層下層有耦合,減少了跨層的耦合。應用層的協議不必關心低層的數據鏈路層,各層都在自己的抽象層次上工作。不同的應用層可以復用下層的協議,可重用性得到提高。但分層系統會降低系統的性能,各層會有自己的數據格式,數據格式的層層轉換會帶來一定開銷。分層-客戶-服務器風格,是在客戶-服務器的基礎上添加了代理(proxy) 和網關(gateway) 組件。
無狀態-客戶-服務器風格,是在客戶-服務器風格基礎上添加了無狀態這一約束。無狀態的意思就是客戶端與服務端的會話狀態,不能在服務端保存。我們舉個移動廣告平臺跟蹤廣告展示,點擊的例子,來說明有狀態的設計與無狀態的設計的區別。
客戶端向服務端請求廣告,服務端除了返回廣告內容外,還需要返回展示上報和點擊上報跟蹤鏈接,當廣告有展示時,客戶端會訪問展示上報鏈接,服務端收到上報后,對展示進行計數,點擊上報也是如此。
我們先來看有狀態的設計:
這種設計中,服務端向客戶端返回的跟蹤鏈接只有一個 id,值為 123, 與此值相關的上下文信息,例如廣告位,請求 ip,時間等信息被存入數據庫。當廣告有展示時,客戶端上跟蹤服務上報,參數中即有 id 123, 跟蹤服務收到請求時,從數據庫中提取出與 123 相關的上下文信息,之后進入統計流程。
這種設計就是一種有狀態的設計,此處的狀態就是指 id 123 的上下文信息,可以看出他們被保存在服務端。這種設計的好處是,跟蹤鏈接會比較短,因而網絡上傳輸的數據會少。壞處有很多,一是系統的伸縮性變差,因為廣告上下文信息要在服務端存儲,并且在有展示時提取出來,當請求量變大,展示周期變大時,需要存儲的數據量都會被放大。二是系統的可靠性變差,因為不是請求的廣告都會有展示,請求的上下文信息不可能永遠存在服務端,總有過期時間,但是如果過期之后,展示上報到達跟蹤服務,就會提取不到之前的狀態信息。三是可見性下降,如果展示跟蹤失效,可能是客戶端出問題,也可能數據庫里沒數據了,數據沒存進去,數據過期,出了問題會很難查。
我們再看來無狀態的設計。
無狀態的設計中,與展示/點擊相關的上下文信息,直接放在跟蹤鏈接中。有展示時,上報的鏈接中就包含了與此展示相關的所有信息。好處就是系統伸縮性提高,因為展示相關的狀態不需要在服務端保存了,分散到了客戶端;同時可靠性也得到提高,因為狀態無需維護;另外,可見性也得到提高,因為展示相關的信息都在跟蹤鏈接里了,直接觀察是否上報了正確的鏈接就可以分析展示上報情況。壞處就是如果展示相關信息很多,鏈接會很長,在網絡上傳輸的數據會變多,也可能會被截斷。
當然,有狀態的設計自然有它的用處,但是不在上面我們舉例的那類場景。這就是我們要講的遠程會話風格。
遠程會話風格是客戶-服務器風格的變體,客戶端在服務器啟動會話,而應用的狀態完全保留在服務器上。這樣的好處是,客戶端的設計可以得到很大簡化,可重用性得到提高。而系統功能的擴展性也得到提高,因為升級服務端后,所有客戶端可以同時受益,想想升級客戶端與服務端的難度就可以明白這一點就多方便。當然,壞處就是因為服務端要保存客戶端狀態,降低了伸縮性。
移動代碼風格
移動代碼風格使用移動性(mobility)來動態地改變處理過程與數據源之間的距離。主要有下面幾種風格:
- 虛擬機(VM)
- 遠程求值(REV)
- 按需代碼(COD)
虛擬機風格應該是我們比較熟悉的,例如常用的 Java 語言運行在虛擬機上,虛擬機分離了指令與實現,可移植性比較好,因為虛擬機本身幫我們屏蔽了底層系統的細節,另外擴展性也比較好,例如 Java 虛擬機有了 G1 垃圾收集器后,所有平臺上的 Java 代碼均可同時受益。
遠程求值風格適用的場景是客戶端知道如何執行服務,但是缺少執行服務的資源,此時,客戶端可以將代碼發送到服務端,服務端組件執行完后,將結果返回客戶端。例如,我們用 Spark Shell 對大規模的數據作交互式的分析時,數據和計算資源都不在本地,但是客戶端知道分析機群上的哪些數據,知道如何分析,代碼會被發到 Spark 集群上,對數據進行分析,分析結果最后又返回 Spark Shell。
按需代碼風格中,客戶端知道如何訪問資源,但是不知道如何處理資源,此時客戶端可以向遠程服務器請求處理資源的代碼,在本地執行。典型的例子就是 JavaScript,瀏覽器從服務器上下載 JS 代碼,在瀏覽器中執行。這種方式的好處是,能夠方便地為已經部署的客戶端添加功能,改善了可擴展性。我們想添加新功能時,升級 JS 代碼就可以了,瀏覽器下載了新版本的 JS 代碼,就能使用新功能了。
點對點風格
點對點風格要介紹的一種風格叫 基于事件的集成(EBI),其含義是一個組件發布一個或者多個事件,其它組件對某類事件進行訂閱。
例如,在 Kafka 中,生產者向某個 topic 生產數據,可以有多個消費者消費這個 topic 中的數據。當我們想用這個 topic 中的數據做更多的事情時,只需要新增加一個消費者就可以了。這樣,系統的擴展性就很好。同時消費者可以獨立地演化,而不會影響其它消費者。想象一下,如果你把這些消費者的功能集中在某一個系統中,那每次添加功能,都要改這個系統,同時如果某個功能對資源的消耗比較大,因為其它功能和這個功能在同一系統中,就會對其它功能造成影響。
到這里,我們對各種架構風格的介紹就結束了。
設計 Web 架構
Roy Fielding 博士在定義了架構屬性,架構約束,架構風格之后,用這些理論去指導 Web 架構的設計。
之前已經說過,Web 規模開始擴大后,現有架構的缺陷已經不能支持這么大的規模,需要對其進行增強,擺在眼前的任務有:
- 定義在現有的早期 Web 架構中被公共地、一致地實現的架構通信的子集
- 識別出在這個架構中存在的問題
- 定義標準解決問題: URI, HTTP, HTML
架構風格就被用于解決這些問題,首先架構風格被用于描述現有 Web 架構背后的基本原理,包括分離關注點,簡單性,通用性等。然后通過為架構風格添加約束,使其滿足現代 Web 架構所需要的屬性。在修改 Web 架構時,要與新 Web 架構風格進行比較分析,以便在新提議部署前識別出其與 Web 架構風格的沖突。
表述性狀態轉移(REST)
在介紹完各種約束,架構風格后,REST 架構風格就變得水到渠成了。它是通過在一個空風格基礎上,不斷添加約束而形成的。
從空風格開始,先后添加下面的約束:
- 客戶-服務器
- 無狀態
- 緩存
- 統一接口
- 分層
- 按需代碼
之前我們已經講了這些約束對系統設計而言意味著什么。
最后用兩張圖總結一下 REST 架構風格推導中的約束與屬性的關系。
上圖可以看出,在 REST 架構推薦過程中,添加的各種約束。
上圖可以看出,各種約束為系統帶來的各種特性。
總結
文章到這里就全部結束了,這篇文章主要結合 Roy Fielding 博士論文的前三章介紹了基于風格的應用架構設計中,需要關注的架構屬性,架構約束,以及架構風格。有些內容基于經驗所限,可能無法做到表達精確,希望以后能不斷修改,補充。
總結
以上是生活随笔為你收集整理的架构风格与基于网络的软件架构设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 第八章《对象引用、可变性和垃圾回收》(下
- 下一篇: tensorflow2.x版本无法调用g