ODCA最佳實踐翻譯:Architecting Cloud-Aware Applications (三)

雲應用設計模式

下面的章節詳細介紹了一些設計模式,這些現有的設計模式能夠有效地應用到雲服務應用程序設計中去。算法

電路開關

電路開關(Circuit Breaker)設計模式由Michael Nygard所提出,Netflix將它做爲雲服務的一部分進行了進一步的發展。在該模式下,一個「電路開關」被插入在發送請求和處理請求的組件之間。就像在一個電路中,一個開關有兩個狀態:閉合或者斷開。當電路開關閉合則整個電路激活,能量被傳輸;對於軟件來講,請求被髮送到正常處理它的組件上。當電路開關斷開則意味着正常路徑被打斷,則系統執行一個備份的代碼路徑。電路開關用於檢測電路中的故障而後使電路倒換到一個安全的備份狀態。如下條件能夠觸發軟件電路斷開:數據庫

  • 一個到遠端服務的請求超時;
  • 遠端服務的請求隊列滿了,意味着遠端服務不能處理更多的請求了;

這些因素能夠設計一個錯誤率做爲閾值,當閾值超過期,電路開關進入到斷開狀態。設計模式

當電路開關斷開,組件轉移到備用狀態,有三種備用狀態的策略:api

  • 失敗透明。經過設計一個備用API的方式產生一個替代響應,例如該方法能夠根據緩存構建返回響應或者根據默認值返回一個響應。
  • 失敗靜默。對請求返回空值。在返回值內容對於呼叫服務並不關鍵的場景是有用的。
  • 快速失敗。產生一個包含錯誤碼的響應,這意味着呼叫服務必須處理這些對用戶來講有意義的錯誤。

理想狀況下,組件最好應該失敗透明,但實際上每每不是總能作到這點。緩存

注意電路開關和根據條件進行響應(「若是請求超時,則返回錯誤碼500」)並不一樣:安全

  • 電路開關的觸發條件是隨着時間變化的,根據一個滑動窗口,而不是一個單一的失敗;
  • 當電路開發觸發後會進行保持,備份狀態會持續到開關被重置;
  • 電路開關的狀態組件外部是可見的,工具能夠看到電路開關的狀態以便進行故障處理;
  • 電路開關能夠由組件外部進行控制;

在Netflix,電路開關週期性地讓部分請求經過,若是請求處理成功,則開關重置,全部的請求都容許經過。下圖描述了電路開關的邏輯:服務器

Netflix經過儀表盤將電路開關的狀態可視化出來。這使得組件狀態更加一目瞭然,比傳統的使用異常碼的組件更加容易狀態可視化。網絡

下面的截圖顯示了一個電路開發服務在儀表盤中可視化的樣子:架構

** 電路開關模式 **
如下是電路開關設計模式的要點:併發

  • 優勢
    • 加強容錯性
    • 對於組件失敗可視化(經過對電路開發狀態的可視化)
  • 使用場景
    • 爲了減小因爲網絡或者虛機實例失敗形成的超時或者延遲
    • 爲了不錯誤級聯以及複雜的上游錯誤處理(失敗透明,失敗靜默)

請求隊列

請求隊列設計模式包含一個組件,將請求消息(或者任務)放入一個或者多個待處理隊列中。計算節點從隊列中取出請求消息進行處理。隊列做爲請求和處理服務之間的緩衝區能夠防止重負載致使的服務失敗或者超時。這種模式是生產者消費者或者基於隊列的負載均衡模式的一個變種。

將請求放入隊列,能夠方便負載被分佈的計算集羣進行處理。若是一個計算節點失敗,其它的工做者能夠繼續處理隊列中的請求。這提供了分級的容錯性能夠確保雲計算應用程序的高可用性。

消息隊列能夠充當閥門的做用,能夠經過阻止請求進入控制服務的資源消耗。若是某個服務超過了每秒給定的消息配額,能夠阻塞後續的請求進入該服務。對於進入的請求在入隊前能夠應用性能計數器,一旦某一客戶端的請求超過了雲應用程序的配置閾值,則後續的消息被阻塞。在互聯網應用中,能夠給客戶端發送HTTP 429錯誤碼(含義:請求過多)以及經過報文頭中的Retry-After指示多久服務沒法響應客戶端的請求。下圖用Microsoft Developer Network描述了隊列是如何有效地進行服務間的負載均衡的。

