基於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 會不會涼,放心,它至少最近幾年是不會涼的。