分佈式專題-漫談分佈式架構02-分佈式架構設計

前言

本節我們來談一談分佈式架構的設計思想

主流架構模型-SOA

架構和微服務架構

SOA 全稱(Service Oriented Architecture),中文意思爲

「面向服務的架構」,他是一種設計方法,其中包含多個服務,服務之間通過相互依賴最終提供一系列的功能。一個服務通常以獨立的形式存在與操作系統進程中。各個服務之間通過網絡調用

跟 SOA 相提並論的還有一個 ESB(企業服務總線),簡單來說 ESB 就是一根管道,用來連接各個服務節點。爲了集成不同系統,不同協議的服務,ESB 做了消息的轉化解釋和路由工作,讓不同的服務互聯互通;
在這裏插入圖片描述
在這裏插入圖片描述

SOA 所解決的核心問題

1.系統集成:站在系統的角度,解決企業系統間的通信問題,把原先散亂、無規劃的系統間的網狀結構,梳理成規整、可治理的系統間星形結構,這一步往往需要引入一些產品,比如 ESB、以及技術規範、服務管理規範;這一步解決的核心問題是【有序】

2.系統的服務化:站在功能的角度,把業務邏輯抽象成可複用、可組裝的服務,通過服務的編排實現業務的快速再生,目的:把原先固有的業務功能轉變爲通用的業務服務,實現業務邏輯的快速複用;這一步解決的核心問題是【複用】

3.業務的服務化:站在企業的角度,把企業職能抽象成可複用、可組裝的服務;把原先職能化的企業架構轉變爲服務化的企業架構,進一步提升企業的對外服務能力;「前面兩步都是從技術層面來解決系統調用、系統功能複用的問題」。第三步,則是以業務驅動把一個業務單元封裝成一項服務。這一步解決的核心問題是【高效】

微服務架構

微服務架構其實和 SOA 架構類似,微服務是在 SOA 上做的昇華,微服務架構強調的一個重點是「業務需要徹底的組件化和服務化」,原有的單個業務系統會拆分爲多個可以獨立開發、設計、運行的小應用。這些小應用之間通過服務完成交互和集成。

組件表示一個可以獨立更換和升級的單元,就像 PC 中的CPU、內存、顯卡、硬盤一樣,獨立且可以更換升級而不影響其他單元。如果我們把 PC 作爲組件以服務的方式構建,那麼這臺 PC 只需要維護主板和一些必要的外部設備。CPU、內存、硬盤都是以組件方式提供服務,PC 需要調用 CPU 做計算處理,只需要知道 CPU 這個組件的地址即可。

微服務的特徵

1.通過服務實現組件化

2.按業務能力來劃分服務和開發團隊

3.去中心化

4.基礎設施自動化(devops、自動化部署)

SOA 和微服務架構的差別

1.微服務不再強調傳統 SOA 架構裏面比較重的 ESB 企業服務總線,同時 SOA 的思想進入到單個業務系統內部實現真正的組件化

2.Docker 容器技術的出現,爲微服務提供了更便利的條件,比如更小的部署單元,每個服務可以通過類似 Node 或者 Spring Boot 等技術跑在自己的進程中。

3.還有一個點大家應該可以分析出來,SOA 注重的是系統集成方面,而微服務關注的是完全分離

領域驅動設計及業務驅動劃分

我拿來項目實戰專題的系統架構圖先講一下:
在這裏插入圖片描述
我們傳統的代碼模型就是基於Controller、Service、Dao層來實現解耦,但是通常都是Controller作爲空實現,數據透傳後,所有的業務邏輯都在Service裏去寫,這樣Service層就會顯得很重。所以,本小節提出領域概念。實現高內聚,低耦合的效果!

領域驅動設計的概念

領域驅動設計(DDD,Domain-Driven Design),軟件開發不是一蹴而就的事情,我們不可能在不瞭解產品(或行業領域)的前提下進行軟件開發,在開發前,通常需要進行大量的業務知識梳理,然後纔到軟件設計的層面,最後纔是開發。而在業務知識梳理的過程中,我們必然會形成某個領域知識,根據領域知識來一步步驅動軟件設計,就是領域驅動設計的基本概念