** 消息隊列模式 **
下面是消息隊列設計模式的特色:

  • 優勢
    • 增長容錯性以支持高可用
    • 對於高訪問的API能夠提高性能
  • 使用場景
    • 須要管理應用程序的故障轉移(容錯)
    • 對於高訪問的API下降負載
    • 將閥門機制做爲自動伸縮的替代策略

合併請求

合併請求設計模式將對同一API的相鄰請求進行緩存合併。例如,播放視頻的網絡應用程序須要從數據中心得到視頻的元信息(例如片長),傳統的實現中每一個用戶單獨發送請求獲取視頻的元信息,利用請求合併,必定時間間隔內的請求能夠併合併成一個,這樣減小了網絡帶寬消耗以及API的負載,使得API端能夠水平擴展支持更大量的併發用戶。

爲了合併請求,對API使用一個代理(Proxy)將給定時間窗(例如10ms)內的請求進行排隊,週期地將隊列內的消息合併成一個請求發給API。響應消息則併發地分發給全部請求者。這種優化對高併發的請求頗有意義。若是隊列中只有一個消息,它也得等夠時間窗的時間長度才能發送,這會引入必定的延遲可能會影響性能。通常請求會落在時間窗內的任何位置,因此引發的平均時延等於時間窗的一半(對於10ms的時間窗,平均延遲爲5ms)。下圖給出請求合併的一個例子:

對象變動通知

傳統的緊耦合的軟件架構中,系統組件間的關係是固定的。例如兩個組件A和B存在依賴,當A變化的時候須要通知B,這時A知道B只有一個實例,同時也知道B的位置。若是B因爲某種緣由不可訪問了,A沒法將通知發送給B,這時A必須實現某種機制去處理這種場景。A和B之間的這種依賴致使了系統存在單點失敗,影響系統的可擴展性。

分佈式系統中若是存在許多這樣的靜態依賴,每個都會下降系統總體的彈性。對該問題的一個解決方式是爲架構引入冗餘,用一對多的關係代替組件間的一對一關係。例如A再也不依賴於固定的B,而是存在B的多份實例,任意B的失敗給A帶來的影響能夠忽略不計。

對象變動通知模式,也被稱做觀察者模式,在這種處理模式下使用能夠加強系統的彈性。

該模式下,通知組件(A)實現一個可觀察(Observable)的接口,觀察者(B)經過該接口註冊本身感興趣的變化。當A發生一些變化,它則將變化通知給全部的觀察者們。觀察者能夠經過變化通知消息直接獲取變化內容(push mode),也能夠根據事件內容再去向被觀察對象請求變化細節(pull mode)。

雲環境下該模式可使得應用程序具有彈性擴展的能力。由於組件再也不是緊耦合,額外的實例能夠動態的添加以處理增長的負載。這對於計算或者I/O密集型的任務作並行化頗有價值。例如Netflix爲了適應不一樣終端設備和網絡帶寬,須要將視頻轉換成不一樣碼率的好多版本。利用對象變動通知模式,當第一個新的視頻加載到存儲時,一個代理檢測到存儲設備發生了變化則觸發產生一組轉碼任務去並行處理不一樣碼率的視頻版本,當計算完成,這些轉碼任務自行關閉。

** 對象變動通知模式 **
下面是對象變動通知模式的要點:

  • 優勢:
    • 提升容錯性
    • 增長可擴展性
    • 更高的資源利用率
  • 使用場景
    • 爲了將計算密集型任務並行化
    • 爲了減小單點失敗
    • 爲了減小組件間的耦合以即可以替換組件的實現

服務發現

在分佈式應用中,組件須要知道對端服務地址才能向對端發送請求。在傳統的分層架構中,服務的主機名存在配置文件中。DNS用來查詢主機的實際IP地址。爲了可擴展性,IP能夠指向一個負載均衡使得負載能夠分發給多個服務實例。

雲環境具備高度的動態性,服務和組件實例不斷的因爲負載的變化或者故障緣由而產生和消失。在雲中基於DNS的負載均衡可能會將請求轉給不存在或者已經失敗的服務實例。解決方案是實現一套雲服務的發現機制,只有正常的服務實例是可被定位的。這樣的服務發現機制應該具有如下能力:

  • 提供給組件一種直接將請求發給可用實例的方式
  • 支持服務實例的動態註冊和註銷
  • 對於給定實例能夠查詢它的狀態

