分散式架構設計概述
目錄
主流架構模型-SOA 架構和微服務架構
SOA 全稱(Service Oriented Architecture),中文意思為
“面向服務的架構”,他是一種設計方法,其中包含多個服務, 服務之間通過相互依賴最終提供一系列的功能。一個服務 通常以獨立的形式存在與作業系統程序中。各個服務之間 通過網路呼叫跟 SOA 相提並論的還有一個 ESB(企業服務匯流排),簡單來說ESB 就是一根管道,用來連線各個服務節點。為了整合不同系統,不同協議的服務,ESB 做了訊息的轉化解釋和路由工作,讓不同的服務互聯互通
傳統架構
SOA架構
SOA 所解決的核心問題
- 系統整合:站在系統的角度,解決企業系統間的通訊問題,把原先散亂、無規劃的系統間的網狀結構,梳理成規整、可治理的系統間星形結構,這一步往往需要引入一些產品,比如 ESB、以及技術規範、服務管理規範; 這一步解決的核心問題是【有序】
- 系統的服務化:站在功能的角度,把業務邏輯抽象成可複用、可組裝的服務,通過服務的編排實現業務的快速再生,目的:把原先固有的業務功能轉變為通用的業務服務,實現業務邏輯的快速複用;這一步解決的核心問題是【複用】
- 業務的服務化:站在企業的角度,把企業職能抽象成可複用、可組裝的服務;把原先職能化的企業架構轉變為服務化的企業架構,進一步提升企業的對外服務能力;“前面兩步都是從技術層面來解決系統呼叫、系統功能複用的問題”。第三步,則是以業務驅動把一個業務單元封裝成一項服務。這一步解決的核心問題是【高效】
微服務架構
微服務架構其實和SOA 架構類似,微服務是在SOA 上做的昇華,微服務架構強調的一個重點是“業務需要徹底的元件化和服務化”,原有的單個業務系統會拆分為多個可以獨立開發、設計、執行的小應用。這些小應用之間通過服務完成互動和整合。
元件表示一個可以獨立更換和升級的單元,就像 PC 中的
CPU、記憶體、顯示卡、硬碟一樣,獨立且可以更換升級而不 影響其他單元。如果我們把 PC 作為元件以服務的方式構建,那麼這臺 PC 只需要維護主機板和一些必要的外部裝置。CPU、記憶體、硬碟都是以元件方式提供服務,PC 需要呼叫CPU 做計算處理,只需要知道CPU 這個元件的地址即可。
微服務的特徵
- 通過服務實現元件化
- 按業務能力來劃分服務和開發團隊
- 去中心化
- 基礎設施自動化(devops、自動化部署)
SOA 和微服務架構的差別
- 微服務不再強調傳統SOA 架構裡面比較重的ESB 企業服務匯流排,同時SOA 的思想進入到單個業務系統內部實現真正的元件化
- Docker 容器技術的出現,為微服務提供了更便利的條件,比如更小的部署單元,每個服務可以通過類似Node 或者Spring Boot 等技術跑在自己的程序中。
- 還有一個點大家應該可以分析出來,SOA 注重的是系統整合方面,而微服務關注的是完全分離
領域驅動設計及業務驅動劃分
領域驅動設計的概念
領域驅動設計(DDD,Domain-Driven Design),軟體開發不是一蹴而就的事情,我們不可能在不瞭解產品(或行業領域)的前提下進行軟體開發,在開發前,通常需要進行大量的業務知識梳理,然後才到軟體設計的層面,最後才是開發。而在業務知識梳理的過程中,我們必然會形成某個領域知識,根據領域知識來一步步驅動軟體設計,就是領域驅動設計的基本概念
為什麼需要DDD
業務初期,功能大都非常簡單,普通的CRUD 就能滿
足,此時系統是清晰的。隨著產品不斷迭代和演化,業務邏輯變得越來越複雜,我們的系統也越來越冗雜。各個模組之間彼此關聯,甚至到後期連作者都很難說清模組的具體功能意圖是啥。導致在修改一個功能時,要追溯到這個功能需要的修改點就需要很長時間,更別提修改帶來的不可預知的影響面。 比如說:
絕大部分公司都是這樣一個狀態,然後一般的解決方案是不斷的重構系統,讓系統的設計隨著業務成長也進行不斷的演進。通過重構出一些獨立的類來存放某些通用的邏輯解決混亂問題,但是我們很難給它一個業務上的含義,只能以技術緯度進行描述,這個帶來的問題就是其他人接手這塊程式碼的時候不知道這個的含義或者可以通過修改這塊通用邏輯來達到某些需求
領域模型追本溯源
實領域模型本身就不是一個陌生的單詞,說直白點,在早期領域模型就是資料庫設計. 我們做傳統專案的流程或者說包括現在我們做專案的流程,都是首先討論需求,然後是資料庫建模, 在需求逐步確定的過程不斷的去更新資料庫的設計。接著我們在專案開發階段,發現有些關係沒有建、有些欄位少了、有些表結構設計不合理,又在不斷的去調整設計。最後上線。在傳統專案中,資料庫是整個專案的根本,資料模型出來以後後續的開發都是圍繞著資料展開;然後形成如下的一個架構
- service 很重,所有邏輯處理基本都放在 service 層。
- POJO()作為 service 層的非常重要的一個實體,會因為不同場景的需求做不同的變化和組合,就會早成
POJO 的幾種不同模型(貧血、充血),用來形容領域模型太胖或者太瘦
隨著業務變得複雜以後,包括資料結構的變化,那麼各個模組就需要進行修改,原本清晰的系統經過不斷的演化變得複雜、冗餘、耦合度高。後果就非常嚴重
我們試想一下如果一個軟體產品不依賴資料庫儲存設 備,那我們怎麼去設計這個軟體呢?如果沒有了資料儲存,那麼我們的領域模型就得基於程式本身來設計。
以抽獎設計為例
DDD 理解起來有點抽象, 這個有點像設計模式,感覺很有用,但是不知道怎麼應用到自己寫的程式碼裡面,或者生搬硬套最後看起來又很彆扭,那麼接下來以一個簡單的轉盤抽獎案例來分析一下 DDD 的應用
針對功能層面劃分邊界
這個系統可以劃分為運營管理平臺和使用者使用層,運營平臺對於抽獎的配置比較複雜但是操作頻率會比較低。而使用者對抽獎活動頁面的使用是高頻率的但是對於配置規則來說是無感知的,根據這樣的特點,我們把抽獎平臺劃分針對C 端抽獎和M 端抽獎兩個子域
在確認了M 端領域和C 端的限界上下文後,我們再對各自上下文內部進行限界上下文的劃分,接下來以C 端使用者為例來劃分界限上下文
確認基本需求
首先我們要來了解該產品的基本需求
- 抽獎資格(什麼情況下會有抽獎機會、抽獎次數、抽獎的活動起始時間)
- 抽獎的獎品(實物、優惠券、理財金、購物卡…)
- 獎品自身的配置,概率、庫存、某些獎品在有限的概率下還只能被限制抽到多少次等
- 風控對接, 防止惡意薅羊毛
針對產品功能劃分邊界
抽獎上下文是整個領域的核心,負責處理使用者抽獎的核心業務。
- 對於活動的限制,我們定義了活動資格的通用語言,將活動開始/ 結束時間,活動可參與次數等限制條件都收攏到活動資格子域中。
- 由於 C 端存在一些刷單行為,我們根據產品需求定義了風控上下文,用於對活動進行風控
- 由於抽獎和發放獎品其實可以認為是兩個領域,一個負責根據概率去抽獎、另一個負責將選中的獎品發放出去。所以對於這一塊也獨
細化上下文
通過上下文劃分以後,我們還需要進一步梳理上下文之間的關係,梳理的好處在於:
- 任務更好拆分(一個開發人員可以全身心投入到相關子域的上下文中),
- 方便溝通,明確自身上下文和其他上下文之間的依賴關係,可以實現更好的對接
然後是基於上下文的更進一步細化建模,在 DDD 中存在一些名字定義
實體:當一個物件由其標識(而不是屬性)區分時,這種物件稱為實體(Entity)。
值物件:當一個物件用於對事物進行描述而沒有唯一標識時,它被稱作值物件
聚合根:聚合根屬於實體物件,它是領域物件中一個高度內聚的核心物件。(聚合根具有全域性的唯一標識,而實體只有在聚合內部有唯一的本地標識,值物件沒有唯一標識,不存在這個值物件或那個值物件的說法)
領域服務 :一些重要的領域行為或操作,可以歸類為領域服務。它實現了全部業務邏輯並且通過各種校驗手段保證業務的正確性。
資源庫 :資源封裝了基礎設施來提供查詢和持久化聚合操作。這樣能夠讓我們始終關注在模型層面,把物件的儲存和訪問都委託給資源庫來完成。他不是資料庫的封裝,而是領域層與基礎設施之間的橋樑。DDD 關心的是領域內的模型, 而不是資料庫的操作。
程式碼設計
在實際開發中,我們一般會採用模組來表示一個領域的界限上下文,比如
com.gupaoedu.michael.bussiness.lottery.*;//抽獎上下文
com.gupaoedu.michael.bussiness.riskcontrol.*;// 風控上下文
com.gupaoedu.michael.bussiness.prize.*;//獎品上下文
com.gupaoedu.michael.bussiness.qualification.*;// 活 動資格上下文
com.gupaoedu.michael.bussiness.stock.*;//庫存上下文
對於模組內的組織結構,一般情況下我們是按照領域物件、領域服務、領域資源庫、防腐層等組織方式定義的。com.gupaoedu.michael.bussiness.lottery.domain.valobj.
*;//領域物件-值物件com.gupaoedu.michael.bussiness.lottery.domain.entity.*
;//領域物件-實體com.gupaoedu.michael.bussiness.lottery.domain.aggre
gate.*;//領域物件-聚合根
com.gupaoedu.michael.bussiness.lottery.service.*;//領域服務
com.gupaoedu.michael.bussiness.lottery.repo.*;//領域資源庫
領域驅動的好處
用 DDD 可以很好的解決領域模型到設計模型的同步、演進最後對映到實際的程式碼邏輯。
總的來說,DDD 有幾個好處
- DDD 能夠讓我們知道如何抽象出限界上下文上下文以及如何去分而治之
分而治之:把複雜的大規模軟體拆分成若干個子模組,每一個模組都能獨立執行和解決相關問題。並且分割後各個部分可以組裝成為一個整體。
抽象:使用抽象能夠精簡問題空間,而且問題越小越容易理解,比如說我們要對接支付,我們抽象的緯度應該是支付,而不是具體的微信支付還是支付寶支付
2.DDD 的限界上下文可以完美匹配微服務的要求
在系統複雜之後,我們都需要用分治來拆解問題。一般有兩種方式,技術維度和業務維度。技術維度是類似 MVC 這樣,業務維度則是指按業務領域來劃分系統。
微服務架構更強調從業務維度去做分治來應對系統複雜度, 而 DDD 也是同樣的著重業務視角
總結
領域驅動設計其實我們可以簡單認為是一種指導思想,是一種軟體開發方法,通過 DDD 可以將系統解構更加合理, 最終滿足高內聚低耦合的本質。在我的觀點來看,有點類似資料庫的三正規化,我們開始在學的時候並不太理解,當有足夠的設計經驗以後慢慢發現三正規化帶來的好處。同時我們也並不一定需要嚴格按照這三正規化去進行實踐,有些情況下是可以靈活調整。
分散式架構的基本理論 CAP、BASE 以及應用
說CAP、BASE 理論之前,先要了解下分散式一致性的這個問題
實際上,對於不同業務的產品,我們對資料一致性的要求是不一樣的,比如 12306,他要求的是資料的嚴格一致性, 不能說把票賣給使用者以後發現沒有座位了;比如銀行轉賬, 你們通過銀行轉賬的時候,一般會收到一個提示:轉賬申請將會在 24 小時內到賬;實際上這個場景滿足的是最終錢只要匯出去了即可,同時以及如果錢沒匯出去要保證資金不丟失就行;所以說,使用者在使用不同的產品的時候對資料一致性的要求是不一樣的
關於分散式一致性問題
在分散式系統中要解決的一個重要問題就是資料的複製。在我們的日常開發經驗中,相信很多開發人員都遇到過這樣的問題:在做資料庫讀寫分離的場景中,假設客戶端 C1 將系統中的一個值 K 由 V1 更新為 V2,但客戶端 C2 無法立即讀取到 K 的最新值,需要在一段時間之後才能 讀取到。這很正常,因為資料庫複製之間存在延時。
所謂的分散式一致性問題,是指在分散式環境中引入資料複製機制之後,不同資料節點之間 可能出現的,並無法依靠計算機應用程式自身解決的資料不一致的情況。簡單講, 資料一致性就是指在對一個副本資料進行更新的時候,必須確保也能夠更新其他的 副本,否則不同副本之間的資料將不一致。
那麼如何去解決這個問題?按照正常的思路,我們可能會想,既然是因為網路延遲導致的問題,那麼我們可以把同步動作進行阻塞,使用者 2 在查詢的時候必須要等到資料同步完成以後再來做。但是這個方案帶來的問題是效能會收到非常大的影響。如果同步的資料比較多或者比較頻繁,
那麼因為阻塞操作可能將導致整個新系統不可用的情況;
總結: 所以我們沒有辦法找到一種能夠滿足資料一致性、又不影響系統執行的效能的方案,所以這個地方就誕生了一個一致性的級別:
- 強一致性:這種一致性級別是最符合使用者直覺的,它要求系統寫入什麼,讀出來的也會是什麼,使用者體驗好,但實現起來往往對系統的效能影響大
- 弱一致性:這種一致性級別約束了系統在寫入成功後, 不承諾立即可以讀到寫入的值,也不久承諾多久之後資料能夠達到一致,但會盡可能地保證到某個時間級別(比如秒級別)後,資料能夠達到一致狀態
- 最終一致性:最終一致性是弱一致性的一個特例,系統會保證在一定時間內,能夠達到一個數據一致的狀態。這裡之所以將最終一致性單獨提出來,是因為它是弱一致性中非常推崇的一種一致性模型,也是業界在大型分散式系統的資料一致性上比較用的多的模型
CAP 理論
一個經典的分散式系統理論。CAP 理論告訴我們:一個分散式系統不可能同時滿足一致性(C:Consistency)、可用性(A:Availability)和分割槽容錯性(P:Partition tolerance)這三個基本需求,最多隻能同時滿足其中兩項。CAP 理論
在網際網路界有著廣泛的知名度,也被稱為“帽子理論”,它是由 Eric Brewer 教授在 2000 年舉行的 ACM 研討會提出的一個著名猜想:一致性(Consistency)、可用性(Availability)、分割槽容錯(Partition-tolerance)三者無法在分散式系統中同時被滿足,並且最多隻能滿足兩個!
一致性:所有節點上的資料時刻保持同步
可用性:每個請求都能接收一個響應,無論響應成功或失敗
分割槽容錯:系統應該持續提供服務,即時系統內部(某個節點分割槽)有訊息丟失。比如交換機失敗、網址網路被分成幾個子網,形成腦裂;伺服器發生網路延遲或宕機,導致某些 server 與叢集中的其他機器失去聯系
分割槽是導致分散式系統可靠性問題的固有特性,從本質上來看,CAP 理論的準確描述不應該是從 3 個特性中選取兩個,所以我們只能被迫適應,根本沒有選擇權;
總結一下:CAP 並不是一個普適性原理和指導思想,它僅適用於原子讀寫的NoSql 場景中,並不適用於資料庫系統。
BASE 理論
從前面的分析中知道:在分散式(資料庫分片或分庫存在的多個例項上)系統下,CAP 理論並不適合資料庫事務(因為更新一些錯誤的資料而導致的失敗,無論使用什麼樣的高可用方案都是徒勞,因為資料發生了無法修正的錯誤)。此外 XA 事務雖然保證了資料庫在分散式系統下的 ACID(原子性、一致性、隔離性、永續性)特性,但也帶來了一些效能方面的代價,對於併發和響應時間要求比較高的電商平臺來說,是很難接受的。
eBay 嘗試了另外一條完全不同的路,放寬了資料庫事務的
ACID 要求,提出了一套名為 BASE 的新準則。BASE 全稱是 Basically available,soft-state,Eventually Consistent.系統基本可用、軟狀態、資料最終一致性。相對於 CAP 來說,它大大降低了我們對系統的要求。
Basically available(基本可用),在分散式系統出現不可預知的故障時,允許瞬時部分可用性
- 比如我們在淘寶上搜索商品,正常情況下是在 0.5s 內返回查詢結果,但是由於後端的系統故障導致查詢響應時間變成了 2s
- 再比如資料庫採用分片模式,100W 個使用者資料分在 5 個數據庫例項上,如果破壞了一個例項,那麼可用性還有 80%,也就是 80%的使用者都可以登入,系統仍然可用
- 電商大促時,為了應對訪問量激增,部分使用者可能會被引導到降級頁面,服務層也可能只提供降級服務。這就是損失部分可用性的體現
soft-state(軟狀態). 表示系統中的資料存在中間狀態,並且這個中間狀態的存在不會影響系統的整體可用性,也就是表示系統允許在不同節點的資料副本之間進行資料同步過程中存在延時;比如訂單狀態,有一個待支付、支付中、支付成功、支付失敗, 那麼支付中就是一箇中間狀態,這個中間狀態在支付成功以後,在支付表中的狀態同步給訂單狀態之前,中間會存在一個時間內的不一致。Eventually consistent(資料的最終一致性),表示的是所有資料副本在一段時間的同步後最終都能達到一個一致的狀態,因此最終一致性的本質是要保證資料最終達到一致, 而不需要實時保證系統資料的強一致
BASE 理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,採用適當的方式來使系統達到最終一致性
什麼是分散式架構下的高可用設計
避免單點故障
- 負載均衡技術(failover/選址/硬體負載/軟體負載/ 去中心化的軟體負載( gossip(redis- cluster)))
- 熱備(linux HA)
- 多機房(同城災備、異地災備)
應用的高可用性
- 故障監控(系統監控(cpu、記憶體)/鏈路監控/日誌監控) 自動預警
- 應用的容錯設計、(服務降級、限流)自我保護能力
- 資料量(資料分片、讀寫分離)
分散式架構下的可伸縮設計
垂直伸縮 提升硬體能力水平伸縮 增加伺服器
加速靜態內容訪問速度的 CDN
CDN 是Content Delivery Network 的縮寫,表示的是內容分發網路。CDN 的作用是把使用者需要的內容分發到離使用者最近的地方,這樣可以是使用者能夠快熟獲取所需要的內容。
CDN 其實就是一種網路快取技術,能夠把一些相對穩定的資源放到距離終端使用者較近的地方,一方面可以節省整個廣域網的貸款消耗,另外一方面可以提升使用者的訪問速度, 改進使用者體驗。我們一般會把靜態的檔案(圖片、指令碼、靜態頁面)放到CDN 中
- 當用戶點選網站頁面上的內容 URL,經過本地 DNS 系統解析,DNS 系統會最終將域名的解析權交給 CNAME 指向的 CDN 專用 DNS 伺服器
- CDN 的DNS 伺服器將 CDN 的全域性負載均衡裝置 IP 地址返回使用者
- 使用者向CDN 的全域性負載均衡裝置發起內容 URL 訪問請求
- CDN 全域性負載均衡裝置根據使用者IP 地址,以及使用者請求的內容URL, 選擇一臺使用者所屬區域的區域負載均衡裝置,告訴使用者向這臺裝置發起請求。
- 區域負載均衡裝置會為使用者選擇一臺合適的快取伺服器提供服務, 選擇的依據包括:根據使用者 IP 地址,判斷哪一臺伺服器距使用者最近; 根據使用者所請求的 URL 中攜帶的內容名稱,判斷哪一臺伺服器上有使用者所需內容;查詢各個伺服器當前的負載情況,判斷哪一臺伺服器尚有服務能力。基於以上這些條件的綜合分析之後,區域負載均衡裝置會向全域性負載均衡裝置返回一臺快取伺服器的IP 地址
- 局負載均衡裝置把伺服器的IP 地址返回給使用者
使用者向快取伺服器發起請求,快取伺服器響應使用者請求,將使用者所需內容傳送到使用者終端。如果這臺快取伺服器上並沒有使用者想要的內容,而區域均衡裝置依然將它分配給了使用者,那麼這臺伺服器就要向它的上一級快取伺服器請求內容,直至追溯到網站的源伺服器將內容拉到本地。
什麼情況下用 CDN
最適合的是那些不會經常變化的內容,比如圖片,JS 檔案,CSS 檔案,圖片檔案包括程式模板中的,CSS 檔案中用到的背景圖片,還有就是作為網站內容組成部分的那些圖片都可以;
灰度釋出
我們的應用雖然經過了測試部門的測試,但是仍然很難全面覆蓋使用者的使用場景,為了保證萬無一失,我們在進行釋出的時候一般會採用灰度釋出,也就是會對新應用進行分批發布,逐步擴大新應用在整個及群眾的比例直到最後全部完成。灰度釋出是針對新引用在使用者體驗方面完全無感知。
灰度釋出系統的作用在於,可以根據自己的配置,來將使用者的流量導到新上線的系統上,來快速驗證新的功能修改, 而一旦出問題,也可以馬上的恢復,簡單的說,就是一套A/BTest 系