雲端計算時代,如何快速搭建微服務?
近十年來,Spring因其提供的依賴注入功能而廣受Java開發者的歡迎,因其可以幫助大家開發出鬆散耦合的系統。簡單來說,使用者只需要專注於介面所提供的抽象,即可獲得具體的例項。如今隨著雲端計算越發流行,如何充分利用雲環境提供的自動伸縮能力,並與特定雲供應商實現鬆散的耦合,這已成為一個非常有趣的挑戰。“雲原生”這個概念應運而生。我們先來看看“雲原生”和“微服務”到底是什麼?
雲原生和微服務
“雲原生”你瞭解嗎?
很多人認為,雲原生僅僅是用雲提供商的服務來執行你的現有應用?
答案不是這麼簡單。雲原生會全面改變應用程式的設計、實現、部署和運維過程。
現在比較流行的雲原生定義如下:
在開發Spring框架和雲平臺的軟體公司Pivotal看來,雲原生的定義是:
雲原生是一種構建和執行應用程式的方法,這種方法可以完全發揮出雲端計算模型的優勢。
致力於建立和推動雲原生程式設計正規化的組織CNCF (Cloud Native Computing Foundation),對雲原生的定義是:
雲原生技術可以幫助企業通過公有云、私有云、混合雲等現代化的動態環境構建並執行可伸縮的應用。例如:容器、服務網格(Service Mesh)、微服務、不可變基礎架構(Immutable infrastructure)以及宣告式API。
總結一下,雲原生應用能充分利用雲端計算模型帶來的各種優勢, 而微服務是其中的一種實現形式。下面的定義會幫助你更清晰地理解:
雲原生應用是專門面向雲端計算環境設計的應用,而非簡單的應用遷移上雲。
“微服務”來啦!
“微服務架構風格是一種將單個應用程式開發成一套小型服務的方法,每個小型服務都在自己的程序中執行,並通過輕量級機制(通常為HTTP資源API)相互通訊。這些服務圍繞業務功能構建,可通過全自動部署機制獨立部署。此外,這些服務至少應該能集中管理,可以用不同程式語言編寫,並可以使用不同的資料儲存技術。” ——知名軟體工程師,敏捷開發方法創始人之一,Martin Fowler
由此可見微服務是一種可以協同工作的小型、專注、自治的服務。
小型、專注體現了微服務的單一職責(Single Responsibility)。一個服務只需要將一件事做好就夠了。自治則意味著容錯能力,每個服務可以彼此獨立地演化和部署。
微服務就其本質來說與雲端計算平臺的關聯極為密切,但微服務這個概念本身並不是新事物。這個概念多年前就出現了。
微服務的概念雖然出現已久,但並未真正流行。主要是因為傳統的本地化部署使微服務的落地相當困難。而云計算的出現提供了更好的擴充套件性、可靠性和可維護性,所以基於雲端計算的微服務實現和維護更加便利,也就流行起來。目前我們可以從微服務裡獲得收益包括但不限於這些:
- 彈性 :一個元件出現問題不會拖累整個系統,這通常是通過清晰定義的服務邊界實現的。
- 可伸縮性 :如果只有一個元件的效能達到上限,可單獨進行擴容,而無需對整個系統進行擴容。
- 易於部署 :可只改動一個服務,進而加快釋出週期並簡化排錯過程。
- 可組合性 :每個服務只做一件事,因此可以像Unix管道那樣輕鬆實現服務的複用。
- 可替換性 :每個小型的服務都可以更容易地使用更好的實現或技術來替換。
Spring Cloud
微服務有很多優點,但是構建微服務的過程依然相當複雜。為了使微服務架構輕鬆實現,業界定義了一些通用的模式。比較知名的有,集中化的中心化配置管理、服務註冊和發現、非同步訊息驅動以及分散式追蹤。Spring Cloud將這些微服務架構模式融合到具體實踐中,幫助我們更好地遵循雲原生最佳實踐。如下圖:
Spring Cloud獨特的價值主要體現在這幾方面:
- 為常用模式定義了通用抽象。這也是Spring解耦哲學的另一個精彩應用:每種模式並不與具體的實現緊密耦合。以Config Server為例,我們可以在不影響其他服務的前提下隨意更改配置所用的後端儲存。Discovery和Stream也採用了類似方式。
- 模組化元件。很多人第一印象認為Spring Cloud是一種重量級的全家桶式的解決方案。但實際上,這並不是一種全有或全無的解決方案,我們可以只選擇一個模組並將該模組用作一個微服務,其他服務依然可以靈活選擇其他任何框架。這就像樂高積木,我們只需要選擇自己需要的模組,並確保介面相容就行了。
那麼Spring Cloud的模組是如何融入微服務模式的?
Spring Cloud Config:集中化配置管理
為滿足Store config in environment
(儲存配置於環境中)以及微服務架構的要求,我們需要將所有服務的配置儲存在一個集中的位置。此外還需要具備下列功能:
- 支援開發、測試、生產等多種環境。這樣就可以為所有環境提供同一個軟體包,配置從環境中獲取。
- 以透明的方式引入配置。配置可以無需編寫程式碼自動獲取。
- 屬性變化後自動重新整理。服務應該能獲得變化的通知並重新載入新的屬性。
維持變更歷史並能輕鬆回退至老版本。這是一個很實用的功能,可以幫我們撤銷生產環境中錯誤的改動。
Spring Cloud Config通過兩個簡單的註解來支援這些功能。我們只需要在Config Server中註解@EnableConfigServer,並在其他服務中包含Starter即可啟用客戶端。詳情可參閱Spring Cloud Config文件 (ofollow,noindex" target="_blank">http://cloud.spring.io/spring-cloud-config/single/spring-cloud-config.html )。
Spring Cloud Discovery:服務發現
服務發現是大部分分散式系統和麵向服務的架構中一個重要的元件。這個問題看起來很簡單:客戶端如何確定運行於多個主機上的服務所使用的IP和埠。但隨著在雲環境中部署的服務越來越多,問題開始變得複雜了。
確定服務位置,包含兩部分:
- 服務註冊 :指服務在中央登錄檔中註冊自己位置的過程,此時通常需要註冊主機和埠號,有時候還需要註冊身份驗證憑據、協議、版本號和/或環境細節。
- 服務發現 :指客戶端應用通過查詢中央登錄檔確定服務位置的過程。
那麼又該如何選擇服務發現解決方案?此時有很多因素需要考慮:
- 監控 :如果已註冊的服務失敗瞭然後會怎樣?有時會立即撤銷註冊,或者超時後撤銷註冊,或者被其他程序撤銷註冊。因此通常需要為服務實現心跳檢測機制來確保服務始終線上,並確保客戶端通常可以正確、可靠地處理服務失敗。
- 負載均衡 :如果一個服務註冊了多個節點,客戶端該如何實現負載均衡?如果存在主(Master)服務,客戶端可以正確識別嗎?
- 整合方式 :登錄檔是否只提供了與少數語言的繫結(例如僅Java)?整合過程是否需要在應用程式中嵌入實現註冊和發現的程式碼?還是需要提供守護程序?
- 可用性問題 :失去一個節點後是否依然可以正常執行?是否可以在不造成中斷的前提下升級?登錄檔是整個架構的核心,這是否會導致單點故障?
Spring Cloud為註冊和發現提供了通用的抽象,我們只需要使用@EnableDiscoveryClient,相應的客戶端會自動繫結。Spring Cloud Discovery Eureka和Spring Cloud Discovery Zookeeper提供了服務發現的具體實現。我們需要根據具體地業務場景選擇不同的實現,詳情可參閱Spring cloud Discovery文件 (https://cloud.spring.io/spring-cloud-commons/multi/multi__spring_cloud_commons_common_abstractions.html )。
Spring Cloud Stream:訊息驅動的架構
既然我們有了眾多的微服務,那麼它們肯定需要互相通訊。傳統的同步呼叫顯然不能滿足微服務多變的複雜環境,所以非同步的訊息驅動是必然趨勢。其實,一切請求都可以視作是訊息,因此也就誕生了使用不同格式和API的訊息中介軟體。讓這些訊息中介軟體相互通訊無異於一場噩夢。解決這個問題其實很簡單,只需要定義統一的訊息介面,隨後為每個中介軟體提供介面卡,讓它們知道如何在自己的訊息和標準格式之間進行轉換就行了。這就是Spring Integration的核心設計理念。
Spring Integration的目標主要在於:
- 為複雜的企業級整合方案提供一種更簡單的模型。
- 推行非同步的訊息驅動行為。
- 促進現有Spring使用者漸進地採用這一技術。
同時Spring Integration的設計主要遵循了下列原則:
- 為了提高模組化程度和可測試性,元件必須鬆散耦合。
- 框架應在業務邏輯和整合邏輯之間強制實施關注點隔離(Separation of concerns)。
- 擴充套件點是抽象的,並且有合理地邊界,以此來促進複用和可移植能力。
詳情可參閱Spring Integration文件 (https://docs.spring.io/spring-integration/reference/htmlsingle/ )。
不過目前Spring Integration依然專注於底層,幷包含很多晦澀難懂的術語。所以這種程式設計模型不像其他Spring技術那麼易用,於是又誕生了Spring Cloud Stream。Spring Cloud Stream基於標準訊息格式和Spring integration提供的多種介面卡,可以工作在高層抽象中,藉此以更簡單的方式產生、處理和使用訊息。它也類似於Unix管道,我們只需要關注如何處理訊息,訊息將按照我們的預期來之,去之。詳情可參閱Spring Cloud Stream文件 (https://docs.spring.io/spring-cloud-stream/docs/current/reference/htmlsingle/ )。
Spring Cloud Sleuth & Zipkin:分散式追蹤
在微服務架構下,由於服務進行了拆分,一個請求很可能涉及多個服務,這些服務可能部署在不同的機器之上。儘管很多解決方案都實現了集中的日誌儲存和查詢,但是如果遇到呼叫失敗,想快速定位問題仍然非常困難。一般的做法是多次反覆在日誌中查詢相關的關鍵字來尋找線索。這種方法費時費力,而且容易出錯。實際上,我們需要的是一個可以幫助我們整合一次呼叫鏈的所有相關資訊的系統。
Spring Cloud Sleuth 通過引入Span和Trace的概念來實現這種聚合。通俗地講,一個Span就是一次服務的呼叫,而Trace就是包含多個Span的樹形結構,比如一次分散式呼叫會包含多個服務呼叫。Sleuth會把相應的SpanId和TraceId記錄在對應的日誌中。如下圖:
實際中,會被記錄的操作包括:
- MVC controller收到的HTTP請求
- 通過RestTemplate傳送的請求
- 通過Spring Cloud Stream Binder傳送和接受的請求
- 其他在Spring生態系統中的請求和回覆
然而,有了這些資訊還遠遠不夠,我們還需要把這些資訊整合,處理,並以簡單直觀的方式展現出來。這就是Zipkin發揮作用的地方。它提供的儲存模組和UI介面可以幫助我們理解整個呼叫鏈。如下圖所示。
Spring Cloud Azure
Spring Cloud 雖然對通用的模式提供了很好的實現,但如果想在實際的雲服務上面使用還有一定的距離。所以,Spring Cloud Azure遵循了Spring Cloud提供的最佳實踐和通用抽象,並在此基礎上更進一步提供了自動化的資源配置和自動配置Azure服務相關屬性的能力。藉此使用者只需要從較高層面瞭解Azure服務即可順利使用,而無需涉及有關配置和SDK API的底層細節。以Azure EventHub為例,我們只需要知道這是一種在設計上與Kafka類似的訊息服務,隨後即可使用Azure EventHub的Spring Cloud Stream Binder生成並使用訊息。
Spring Cloud Azure的設計理念主要包括:
- 簡化Spring Cloud與Azure的整合 。使用者無需修改現有程式碼即可輕鬆使用Azure服務,並且只需要提供最少的依賴和配置。
- 極簡配置 。為此可以充分利用Spring boot auto config,根據Azure資源管理器API預配置預設屬性值,但使用者也可以用自己的配置覆蓋預設值。
- 自動管理資源 。如果資源不存在,將能在使用者指定的訂閱和資源組中自動建立所需資源。
- 與雲供應商解耦 。使用者可以輕鬆地使用Azure服務以及Spring Cloud提供的便利性,無需受制於某個特定的雲供應商。
Azure資源管理器:自動配置和資源管理
配置,這可能是開發者最不願做的工作之一。配置每個屬性前,開發者必須完整閱讀相關文件並全面瞭解每個屬性的含義,隨後小心謹慎地從一個位置複製每個屬性,然後貼上到應用的屬性檔案中。然而麻煩還沒完,他們還需要為每個屬性提供必要的備註,以便讓其他開發者明白在每種場景下需要更改哪個屬性,並且怎樣做才不會出錯。我們想要解決這個痛點,因此基於Spring boot提供了自動配置功能。
舉例來說,如果想使用Azure EventHub,此時並不需要了解連線字串是什麼,只需輸入EventHub的名稱空間(類似於Kafka的叢集名稱)和EventHub名稱(類似於Kafka的話題名稱),其他都會自動配置。當然,我們也可以通過自定義配置覆蓋預設值。
雲最大的優點之一在於可以通過可程式設計的API建立並查詢自己的資源。這也是實現自動化的關鍵。Spring Cloud Azure可藉助Azure資源管理器實現自動化的資源配置。其實資源的範疇很大,例如AzureEventHub的Consumer group。當我們有一個新服務使用另一個新的Consumer Group時,無需手工建立。
Spring Cloud Stream Binder和Azure EventHub
在瞭解了Spring Cloud Stream的優勢後,假設你開始使用它,但希望將其遷移至Azure,此時該怎麼做?你可能已經使用了Kafka或RabbitMQ Binder,但Azure似乎並未提供此類託管式的Kafka或RabbitMQ服務。那麼怎樣用最快捷的方法來遷移?
實際上我們並不需要關注自己到底使用了哪種訊息中介軟體,只要有一個元件能提供類似的功能並滿足效能要求就行了。因此我們只需要將依賴項從Kafka Binder改為Azure EventHub Binder就行,完全不用更改任何程式碼即可平滑遷移。Azure EventHub Binder提供了豐富地高階功能,如下:
Consumer Group:
與Apache Kafka類似,EventHub也為輕量級Consumer組提供了類似的支援,但實現上略有差異。Kafka會將所有已提交的偏移量(Offset)儲存在Broker中,但EventHub偏移量的儲存工作需要手工進行。EventHub SDK提供了在Azure儲存帳戶中儲存偏移量的功能。
分割槽支援:
EventHub提供了與Kafka類似的物理分割槽概念,但與Kafka會在Consumer和分割槽之間自動再均衡的做法不同,EventHub使用了一種搶佔模式。Azure儲存帳戶可以充當「租約」來決定每個分割槽是哪個Consumer所擁有的。當新Consumer啟動後,它會嘗試著從負載最重的Consumer「搶佔」一部分分割槽,藉此實現負載均衡。
檢查點支援:
在分散式釋出-訂閱訊息系統中,主要存在三種訊息語義:最少一次(At-least-once)、最多一次(At-most-once)以及嚴格一次(Exactly-once)。目前我們只考慮了使用方:
-
最少一次
:Consumer接收並處理訊息,但在訊息成功處理完畢之前,並不向Broker發出確認。如果因為某些原因導致處理工作未能完成,例如Consumer節點故障,同一條訊息將被(作為分割槽中的下一條可用事件)重新處理,藉此確保訊息最少可以被使用一次。這種情況下,(在訊息被成功處理完成之前)Consumer可能會將同一條訊息處理多次,
Manual
檢查點模式為訊息處理完畢之後的手工檢查點操作提供了支援。 -
最多一次
:Consumer收到訊息並立即向訊息Broker傳送確認,隨後開始處理該訊息。但如果Consumer在處理過程中遇到故障,該訊息將無法重新處理,因為Broker會認為Consumer已經收到了這條訊息。這種情況下,Consumer最多將一條訊息處理一次,但可能因為處理過程中的錯誤而丟失某些訊息。這也是EventHub Binder預設的
Batch
檢查點模式。 - 嚴格一次 :在嚴格一次語義中,我們會通過唯一的訊息ID對已經處理過的訊息進行去重。或者也可以實施冪等的訊息處理機制。詳情可參閱Exactly once (https://www.confluent.io/blog/exactly-once-semantics-are-possible-heres-how-apache-kafka-does-it/ )。
通過使用自定義訊息頭暴露Checkpointer,Azure EventHub Binder即可支援不同的訊息使用語義。詳情可參閱Spring Cloud Stream Event Hub binder文件 或者也可以通過範例自行嘗試。
Spring資源和Azure儲存Blob
Spring資源為UrlResource
、ClassPathResource
和FileSystemResource
等基於流的資源的操作提供了通用介面。很明顯,Azure儲存Blob很適合充當這種BlobResource
。在這種Resource
中,所有實現方面的細節均已隱藏,不存在的檔案也可以自動建立。
詳情可參閱Spring Resource with Azure Storage文件,或通過範例自行嘗試。
Spring Cloud Azure Playground:一鍵執行微服務
儘管Spring Cloud提供了很好的微服務構建支援,但是對於首次嚐鮮微服務的小白,想迅速搭建一個基於Spring Cloud的可以執行的微服務仍然很有挑戰,通常包含以下工作:
- 需要為每個微服務建立單獨的專案和依賴。儘管Spring Initializr 在這方面提供了很好的支援,但是微服務的數量很多,每個分別建立也是很繁瑣的工作。
- 確保微服務之間的版本和依賴互相相容。
- 不同的微服務有不同的配置,而且有些有相互的依賴關係。手動配置很容易遺漏和犯錯。
- Spring Cloud 提供的基礎微服務有各自的註解和配置來啟動微服務。手工配置需要參考各自的樣例。
- 很多使用者希望可以一鍵在本地執行微服務來驗證可行性。手工編寫docker file也需要一定的時間和對各個服務關係的理解。
為了解決上述問題,我們構建了 Spring Cloud Azure Playground來幫助大家輕鬆構建微服務。提供的功能如下:
- 基於Spring Initializr,可以一次建立全套的可執行微服務
- 每個微服務包含完整的依賴,註解,配置和樣例。通過相應的docker file可以一鍵執行。
- 使用者可以自定義微服務的埠和名字,以避免與本地執行的其他服務衝突。
- 使用者可以選擇下載到本地或者推送到GitHub。方便團隊共享。
你可以通過以下連結訪問:https://aka.ms/springcloud 。如下圖:
目前,Spring Cloud Azure已全面開源並已釋出至Github (https://github.com/Microsoft/spring-cloud-azure )。歡迎感興趣的朋友進一步瞭解、使用或提出意見建議。
參考連結:
Spring Resource with Azure Storage文件:https://github.com/Microsoft/spring-cloud-azure/tree/release/1.0.0.M2/spring-cloud-azure-starters/spring-azure-starter-storage
Spring Cloud Stream Event Hub binder文件:https://github.com/Microsoft/spring-cloud-azure/tree/release/1.0.0.M2/spring-cloud-azure-eventhub-stream-binder
作者簡介
朱仲瑋,Azure資深工程師,主要致力於改進Java開發者在Azure的體驗,Spring Cloud Azure的主要貢獻者。曾就職於惠普,eBay和Bing,對分散式架構,微服務和大資料有豐富的經驗。
感謝張嬋對本文的審校。