利用Netflix所打造的元件及各類大家熟知的工具,我們完全可以順利應對由微服務以及分散式計算所帶來的技術挑戰。
在過去一年當中,微服務已經成為軟體架構領域一個炙手可熱的新名詞,而且我們也能輕鬆舉出由其帶來的諸多比較優勢。然而,我們必須清醒意識到的是,一旦開始遵循微服務思路而對現有架構體系進行拆分,就意味著我們將不可避免地進入分散式系統領域。在之前的文章中我們曾經探討過分散式計算的八大認識誤區*,由此可見此類系統本身充滿著風險,而且一旦犯下這八種錯誤中的任何一種、我們都將面對災難性的後果。
在我個人看來,如果要將這些誤區總結成一句觀點,那就是:對於一套分散式系統來說,任何關於一致性或者可靠性的表達都毫無保障可言。我們需要假定系統當中的各種行為以及元件位置始終處於不斷變化狀態。由此產生的後果主要有兩點:元件有時候會導致糟糕的服務質量甚至令服務直接離線,我們則只能將其統稱為“故障”、而很難具體闡明到底是哪裡出了問題。一旦沒能得到妥善處理,此類故障將引中斷與停機,這意味著系統將無法按照既定設計方案為使用者提供服務專案。
有鑑於此,為了享受微服務所帶來的諸多優勢(包括鬆散耦合、自治服務、分散化治理以及易於持續交付等等),我們必須避免由單一故障依次遞進而最終導致系統崩潰的恐怖狀況。關於這一點,Erlang語言之父Joe Armstrong曾經在題為《如何構建永遠執行、自我修復且可擴充套件的系統》一文中作出過透徹的表述。在他看來,此類系統看起來與我們所說的微服務架構非常相近,但其著重強調的是其作為自我修復系統的容錯能力。那麼對我們來說,如何才能建立起這樣一套堅實可靠的系統方案?
Netflix公司在微服務架構的實施與推動方面一直扮演著先行者的角色。作為其業務構建的原則性方針之一,Netflix公司認為系統方案必須要能夠承受任意元件的突發性故障,而整體系統仍能繼續正常運轉(這意味著我們仍然能夠在該平臺上觀看電影,而Netflix也可以繼續記錄使用者的觀看喜好)。在嘗試建立這樣一套系統時,我們遭遇到以下這些常見的技術挑戰:
- 由於需要將系統拆分成多個分散式程式,我們要如何在保證一致性與可靠性的前提下將這些配置分發至這些程式當中?
- 當這些配置方案需要加以修改時,我們該如何在無需重新部署全部程式的前提下對配置內容進行更新?
- 在這樣一套系統當中,特別是對於部署於雲環境內的系統,各個程式不僅內容經常變動、所在位置亦會不斷轉換(特別是在進行故障轉移的情況下)。我們要如何準確判斷那些需要進行協同的程式的具體位置?
- 一旦我們檢測到了當前程式關聯性的幾種可能位置,我們該如何選擇接下來要進行通訊的程式例項?
- 假設在選定一個程式例項並與該例項進行通訊的過程當中該例項出現了故障,我們該如何防止由此引發的連鎖故障?
- 在系統綜合運作行為不斷給自治服務帶來演進拓撲結構的情況下,我們要如何對其狀態保持視覺化監控、從而作出有針對性的準確調整?
事實上,大家可以部署多種樣板模式及開源工具來解決上述技術挑戰。Netflix公司就構建出多種元件且加以開源,並在生產環境中進行了一系列測試。從理論角度講,我們能夠利用這些工具來建立起有能力“永遠執行、自我修復且實現規模化擴充套件”的系統。對剛剛著手建立分散式系統的朋友們來說,我們目前的第一要務在於理解這些實現模式、掌握Netflix元件並加以應用,而後將這些元件部署、管理並整合至自己的系統當中。由於採取任何新的技術依賴關係都會給軟體工程方案帶來前所未見的複雜性元素,因此我們建議大家最好直接採用Netflix的堆疊來儘可能減少此類潛在摩擦。
Spring工程技術團隊從建立之初至今一直在努力打造出足以應對Java複雜性的強大武器。我們的早期關注重點在於消除J2EE給企業級應用程式開發者帶來的生產效率影響。而著眼於最近一段時間,我們的主要精力則轉移到了實現雲-本地應用程式構建身上,而且這方面的大部分工作成果都被納入或者圍繞著Spring Cloud專案所展開。
Spring Cloud專案的既定目標在於為Spring開發人員提供一整套易於使用的工具集,從而保證其輕鬆構建起自己需要的分散式系統方案。為了實現這一目標,Spring Cloud以Netflix OSS堆疊為基礎將大量實現堆疊加以整合並打包。這些堆疊而後可以通過大家所熟知的各類基於註釋的配置工具、Java配置工具以及基於模板的程式設計工具實現交付。下面就讓我們一起了解Spring Cloud當中的幾類常見元件。
Spring Cloud Config Server
Spring Cloud Config Server能夠提供一項具備橫向擴充套件能力的集中式配置服務。它所使用的資料被儲存在一套可插拔庫層當中,後者目前能夠支援本地儲存、Git以及Subversion。通過利用一套版本控制系統作為配置儲存方案,開發人員能夠輕鬆實現版本與審計配置的內容調整。
圖一:Spring Cloud Config Server
配置內容會以Java屬性或者YAML檔案的形式體現。該Config Server會將這些檔案合併為環境物件,其中包含易於理解的Spring屬性模型以及作為REST API存在的配置檔案。任何應用程式都能夠直接呼叫該REST API當中所包含的配置資料,但我們也可以將智慧客戶端繫結方案新增到Spring Boot應用程式當中,並由後者自動將接收自Config Server的配置資訊分配至任意本地配置當中。
Spring Cloud Bus
Spring Cloud Config Server是一套強大的配置分發機制,能夠在保障一致性的前提下將配置內容分發到多個應用程式例項當中。然而根據其設計思路的限定,我們目前只能在應用程式啟動時對其配置進行更新。在向Git中的某一屬性傳送新值時,我們需要以手動方式重啟每個應用程式程式,從而保證該值被切實納入應用當中。很明顯,大家需要能夠在無需重啟的前提下完成對應用程式配置內容的更新工作。
圖二: 配備Spring Cloud Bus的Spring Cloud Config Server
Spring Cloud Bus的任務正是為應用程式例項新增一套管理背板。它目前依靠將一套客戶端繫結至一組AMQP交換與佇列當中來實現,但這一後端在設計上也實現了可插拔特性。Spring Cloud Bus為我們的應用程式帶來了更多管理端點。在圖二中,我們可以看到一個面向greeting屬性的值被髮送至Git當中,而後一條請求被髮送至應用A中的/bus/refresh端點。該請求會觸發以下三個事件:
- 應用A從Config Server處請求獲取最新版本的配置內容。任意註明了@RefreshScope的Spring Bean都會被重新初始化並載入新的配置內容。
- 應用A向AMQP交換機制傳送一條訊息,表明其已經收到更新指示。
- 通過監聽AMQP佇列而被納入Cloud Bus的應用B與應用C會獲取到上述訊息,並以與應用A同樣的方式實現配置更新。
現在我們已經有能力在無需重啟的情況下對應用程式配置進行更新了。
Spring Cloud Netflix
Spring Cloud Netflix針對多種Netflix元件提供打包方案,其中包括Eureka、Ribbon、Hystrix以及Zuul。接下來我將分別對它們作出講解。
Eureka是一套彈性服務註冊實現方案。其中服務註冊屬於服務發現模式的一種實現機制(如圖三所示)。
圖三:利用服務註冊實現服務發現
Spring Cloud Netflix通過直接將spring-cloud-starter-eureka-server關聯性新增到Spring Boot應用程式、隨後將該應用程式的配置類與@EnableEurekaServer相整合的方式病嵌入式Eureka伺服器的部署工作。
應用程式能夠通過新增spring-cloud-starter-eureka關聯性並將其配置類與@EnableDiscoveryClient相整合的方式加入到服務發現流程當中。通過整合,我們能夠將經過配置的適合DiscoveryClient例項注入至任意Spring Bean內。在我們所列舉的例項中,DiscoveryClient作為服務發現的一種抽象機制恰好可以通過Eureka實現,不過大家也可以將其與Consul等其它備選堆疊相整合。DiscoveryClient能夠通過服務的邏輯識別符號提供位置資訊(例如網路地址)以及其它與已註冊至Eureka的服務例項相關的後設資料。
Eureka提供的負載均衡機制僅支援單迴圈條件。而Ribbon提供的客戶端IPC庫則更為精巧,其同時具備可配置負載均衡機制與故障容錯能力。Ribbon能夠通過獲取自Eureka伺服器的動態伺服器列表進行內容填充。Spring Cloud Netflix通過將spring-cloud-starter-ribbon關聯性新增至Spring Boot應用程式的方式實現與Ribbon的整合。這套額外庫允許使用者將經過適當配置的LoadBalancerClient例項注入至Spring Bean當中,從而實現客戶端負載均衡(如圖四所示)。
圖四:使用客戶端負載均衡機制
在此類任務當中,我們可以利用Ribbon實現額外負載均衡演算法,包括可用性過濾、加權響應時間以及可用域親和等。
Spring Cloud Netflix還通過自動建立能夠被注入至任意Spring Bean的Ribbon強化型RestTemplate例項的方式進一步改進了Spring開發者的Ribbon使用方式。在此之後,開發人員能夠輕鬆將URL所提供的邏輯服務名稱遞交至RestTemplate:
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
@RequestMapping(“/”)
public String consume() {
ProducerResponse response = restTemplate.getForObject(“http://producer”, ProducerResponse.class);
return String.format(“{”value”: %s}”, response.getValue());
}複製程式碼
Hystrix能夠為斷路器以及密閉閘門等分散式系統提供一套通用型故障容錯實現模式。斷路器通常會被作為一臺狀態機使用,具體如圖五所示。
圖五:斷路器狀態機
斷路器能夠介於服務及其遠端關聯性之間。如果該電路處於閉合狀態,則所有指向該關聯性的呼叫通常將直接通過。如果某一呼叫失敗,則故障將被計入計數。而一旦失敗次數達到可配置時間區間內的閾值,該電路將被跳閘至斷開。在處於斷開狀態時,呼叫將不再被髮往該關聯,而由此產生的結果將可自行定製(包括報告異常、返回虛假資料或者呼叫其它關聯等等)。
該狀態機會定期進入所謂“半開”狀態,旨在檢測關聯性是否處於健康運作狀態。在這種狀態下,請求一般仍將繼續得以通過。當請求成功通過時,該裝置會重新迴歸閉合狀態。而如果請求失敗,則該裝置會重新迴歸斷開狀態。
Spring Cloud應用程式能夠通過新增spring-cloud-starter-hystrix關聯性並將其配置類與@EnableCircuitBreaker相整合的方式利用Hystrix。在此之後,大家可以通過與@HystrixCommand整合的方式將斷路器機制納入到任意Spring Bean方法內:
@HystrixCommand(fallbackMethod = “getProducerFallback”)
public ProducerResponse getValue() {
return restTemplate.getForObject(“http://producer”, ProducerResponse.class);
}
複製程式碼
以上例項中指定了一個名為getProducerFallback的備用方法。當該斷路器處於斷開狀態時,此方法將替代getValue接受呼叫:
private ProducerResponse getProducerFallback() {
return new ProducerResponse(42);
}複製程式碼
除了實現狀態機機制之外,Hystrix還能夠提供來自各斷路機制的重要遙測指標流,具體包括請求計量、響應時間直方圖以及成功、失敗與短路請求數量等(如圖六所示)。
圖六:Hystrix儀表板
Zuul能夠處理全部指向Netflix邊緣服務的輸入請求。它能夠與Ribbon以及Hystrix等其它Netflix元件相結合,從而提供一個靈活且具有彈性的Netflix服務路由層。
Netflix公司在Zuul當中載入動態過濾機制,從而實現以下各項功能:
- 驗證與安全保障: 識別面向各類資源的驗證要求並拒絕那些與要求不符的請求。
- 審查與監控: 在邊緣位置追蹤有意義資料及統計結果,從而為我們帶來準確的生產狀態結論。
- 動態路由: 以動態方式根據需要將請求路由至不同後端叢集處。
- 壓力測試: 逐漸增加指向叢集的負載流量,從而計算效能水平。
- 負載分配: 為每一種負載型別分配對應容量,並棄用超出限定值的請求。
- 靜態響應處理: 在邊緣位置直接建立部分響應,從而避免其流入內部叢集。
- 多區域彈性: 跨越AWS區域進行請求路由,旨在實現ELB使用多樣化並保證邊緣位置與使用者儘可能接近。
除此之外,Netflix公司還利用Zuul的功能通過金絲雀版本實現精確路由與壓力測試。
Spring Cloud已經建立起一套嵌入式Zuul代理機制,從而簡化常見用例當中UI應用需要將呼叫代理至一項或者多項後端服務處的對應開發流程。這項功能對於要求將使用者介面代理至後端服務的用例而言極為便捷,其避免了管理CORS(即跨域資源共享)以及為全部後端進行獨立驗證等複雜流程。Zuul代理機制的一類重要應用在於實現API閘道器模式(如圖七所示)。
圖七:API閘道器模式
Spring Cloud對嵌入式Zuul代理進行了強化,從而使其能夠自動實現檔案上傳處理。而與Spring Cloud Security配合之後,其能夠輕鬆實現OAuth2 SSO以及將令牌傳遞至下游服務等工作。Zuul利用Ribbon作為其客戶端與全部出站請求的負載均衡機制。Ribbon的動態伺服器列表內容通常由Eureka負責填充,但Spring Cloud也能夠通過其它來源填充該列表。Spring Cloud Lattice專案就已經能夠通過輪詢Cloud Foundry Diego的Receptor API填充Ribbon的伺服器列表。
跨入微服務領域的決定意味著我們將正式迎接分散式系統所帶來的諸多挑戰,而分散式系統絕不是那種能夠“湊合使用”的方案。因此,我們必須假設系統內各元件的行為及位置始終處於不斷變化當中,甚至經常表現出不可預知狀態。在今天的文章中,我們已經談到了幾種能夠幫助大家解決此類挑戰的現成模式,而且這些模式已經在Netflix OSS與Spring Cloud得到切實驗證。我個人建議大家在著手建立理想中的“永遠執行、自我修復且具備可擴充套件能力”的系統方案之前,首先對它們進行一番嘗試與體驗。
*備註:這八大誤區分別為:
1.網路環境是可靠的
2.延遲水平為零
3.傳輸頻寬是無限的
4.網路環境是安全的
5.拓撲結構不會變化
6.總會有管理員幫助解決問題
7.流量成本為零
8.網路內各組成部分擁有同質性
原文標題:Build self-healing distributed systems with Spring Cloud