基於SpringCloud分散式架構
為什麼要使用分散式架構
- Spring Cloud 專注於提供良好的開箱即用經驗的典型用例和可擴充套件性機制覆蓋
- 分散式/版本化配置
- 服務註冊和發現
- 路由
- Service-to-Service 呼叫
- 負載均衡
- 斷路器
- 分散式訊息傳遞
這是分散式的優點,這樣看起來可能比較抽象,舉個例子來說,對於單體服務來說,如果我想更新訂單中的某個功能,我是不是需要重啟整個服務。
這個時候就會導致整個專案都處於不可用狀態,或者在處理訂單的時候由於程式程式碼寫的有問題,導致死鎖了,這個時候也會導致整個服務處於當機專改,容錯率很差。
但是分散式不同,如上圖所示,訂單服務、售後服務、使用者服務都是獨立的服務,如果需要更新訂單服務或者訂單服務發生死鎖,受影響的只會是訂單服務,售後服務與使用者服務還是可以正常工作的,這就是分散式相對單體來說最大的優勢之一。
分散式基礎元件
Spring Cloud Config:配置管理工具包,讓你可以把配置放到遠端伺服器,集中化管理叢集配置,目前支援本地儲存、Git 以及 Subversion。
Spring Cloud Bus:事件、訊息匯流排,用於在叢集(例如,配置變化事件)中傳播狀態變化,可與 Spring Cloud Config 聯合實現熱部署。
Eureka:雲端服務發現,一個基於 REST 的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。
Hystrix:熔斷器,容錯管理工具,旨在通過熔斷機制控制服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。
Zuul:Zuul 是在雲平臺上提供動態路由,監控,彈性,安全等邊緣服務的框架。Zuul 相當於是裝置和 Netflix 流應用的 Web 網站後端所有請求的前門。
Archaius:配置管理 API,包含一系列配置管理 API,提供動態型別化屬性、執行緒安全配置操作、輪詢框架、回撥機制等功能。
Consul:封裝了 Consul 操作,Consul 是一個服務發現與配置工具,與 Docker 容器可以無縫整合。
Spring Cloud for Cloud Foundry:通過 Oauth2 協議繫結服務到 CloudFoundry,CloudFoundry 是 VMware 推出的開源 PaaS 雲平臺。
Spring Cloud Sleuth:日誌收集工具包,封裝了 Dapper 和 log-based 追蹤以及 Zipkin 和 HTrace 操作,為 Spring Cloud 應用實現了一種分散式追蹤解決方案。
Spring Cloud Data Flow:大資料操作工具,作為 Spring XD 的替代產品,它是一個混合計算模型,結合了流資料與批量資料的處理方式。
Spring Cloud Security:基於 Spring Security 的安全工具包,為你的應用程式新增安全控制。
Spring Cloud Zookeeper:操作 Zookeeper 的工具包,用於使用 Zookeeper 方式的服務發現和配置管理。
Spring Cloud Stream:資料流操作開發包,封裝了與 Redis、Rabbit、Kafka 等傳送接收訊息。
Spring Cloud CLI:基於 Spring Boot CLI,可以讓你以命令列方式快速建立雲元件。
Ribbon:提供雲端負載均衡,有多種負載均衡策略可供選擇,可配合服務發現和斷路器使用。
Turbine:Turbine 是聚合伺服器傳送事件流資料的一個工具,用來監控叢集下 Hystrix 的 Metrics 情況。
Feign:Feign 是一種宣告式、模板化的 HTTP 客戶端。
Spring Cloud Task:提供雲端計劃任務管理、任務排程。
Spring Cloud Connectors:便於雲端應用程式在各種 PaaS 平臺連線到後端,如:資料庫和訊息代理服務。
Spring Cloud Cluster:提供 Leadership 選舉,如:Zookeeper,Redis,Hazelcast,Consul 等常見狀態模式的抽象和實現。
Spring Cloud Starters:Spring Boot 式的啟動專案,為 Spring Cloud 提供開箱即用的依賴管理。
我們常用的元件:
- Spring Cloud Config
- Spring Cloud Bus
- Hystrix
- Eureka
- Zuul
- Ribbon
- Feign
Eureka
Eureka 屬於 Spring Cloud Netflix 下的元件之一,主要負責服務的註冊與發現,何為註冊與發現?
在剛剛我們分析的分散式中存在這一個問題,那就是訂單服務與使用者服務被獨立了,那麼他們怎麼進行通訊呢?比如在訂單服務中獲取使用者的基礎資訊,這個時候我們需要怎麼辦?
如果按照上面的架構圖,直接去資料庫獲取就可以了,因為服務雖然獨立了,但是資料庫還是共享的,所以直接查詢資料庫就能得到結果,如果我們將資料庫也拆分了呢?這個時候我們該怎麼辦呢?
有人想到了,服務呼叫,服務呼叫是不是需要 IP 和埠才可以,那問題來了,對於訂單服務來說,我怎麼知道使用者服務的 IP 和埠呢?在訂單服務中寫死嗎?如果使用者服務的埠發生改變了呢?
這個時候 Eureka 就出來了,他就是為了解決服務的通訊問題,每個服務都可以將自己的資訊註冊到 Eureka 中,比如 IP、埠、服務名等資訊,這個時候如果訂單服務想要獲取使用者服務的資訊,只需要去 Eureka 中獲取即可。
請看下圖:
這就是 Eureka 的主要功能,也是我們使用中的最值得注意的,他讓服務之間的通訊變得更加的簡單靈活。
Spring Cloud Config
Spring Cloud Config 為分散式系統中的外部配置提供伺服器和客戶端支援。使用 Config Server,您可以在所有環境中管理應用程式的外部屬性。
客戶端和伺服器上的概念對映與 Spring Environment 和 PropertySource 抽象相同,因此它們與 Spring 應用程式非常契合,但可以與任何以任何語言執行的應用程式一起使用。
隨著應用程式通過從開發人員到測試和生產的部署流程,您可以管理這些環境之間的配置,並確定應用程式具有遷移時需要執行的一切。
伺服器儲存後端的預設實現使用 Git,因此它輕鬆支援標籤版本的配置環境,以及可以訪問用於管理內容的各種工具。可以輕鬆新增替代實現,並使用 Spring 配置將其插入。
簡單點來說集中來管理每個服務的配置檔案,將配置檔案與服務分離,這麼多的目的是什麼?
舉個簡單的栗子,我們配置檔案中肯定會存在資料庫的連線資訊,Redis 的連線資訊,我們的環境是多樣的,有開發環境、測試環境、預釋出環境、生產環境。
每個環境對應的連線資訊肯定是不相同的,難道每次釋出的時候都要去修改一下服務中的配置檔案?
我能不能將這些變動較大的配置集中管理,不同環境的管理者分別對他們進行修改,就不需要再服務中做改動了,Config 就做到了。
這就是 Config 的大致架構,所有的配置檔案都集中交給 Config 管理,拿 Config 怎麼管理這些配置檔案呢?
你可以將每個環境的配置檔案存放再一個位置,比如 Lgitlab、SVN、本地等等,Config 會根據根據你設定的位置讀取配置檔案進行管理,然後其他服務啟動的時候直接到 Config 配置中心獲取對應的配置檔案即可。
這樣開發人員只需要關注 -dev 的配置檔案,測試人員只需要關注 -test 的配置檔案,完全和服務解耦,你值得擁有。
Netflix Zuul(閘道器)
路由在微服務體系結構的一個組成部分。例如,/可以對映到您的 Web 應用程式,/api/users 對映到使用者服務,並將 /api/shop 對映到商店服務。Zuul 是 Netflix 的基於 JVM 的路由器和伺服器端負載均衡器。
Netflix 使用 Zuul 進行以下操作:
- 認證 -洞察
- 壓力測試
- 金絲雀測試
- 動態路由
- 服務遷移
- 負載脫落
- 安全
- 靜態響應處理
- 主動/主動流量管理
我們在日常開發過程中並不會使用那麼多,基本上就是認證、動態路由、安全等等,我畫了一張關於閘道器的架構圖,請看:
注意:Nginx 只能為我們做反向代理,不能做到許可權認證,閘道器不但可以做到代理,也能做到許可權認證、甚至還能做限流,所以我們要做分散式專案,少了他可不行。
Spring Cloud Bus
application.yml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test
driver-class-name: com.mysql.cj.jdbc.Driver
比如上面這行配置大家都應該很熟悉,這是資料庫的連線資訊,如果它發生改變了怎麼辦呢?
我們都知道,服務啟動的時候會去 Config 配置中心拉取配置資訊,但是啟動完成之後修改了配置檔案我們應該怎麼辦呢,重啟伺服器嗎?
我們可以通過 Spring Cloud Bus 來解決這個問題,Spring Cloud Bus 將輕量級訊息代理連結到分散式系統的節點。然後可以將其用於廣播狀態更改(例如,配置更改)或其他管理指令。
我們可以通過 Spring Cloud Bus 來解決這個問題,Spring Cloud Bus 將輕量級訊息代理連結到分散式系統的節點。然後可以將其用於廣播狀態更改(例如,配置更改)或其他管理指令。
這個需要我們有一點的 MQ 基礎,不管是 RabbitMQ 還是 Kafka,都可以。
Bus 的基本原理就是:配置檔案發生改變時,Config 會發出一個 MQ,告訴服務,配置檔案發生改變了,並且還發出了改變的哪些資訊,這個時候服務只需要根據 MQ 的資訊做實時修改即可。
這是一個很簡單的原理,理解起來可能也不會怎麼難,畫個圖來理解一下:
大致流程就是這樣,核心就是通過 MQ 機制實現不重啟服務也能做到配置檔案的改動,這方便了運維工程師,不用每次修改配置檔案的時候都要去重啟一遍服務的煩惱。
Feign
Feign 是一個宣告式的 Web 服務客戶端。這使得 Web 服務客戶端的寫入更加方便 要使用 Feign 建立一個介面並對其進行註釋。
它具有可插入註釋支援,包括 Feign 註釋和 JAX-RS 註釋。Feign 還支援可插拔編碼器和解碼器。
Spring Cloud 增加了對 Spring MVC 註釋的支援,並使用 Spring Web 中預設使用的 HttpMessageConverters。
Spring Cloud 整合 Ribbon 和 Eureka 以在使用 Feign 時提供負載均衡的 HTTP 客戶端。
Feign 基於 Rest 風格,簡單易懂,他的底層是對 HttpClient 進行了一層封裝,使用十分方便。
Netflix Hystrix(熔斷)
Hystrix 支援回退的概念:當電路斷開或出現錯誤時執行的預設程式碼路徑。要為給定的 @FeignClient 啟用回退,請將 Fallback 屬性設定為實現回退的類名。
我們可以改造一下剛剛的呼叫架構:
在這裡我部署了一臺備用伺服器,當使用者服務當機了之後,訂單服務進行遠端呼叫的時候可以進入備用服務,這樣就不會導致系統崩潰。
MQ(訊息中介軟體)
我現在這裡有一個需求,修改密碼,修改密碼需要傳送簡訊驗證碼,傳送簡訊在簡訊服務中,修改密碼在使用者服務中,這個時候就會出現服務呼叫。
而且我們知道,傳送簡訊一般都是呼叫第三方的介面,那比如阿里的,既然牽扯到呼叫,那麼就會存在很多不確定因素,比如網路問題。
假如,使用者再點選傳送簡訊驗證碼到時候使用者服務呼叫簡訊服務,但是在簡訊服務中執行呼叫阿里的介面花費了很長的時間。
這個時候就會導致使用者服務調簡訊服務超時,會返回給使用者失敗,但是,簡訊最後又發出去了,這種問題怎麼解決呢?
我們可以通過訊息中介軟體來實現,使用非同步講給使用者的反饋和傳送簡訊分離,只要使用者點了傳送簡訊,直接返回成功,然後再啟動傳送驗證碼,60 秒重發一下,就算髮送失敗,使用者還可以選擇重新傳送。
MQ 不但可以解耦服務,它還可以用來削峰,提高系統的效能,是一個不錯的選擇。
分散式事務
既然我們使用了分散式架構,那麼有一點是我們必須要注意的,那就是事務問題。
如果一個服務的修改依賴另外一個服務的操作,這個時候如果操作不慎,就會導致可怕的後果。
舉個例子,兩個服務:錢包服務(用於充值提現)、交易錢包服務(用於交易),我現在想從錢包服務中轉 1000 元到交易錢包服務中,我們應該如何保證他們資料的一致性呢?
我這裡有兩種方案,第一種是通過 MQ 來保證一致性,另外一種就是通過分散式事務來確保一致性。
MQ 確保一致性
- 生成一個訂單表,記錄著轉入轉出的狀態。
- 向 MQ 傳送一條確認訊息。
- 開啟本地事務,執行轉出操操作,並且提交事務。
交易錢包服務:接收 MQ 的訊息,進行轉入操作(此操作需要 Ack 確認機制的支援)。
系統中會一直定時掃描訂單中狀態,沒有成功的就做補償機制或者重試機制,這個不是唯一要求。
以上就是 MQ 確保分散式事務的大致思路,不是唯一,僅供參考。
Seata(分散式事務)
Seata 有三個基本組成部分:
- 事務協調器(TC):維護全域性事務和分支事務的狀態,驅動全域性提交或回滾。
- 事務管理器 TM:定義全域性事務的範圍:開始全域性事務,提交或回滾全域性事務。
- 資源管理器(RM):管理分支事務正在處理的資源,與 TC 進行對話以註冊分支事務並報告分支事務的狀態,並驅動分支事務的提交或回滾。
Seata 管理的分散式事務的典型生命週期:
- TM 要求 TC 開始一項新的全球交易。TC 生成代表全域性事務的 XID。
- XID 通過微服務的呼叫鏈傳播。
- RM 將本地事務註冊為 XID 到 TC 的相應全域性事務的分支。
- TM 要求 TC 提交或回滾 XID 的相應全域性事務。
- TC 驅動 XID 對應的全域性事務下的所有分支事務以完成分支提交或回滾。
完整的分散式架構
完整的分散式架構如下圖:
這就是一套分散式基本的架構,請求從瀏覽器發出,經過 Nginx 反向代理到 Zuul 閘道器。
閘道器經過許可權校驗、然後分別轉發到對應的服務中,每個服務都有自己獨立的資料庫,如果需要跨庫查詢的時候就需要用到分散式的遠端呼叫(Feign)。
雖然這裡我將服務拆分了,但是有一點需要注意的是閘道器,閘道器承載著所有的請求,如果請求過大會發生什麼呢?
服務當機,所以一般情況下,閘道器都是叢集部署,不止閘道器可以叢集,其他的服務都可以做叢集配置,比如:註冊中心、Redis、MQ 等等都可以。
那我們將這個流程圖再改良一下:
現在這套架構就是比較程數的一套了,不管是效能還是穩定能,都是槓槓的,技術選擇性的會也開得差不多了,最後技術總監做了一個總結。
總結
單體服務與分散式服務區別
-
傳統單體架構 分散式服務架構 新功能開發 需要時間 容易開發和實現 部署 不經常且容易部署 經常釋出,部署複雜 隔離性 故障影響範圍大 故障影響範圍小 架構設計 難度小 難度級數增大 系統效能 響應時間快,吞吐量小 響應時間慢,吞吐量大 系統運維 運維簡單 運維複雜 新手上手 學習曲線大(業務邏輯) 學習曲線大(構架邏輯) 技術 技術單一且封閉 技術多樣且開放 測試和查錯 簡單 複雜 系統擴充套件性 擴充套件性差 擴充套件性很好 系統管理 重點在於開放成本 重點在於服務治理和排程
什麼時候使用分散式/叢集?
總結如下幾點:
- 單機無法支援的時候。
- 想要更好的隔離性(功能與功能)。
- 想要更好使用者體驗的時候。
- 想要更好的擴充套件性。
- 想要更快的響應,更搞得吞吐量。
使用分散式注意事項
雖然現在分散式技術已經十分成熟,但是裡面的坑不是一點兩點,比如:==如何保證分散式事務的一致性、如何保證服務呼叫的冪等性、如何保證訊息的冪等性、如何設定熔斷(服務的降級),如何保證服務的健壯性等等,==這些都是一直需要關注的問題,只有解決了這些問題,你的分散式架構才能真正的立於不敗之地。
關於元件停更訊息
目前註冊中心 Eureka、閘道器 Zuul,Feign 都相繼停更了,停更不代表不能使用,只是除了 Bug 可能不會主動修復,所以這個時候我們可能就需要選擇另外的元件了。
註冊中心可以使用 Consul、Nacos,Zookeeper,閘道器則可以通過 Gateway 替換,OpenFeign 替換 Fiegn。
所以也沒必要聽到元件停更的訊息就擔心 Cloud 會不會涼,放心,它至少最近幾年是不會涼的。