Netflix實現的服務註冊「Eureka」具有上述能力。它是一個分佈的服務,同時嵌入在應用程序的服務組件和客戶端中。

以下圖,Eureka服務維持了一組健康服務的註冊列表。註冊的服務須要按期發送心跳更新它們的狀態。若是和已註冊服務之間鏈接失敗,則發現服務在一個超時週期後將它從註冊列表裏移去。當新的服務啓動的時候,它們向Eureka註冊,一樣當實例終止或者失敗他們自動的從註冊中去掉不會影響別的服務。註冊機制意味着只有準備好處理消息的服務纔會在註冊列表裏面,正在上線過程當中的服務不會。

每一個客戶端組件內嵌了Eureka的客戶端程序,它維護了一個從Eureka服務端拷貝下來的註冊服務的一份緩存。經過這種方式可用服務的知識在整個環境中備份着,這自然爲系統提供了容錯性,避免Eureka服務端故障致使網絡不可用。Eureka客戶端根據某種算法將請求負載均衡到不一樣的處理服務上,默認的負載均衡算法是輪詢,能夠對算法進行修改。

實際上,Eureka將決定請求發送給哪個服務的決策放到每個客戶端,將這種行爲封裝在客戶端的代碼庫中,服務端開發者則下降了對服務端不可用的設計。由於註冊列表中的同類服務會輪流處理請求,Eureka要求服務是無狀態的。這是可擴展性和彈性的先決條件,是雲應用架構的廣泛原則。

由於客戶端組件緩存着可用服務實例,因此它們可以在Ereka服務端故障的時候繼續運行。當分佈式環境跨越多個數據中心或者雲,這時網絡發生分區故障後每一個區域的Eureka服務能夠繼續運行,由於服務的註冊信息在每一個區域都存在冗餘。

** 服務發現模式 **
如下是服務發現模式的要點:

  • 優勢:
    • 提升容錯性
    • 簡化基礎架構的管理
    • 簡化動態環境的應用程序開發
  • 使用場景:
    • 在須要支持自動擴展的場景

微服務

微服務設計模式中,一個單體(monolithic)服務的功能被分解成一系列良好設計的單一職責的微服務。例如,郵件消息系統中的提供消息建立、讀取、更新和刪除的消息存儲服務,能夠將其每一個功能分解成一個獨立的服務,其中讀取服務僅提供讀取消息的功能。微服務有不少優勢,包含更好的彈性和性能、更高的可靠性以及易於部署。

彈性的提升是由於微服務下每一個小的服務均可以獨立的伸縮。例如,若是消息的讀取遠大於寫的請求,這時額外的讀服務能夠被建立來消化負載。而在原來消息存儲做爲一體的狀況下,整個服務都須要複製,這時水平擴展就會引發沒必要要的資源浪費。

微服務簡化了軟件的開發和部署。軟件開發人員能夠擁有一個微服務完整的開發和獨立發佈的權利,若是消息讀取服務的開發者改進了消息的緩存功能,則能夠直接將此發佈到生產環境而不用與其它消息存儲功能進行集成。這種解耦的特性開發使得開發人員能夠按照本身的時間表並行工做。

"部署到雲上最好的方法是什麼,最好的方法是編寫可重用的代碼,咱們如何快速響應業務變化?答案彷佛是微服務。經過建立小的獨立的服務,程序能夠專一將一件事作好 - 咱們減小了開銷,增長了可伸縮性和彈性。開發成百上千的小的程序,去除過多的耦合,促使開發人員從新審視與重構架構,最終建立能夠快速響應的、具備彈性的雲應用程序。"

  • Michael Forhan, AppFirst blog: 「Lessons from Distill」