爲什麼需要 DDD

業務初期,功能大都非常簡單,普通的 CRUD 就能滿足,此時系統是清晰的。隨着產品不斷迭代和演化,業務邏輯變得越來越複雜,我們的系統也越來越冗雜。各個模塊之間彼此關聯,甚至到後期連作者都很難說清模塊的具體功能意圖是啥。導致在修改一個功能時,要追溯到這個功能需要的修改點就需要很長時間,更別提修改帶來的不可預知的影響面。 比如說:

在這裏插入圖片描述
訂單服務接口中提供了查詢、創建訂單相關的接口,也提供了訂單評價、支付的接口。同時訂單表是個大表,包含了非常多字段。在我們維護代碼時,將會導致牽一髮而動全身,很可能只是想改下評價相關的功能,卻影響到了創建訂單的核心路徑。雖然我們可以通過測試來保證功能完備性,但當我們在訂單領域有大量需求同時並行開發時,改動重疊、惡性循環、疲於奔命修改各種問題。

絕大部分公司都是這樣一個狀態,然後一般的解決方案是不斷的重構系統,讓系統的設計隨着業務成長也進行不斷的演進。通過重構出一些獨立的類來存放某些通用的邏輯解決混亂問題,但是我們很難給它一個業務上的含義,只能以技術緯度進行描述,這個帶來的問題就是其他人接手這塊代碼的時候不知道這個的含義或者可以通過修改這塊通用邏輯來達到某些需求

領域模型追本溯源

實領域模型本身就不是一個陌生的單詞,說直白點,在早期領域模型就是數據庫設計. 我們做傳統項目的流程或者說包括現在我們做項目的流程,都是首先討論需求,然後是數據庫建模, 在需求逐步確定的過程不斷的去更新數據庫的設計。接着我們在項目開發階段,發現有些關係沒有建、有些字段少了、有些表結構設計不合理,又在不斷的去調整設計。最後上線。在傳統項目中,數據庫是整個項目的根本,數據模型出來以後後續的開發都是圍繞着數據展開;然後形成如下的一個架構
在這裏插入圖片描述
1.service 很重,所有邏輯處理基本都放在 service 層。

2.POJO()作爲 service 層的非常重要的一個實體,會因爲不同場景的需求做不同的變化和組合,就會早成 POJO 的幾種不同模型(失血、貧血、充血),用來形容領域模型太胖或者太瘦

隨着業務變得複雜以後,包括數據結構的變化,那麼各個模塊就需要進行修改,原本清晰的系統經過不斷的演化變得複雜、冗餘、耦合度高。後果就非常嚴重

我們試想一下如果一個軟件產品不依賴數據庫存儲設備,那我們怎麼去設計這個軟件呢?如果沒有了數據存儲,那麼我們的領域模型就得基於程序本身來設計。那這個就是 DDD 需要去考慮的問題

以抽獎設計爲例
抽獎活動代碼見本文後記

BD理解起來有點抽象, 這個有點像設計模式,感覺很有用,但是不知道怎麼應用到自己寫的代碼裏面,或者生搬硬套最後看起來又很彆扭,那麼接下來以一個簡單的轉盤抽獎案例來分析一下 DDD 的應用

針對功能層面劃分邊界

這個系統可以劃分爲運營管理平臺和用戶使用層,運營平臺對於抽獎的配置比較複雜但是操作頻率會比較低。而用戶對抽獎活動頁面的使用是高頻率的但是對於配置規則來說是誤感知的,根據這樣的特點,我們把抽獎平臺劃分針對 C 端抽獎和 M 端抽獎兩個子域

在確認了 M 端領域和 C 端的限界上下文後,我們再對各自上下文內部進行限界上下文的劃分,接下來以 C 端用戶爲例來劃分界限上下文

確認基本需求

首先我們要來了解該產品的基本需求

1.抽獎資格(什麼情況下會有抽獎機會、抽獎次數、抽獎的活動起始時間)

2.抽獎的獎品(實物、優惠券、理財金、購物卡…)

3.獎品自身的配置,概率、庫存、某些獎品在有限的概率下還只能被限制抽到多少次等

4.風控對接, 防止惡意薅羊毛