Netflix已經開發了一套微服務升級部署的方法。這種方法中再也不一次升級服務的全部實例,而是部署新的版本的一個實例進行冒煙測試。若是新的版本失敗了,對整個系統的影響很小,開發人員能夠修復問題快速的從新部署。若是微服務的新版本運行正常,一組新的實例則被建立分擔現存在實例的負載。新的實例會被密切監控幾小時以便確認沒有內存泄露等其它問題。若是發現存在問題,則將負載從新路由回舊的實例。若是新的微服務版本最終功能一切正常,舊的實例則會在必定時間後自動關閉。Netflix經過持續交付的方式將這一過程自動化,從而加快部署,實現了快速迭代,並且下降了錯誤的可能。Nerflix採用了一套簡單的哲學:錯誤不可避免,可是須要可以快速恢復而不影響總體服務。

微服務的另外一個好處是若是發生故障,很容易定位出故障的緣由,由於錯誤範圍每每內聚在微服務的邊界內,容易定位出哪次升級致使了故障。

** 微服務模式 **
下面是微服務設計模式的要點:

  • 優勢:
    • 減小升級部署的負擔
    • 減小因爲服務共築引發的反作用
    • 提升彈性
    • 對於問題根源的可視化程度更高
    • 提升開發效率
  • 使用場景:
    • 在大的系統中,須要大規模的彈性和成本效益
    • 爲了消除單點失敗
    • 爲了改進性能
    • 爲了支持對關鍵系統的頻繁升級

服務無狀態化

無狀態要求服務不保存每一個客戶請求之間的客戶端狀態,相應的每一個請求消息必須攜帶服務須要處理的全部上下文信息。這一設計模式對於分佈式系統提供了不少的好處,包括:

  • 可靠性。可靠性體如今一個客戶端能夠對一個失敗的請求進行重試,而不用服務端重構失敗前的狀態;
  • 可擴展性。無狀態化能夠經過幾個途徑提升可擴展性。由於服務端實例不存儲狀態,任何實例均可以處理任何客戶端的任何請求,隨着請求負載的增長,在不影響現有實例的狀況下能夠動態地啓動其它實例來進行負載分擔。此外因爲服務端不存儲任何客戶端狀態,服務的資源消耗相對輕量級,能夠提升總體資源利用率使得每一個服務端能夠支持更高的請求負載。
  • 可視化。從運維的角度看,每一個請求和響應消息的淨荷中包含了全部交互信息,這使得能夠容易使用請求代理等方案,對請求的語義具備更大的可視化空間。
  • 簡單。當服務再也不存儲狀態,則就沒有必要管理請求在屢次事務間的數據。無狀態服務的實現也相對容易,更容易調試。無狀態服務可預測性更高,因爲巧合引發的bug也會被消除。

無狀態服務的缺點則是消息的負載會比較大,由於消息中攜帶了全部服務端須要處理它的上下文。然而無狀態設計總體帶來的優勢要比缺點更多。

** 無狀態服務模式 **
下面是無狀態服務設計模式的要點:

  • 優勢:
    • 增長可靠性
    • 增長可擴展性
    • 提升了請求消息管理和監控的可視化
    • 實現簡單
    • 相比狀態敏感的服務bug更少
  • 使用場景
    • 任何須要高擴展性的分佈式系統

配置服務

傳統應用程序的運行時配置一般依靠應用的文件系統上的一個多個文件。在某些場景,能夠對這些文件進行運行時修改(也就是說文件被熱加載)。更通常的場景下這些配置文件在應用啓動的時候進行加載,這意味着配置修改須要從新啓動或者加載應用程序,這時該過程會致使應用停機以及付出額外的管理成本。

一般這些配置文件被存儲和管理在應用程序本地,致使應用程序部署的每一個節點上都須要冗餘地存儲配置文件。當應用程序的集羣增加,每一個節點配置文件的管理開銷開銷也隨之增加。

外部配置存儲

將應用程序的配置文件持久化在外部數據存儲區中,爲雲計算應用程序的配置提供一箇中心化的管理倉庫。將配置移出本地應用實例,可讓跨越應用程序的配置管理和共享變得容易。外部配置數據存儲能夠能夠在不一樣的運維環境中變化。

數據庫或者文件倉庫能夠提供對配置文件讀取和寫入的最大靈活性。敏感信息如密碼等能夠在應用程序動態構建的時候動態注入,或者經過應用程序啓動時動態加載的環境變量進行加載。下圖說明了外部配置存儲和應用程序加載過程。

運行時重配

良好設計的雲計算應用程序應該具有高可用性,儘可能減小停機時間。任何須要應用程序從新部署的配置變化都會增長停機時間。運行時重配須要應用程序可以檢查配置變動從而在運行時從新加載配置。

支持運行時重配須要應用程序源代碼設計可以處理配置變動通知消息。通常這會經過觀察者模式來實現。應用程序向中心配置服務註冊配置變動消息。當通知消息被觸發,例如新的配置文件上傳,應用程序將獲得通知並得到最新的配置,將其加載到內存。

Apache Zookeeper是Apache軟件基金會用來支持運行時重配的開源項目。ZooKeeper提供了一個集中服務用來管理配置信息、命名、並提供分佈式同步和組服務。ZooKeeper能夠被用做共享配置服務,也就是用來作集羣協調。

使用ZooKeeper來存儲配置信息具備兩個主要的好處:

  1. 新的節點能夠接收到如何鏈接到ZooKeeper的指令,而後從ZooKeeper上下載全部的配置信息同時決策它們在集羣中的合適角色。
  2. 應用程序能夠訂閱配置的更改,在運行時容許經過ZooKeeper客戶端得到最新的配置完成雲計算應用程序的集羣行爲修改。ZooKeeper運行在稱做「ensemble」的服務器集羣上進行應用程序的數據狀態共享,它能夠協調雲計算應用程序集羣的分佈式一致性。

** 配置服務模式 **
下面是配置服務設計模式的要點:

  • 優勢:
    • 對於配置管理的獨立集中的數據存儲;
    • 減小應用程序配置管理的負擔;
    • 減小應用停機;
  • 應用場景:
    • 任何須要應用程序配置的分佈式系統;
    • 須要減小停機時間的高可用場景;

受權模式

在雲環境下,不能假定網絡是安全的,由於不少因素都不在應用程序全部者的控制下。這意味着第三方可能監聽或者重定向傳輸的數據。另外的風險是API可能會被多租戶雲環境中的其它租戶訪問,或者被外部互聯網進行訪問。這種暴漏性致使了風險,API可能成爲DoS攻擊的對象,或者致使信息泄露以及使服務遭到惡意破壞。雲應用程序更容易受到這些攻擊,由於它們是由互相依賴的服務組成,每個網絡依賴的接口都會增長應用程序的風險。解決方案是爲網絡流量進行加密以及爲API提供受權保護機制。

API受權

API受權保證了只有被信賴的API客戶可以訪問API,防止服務被惡意訪問,還能夠支持對不一樣客戶端分配不一樣的權限。例如,一些客戶端僅被受權只讀權限,而其它有讀寫權限。受權還能夠用來限制哪些客戶能夠訪問服務。例如,數據庫的API能夠經過訪問限制來確保客戶端使用合適的數據訪問權限。

應用程序一般使用用戶名和密碼來進行認證。這種方法有兩個主要的問題:

  1. 客戶端須要保存用戶名和密碼,這使得帳戶信息可能被泄露,而密碼機制則是出了名的脆弱的。
  2. 管理每一個組件的用戶名和密碼增長了複雜性和管理開銷以及配置錯誤的風險。

OAuth2.0協議(RFC6749)引入了基於令牌進行訪問控制的概念來解決這些問題。在OAuth,受權服務器給客戶特定的令牌來提供特定的訪問權限。

以下圖所示,對於OAuth控制服務:

  • 客戶端向受權服務請求對待訪問服務的認證令牌(1);
  • 受權服務校驗身份驗證憑據,若是有效返回訪問令牌(2);
  • 客戶端在全部對服務端的請求中攜帶訪問令牌(5);
  • 對每個請求,服務端經過受權服務檢查令牌的合法性(6);
  • 受權服務對被訪問服務提供和令牌相關聯的一組權限(7);
  • 服務端使用這組權限來決定每一個請求的操做權限,執行相應的動做而後給客戶端返回響應數據(8);

除了在每一個請求中傳遞令牌,爲了安全性還會給令牌設置生命週期。當令牌過時後,客戶端使用一個刷新令牌來請求一個新的訪問令牌。

** 受權認證設計模式 **
下面給出了受權認證設計模式的要點:

  • 優勢:
    • 提升安全性;
    • 細粒度的服務訪問控制;
  • 適合場景:
    • 對於全部客戶端和服務端之間的請求;
相關文章
相關標籤/搜索