針對產品功能劃分邊界
在這裏插入圖片描述
抽獎上下文是整個領域的核心,負責處理用戶抽獎的核心業務。

1.對於活動的限制,我們定義了活動資格的通用語言,將活動開始/

結束時間,活動可參與次數等限制條件都收攏到活動資格子域中。

2.由於 C 端存在一些刷單行爲,我們根據產品需求定義了風控上下文,用於對活動進行風控

3.由於抽獎和發放獎品其實可以認爲是兩個領域,一個負責根據概率去抽獎、另一個負責將選中的獎品發放出去。所以對於這一塊也獨立出來一個領域

細化上下文
通過上下文劃分以後,我們還需要進一步梳理上下文之間的關係,梳理的好處在於:

1.任務更好拆分(一個開發人員可以全身心投入到相關子

域的上下文中),

2.方便溝通,明確自身上下文和其他上下文之間的依賴關

系,可以實現更好的對接

然後是基於上下文的更進一步細化建模,在 DDD 中存在一

些名字定義

實體

當一個對象由其標識(而不是屬性)區分時,這種對象稱

爲實體(Entity)。

值對象

當一個對象用於對事物進行描述而沒有唯一標識時,它被

稱作值對象

聚合根

聚合根屬於實體對象,它是領域對象中一個高度內聚的核心對象。(聚合根具有全局的唯一標識,而實體只有在聚合內部有唯一的本地標識,值對象沒有唯一標識,不存在這個值對象或那個值對象的說法)

領域服務

一些重要的領域行爲或操作,可以歸類爲領域服務。它實現了全部業務邏輯並且通過各種校驗手段保證業務的正確性。

資源庫

資源封裝了基礎設施來提供查詢和持久化聚合操作。這樣能夠讓我們始終關注在模型層面,把對象的存儲和訪問都委託給資源庫來完成。他不是數據庫的封裝,而是領域層與基礎設施之間的橋樑。DDD 關心的是領域內的模型,而不是數據庫的操作。

代碼設計

詳見文末

在實際開發中,我們一般會採用模塊來表示一個領域的界限上下文,比如

com.test.michael.bussiness.lottery.;//抽獎上下文
com.test.michael.bussiness.riskcontrol.;// 風控上下文
com.test.michael.bussiness.prize.
;//獎品上下文
com.test.michael.bussiness.qualification.;// 活動資格上下文
com.test.michael.bussiness.stock.
;//庫存上下文*

對於模塊內的組織結構,一般情況下我們是按照領域對象、

領域服務、領域資源庫、防腐層等組織方式定義的。

com.test.michael.bussiness.lottery.domain.valobj.;//領域對象-值對象
com.test.michael.bussiness.lottery.domain.entity.;//領域對象-實體
com.test.michael.bussiness.lottery.domain.aggregate.
;//領域對象-聚合根
com.test.michael.bussiness.lottery.service.;//領域服務
com.test.michael.bussiness.lottery.repo.
;//領域資源庫*

領域驅動的好處

用 DDD 可以很好的解決領域模型到設計模型的同步、演進最後映射到實際的代碼邏輯。總的來說,DDD 有幾個好處

1.DDD 能夠讓我們知道如何抽象出限界上下文上下文以及如何去分而治之

分而治之:把複雜的大規模軟件拆分成若干個子模塊,每一個模塊都能獨立運行和解決相關問題。並且分割後各個部分可以組裝成爲一個整體。
抽象:使用抽象能夠精簡問題空間,而且問題越小越容易理解,比如說我們要對接支付,我們抽象的緯度應該是支付,而不是具體的微信支付還是支付寶支付

2.DDD 的限界上下文可以完美匹配微服務的要求

在系統複雜之後,我們都需要用分治來拆解問題。一般有兩種方式,技術維度和業務維度。技術維度是類似 MVC 這樣,業務維度則是指按業務領域來劃分系統。

微服務架構更強調從業務維度去做分治來應對系統複雜度,而 DDD 也是同樣的着重業務視角

總結

領域驅動設計其實我們可以簡單認爲是一種指導思想,是一種軟件開發方法,通過 DDD 可以將系統解構更加合理,最終滿足高內聚低耦合的本質。在我的觀點來看,有點類似數據庫的三範式,我們開始在學的時候並不太理解,當有足夠的設計經驗以後慢慢發現三範式帶來的好處。同時我們也並不一定需要嚴格按照這三範式去進行實踐,有些情況下是可以靈活調整。

分佈式架構的基本理論 CAP、BASE 以及應用

說 CAP、BASE 理論之前,先要了解下分佈式一致性的這個問題

實際上,對於不同業務的產品,我們對數據一致性的要求是不一樣的,比如 12306,他要求的是數據的嚴格一致性,不能說把票賣給用戶以後發現沒有座位了;比如銀行轉賬,你們通過銀行轉賬的時候,一般會收到一個提示:轉賬申請將會在 24 小時內到賬;實際上這個場景滿足的是最終錢只要匯出去了即可,同時以及如果錢沒匯出去要保證資金不丟失就行;所以說,用戶在使用不同的產品的時候對數據一致性的要求是不一樣的

關於分佈式一致性問題

在分佈式系統中要解決的一個重要問題就是數據的複製。在我們的日常開發經驗中,相信很多開發人員都遇到過這樣的問題:在做數據庫讀寫分離的場景中,假設客戶端 C1 將系統中的一個值 K 由 V1 更新爲 V2,但客戶端 C2 無法立即讀取到 K 的最新值,需要在一段時間之後才能 讀取到。這很正常,因爲數據庫複製之間存在延時。
在這裏插入圖片描述
所謂的分佈式一致性問題,是指在分佈式環境中引入數據複製機制之後,不同數據節點之間 可能出現的,並無法依靠計算機應用程序自身解決的數據不一致的情況。簡單講,數據一致性就是指在對一個副本數據進行更新的時候,必須確保也能夠更新其他的 副本,否則不同副本之間的數據將不一致。

那麼如何去解決這個問題?按照正常的思路,我們可能會想,既然是因爲網絡延遲導致的問題,那麼我們可以把同步動作進行阻塞,用戶 2 在查詢的時候必須要等到數據同步完成以後再來做。但是這個方案帶來的問題是性能會收到非常大的影響。如果同步的數據比較多或者比較頻繁,
那麼因爲阻塞操作可能將導致整個新系統不可用的情況;總結: 所以我們沒有辦法找到一種能夠滿足數據一致性、又不影響系統運行的性能的方案,所以這個地方就誕生了一個一致性的級別:

1.強一致性:這種一致性級別是最符合用戶直覺的,它要求系統寫入什麼,讀出來的也會是什麼,用戶體驗好,但實現起來往往對系統的性能影響大

2.弱一致性:這種一致性級別約束了系統在寫入成功後,不承諾立即可以讀到寫入的值,也不久承諾多久之後數據能夠達到一致,但會盡可能地保證到某個時間級別(比如秒級別)後,數據能夠達到一致狀態

3.最終一致性:最終一致性是弱一致性的一個特例,系統會保證在一定時間內,能夠達到一個數據一致的狀態。這裏之所以將最終一致性單獨提出來,是因爲它是弱一致性中非常推崇的一種一致性模型,也是業界在大型分佈式系統的數據一致性上比較用的多的模型

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(基本可用),在分佈式系統出現不可預知的故障時,允許瞬時部分可用性

1.比如我們在淘寶上搜索商品,正常情況下是在 0.5s 內返回查詢結果,但是由於後端的系統故障導致查詢響應時間變成了 2s

2.再比如數據庫採用分片模式,100W 個用戶數據分在 5 個數據庫實例上,如果破壞了一個實例,那麼可用性還有 80%,也就是 80%的用戶都可以登錄,系統仍然可用
3.電商大促時,爲了應對訪問量激增,部分用戶可能會被引導到降級頁面,服務層也可能只提供降級服務。這就是損失部分可用性的體現

soft-state(軟狀態). 表示系統中的數據存在中間狀態,並且這個中間狀態的存在不會影響系統的整體可用性,也就是表示系統允許在不同節點的數據副本之間進行數據同步過程中存在延時;比如訂單狀態,有一個待支付、支付中、支付成功、支付失敗, 那麼支付中就是一箇中間狀態,這個中間狀態在支付成功以後,在支付表中的狀態同步給訂單狀態之前,中間會存在一個時間內的不一致。

Eventually consistent(數據的最終一致性),表示的是所有數據副本在一段時間的同步後最終都能達到一個一直的狀態,因此最終一致性的本質是要保證數據最終達到一直,而不需要實時保證系統數據的強一致

BASE 理論的核心思想是:即使無法做到強一致性,但每個應用都可以根據自身業務特點,採用適當的方式來使系統達到最終一致性

分佈式架構下的高可用設計

避免單點故障

a)負載均衡技術(failover/選址/硬件負載/

軟件負載 / 去中心化的軟件負載( gossip(redis-cluster)))

b)熱備(linux HA)

c)多機房(同城災備、異地災備)

應用的高可用性

a)故障監控(系統監控(cpu、內存)/鏈路監控/日誌監控) 自動預警

b)應用的容錯設計、(服務降級、限流)自我保護能力

c)數據量(數據分片、讀寫分離)

分佈式架構下的可伸縮設計

垂直伸縮 提升硬件能力

水平伸縮 增加服務器

加速靜態內容訪問速度的 CDN

CDN 是 Content Delivery Network 的縮寫,表示的是內容分發網絡。CDN 的作用是把用戶需要的內容分發到離用戶最近的地方,這樣可以是用戶能夠快熟獲取所需要的內容。 CDN 其實就是一種網絡緩存技術,能夠把一些相對穩定的資源放到距離最終用戶較近的地方,一方面可以節省整個

廣域網的貸款消耗,另外一方面可以提升用戶的訪問速度,改進用戶體驗。我們一般會把靜態的文件(圖片、腳本、靜態頁面)放到 CDN 中

在這裏插入圖片描述

1.當用戶點擊網站頁面上的內容 URL,經過本地 DNS 系統解析,DNS系統會最終將域名的解析權交給 CNAME 指向的 CDN 專用 DNS 服務器

2.CDN 的 DNS 服務器將 CDN 的全局負載均衡設備 IP 地址返回用戶

3.用戶向 CDN 的全局負載均衡設備發起內容 URL 訪問請求

4.CDN 全局負載均衡設備根據用戶 IP 地址,以及用戶請求的內容 URL,選擇一臺用戶所屬區域的區域負載均衡設備,告訴用戶向這臺設備發起請求。

5.區域負載均衡設備會爲用戶選擇一臺合適的緩存服務器提供服務,選擇的依據包括:根據用戶 IP 地址,判斷哪一臺服務器距用戶最近;根據用戶所請求的 URL 中攜帶的內容名稱,判斷哪一臺服務器上有用戶所需內容;查詢各個服務器當前的負載情況,判斷哪一臺服務器尚有服務能力。基於以上這些條件的綜合分析之後,區域負載均衡設備會向全局負載均衡設備返回一臺緩存服務器的 IP 地址

6.局負載均衡設備把服務器的 IP 地址返回給用戶

用戶向緩存服務器發起請求,緩存服務器響應用戶請求,將用戶所需內容傳送到用戶終端。如果這臺緩存服務器上並沒有用戶想要的內容,而區域均衡設備依然將它分配給了用戶,那麼這臺服務器就要向它的上一級緩存服務器請求內容,直至追溯到網站的源服務器將內容拉到本地。

什麼情況下用 CDN

最適合的是那些不會經常變化的內容,比如圖片,JS 文件, CSS 文件,圖片文件包括程序模板中的,CSS 文件中用到的背景圖片,還有就是作爲網站內容組成部分的那些圖片,都可以;

灰度發佈

發佈的時候一般會採用灰度發佈,也就是會對新應用進行分批發布,逐步擴大新應用在整個及羣衆的比例直到最後全部完成。灰度發佈是針對新引用在用戶體驗方面完全無感知。

灰度發佈系統的作用在於,可以根據自己的配置,來將用戶的流量導到新上線的系統上,來快速驗證新的功能修改,而一旦出問題,也可以馬上的恢復,簡單的說,就是一套 A/BTest 系統.
在這裏插入圖片描述

後記

本節抽獎活動項目代碼:
抽獎活動 github項目地址