分布式架構原理與實現---第一篇
(一)架構特征:
分布式架構將資源、服務、任務、計算分布到不同的容器、服務器、網絡節點中,它們需要協同完成一個或者多個任務。其特征如下:
● 分布性:將分布兩字分開來看,“分”指的是拆分,可以理解為服務的拆分、存儲數據的拆分、硬件資源的拆分。有時候我們雖然在軟件架構上進行了服務的水平擴展,但是這些分布式的服務卻在一個宿主機上,并沒有分散到多個機器節點,沒有做到嚴格意義上的硬件資源的拆分。布”指的是部署,也指資源的部署。既有計算資源,也有存儲資源的部署。
● 自治性:簡單來說,自治性就是每個應用服務都有管理和支配自身任務和資源的能力。從分布性的特征來看,資源分散了,以前一份資源做的事情,現在由多份資源同時完成,提高了系統的性能和可用性。
● 并行性:自治性導致每個應用服務都是一個獨立的個體,擁有獨立的技術和業務,占用獨立的物理資源。這種獨立能夠減小服務之間的耦合度,增強架構的可伸縮性,為并行性打下基礎。將應用服務進行擴展后,他們完成的功能相同,處理的業務相同,占用的資源也相同,它們并行處理大量請求,相當于將一個大任務拆解成了若干個小任務,分配到不同的服務器上完成,因此并行性也會被稱為并發性。其目的還是提高性能和可用性。
● 全局性。分布性使得服務和資源都是分開部署的,自治性說明單個服務擁有單獨的業務和資源,多個服務通過并行的方式完成大型任務。多個分布在不同網絡節點的服務應用在共同完成一個任務時,需要有全局性的考慮。例如,商品服務在調用支付服務時,需要通過服務注冊中心感知支付服務的存在;多個庫存服務對商品庫存進行扣減時,需要考慮臨界資源的問題;訂單服務在調用支付服務和庫存服務的時候需要考慮分布式事務問題;當主數據庫服務器掛掉的時候,需要及時切換到從數據庫服務器,這些都是全局性的問題。說白了,就是分散的資源要想共同完成一件大事,需要溝通和協作,也就是擁有大局觀。
待解決問題:
任何一個系統都是為業務服務的,所以首先根據業務特點對應用服務進行拆分,拆分之后會形成一個個服務或者應用。這些服務具有自治性,可以完成自己對應的業務功能,以及擁有單獨的資源。當一個服務需要調用其他服務時,需要考慮服務之間通信的問題。同理,多個服務要完成同一件事時,需要考慮協同問題。當遇到大量任務需要進行大量計算工作的時候,需要多個同樣的服務共同完成。任何應用或者計算架構都需要考慮存儲的問題。實現了對應用與資源的管理和調度,才能實現系統的高性能和可用性。此外,加入指標與監控能夠保證系統正常運行。分布式架構需要解決的問題按照順序列舉為如下幾步:
(1) 分布式是用分散的服務和資源代替幾種服務和資源,所以先根據業務進行應用服務拆分。
(2) 由于服務分布在不同的服務器和網絡節點上,所以要解決分布式調用的問題。
(3) 服務能夠互相感知和調用以后,需要共同完成一些任務,這些任務或者共同進行,或者依次進行,因此需要解決分布式協同問題。
(4) 在協同工作時,會遇到大規模計算的情況,需要考慮使用多種分布式計算的算法來應對。
(5) 任何服務的成果都需要保存下來,這就要考慮存儲問題。和服務一樣,存儲的分布式也可以提高存儲的性能和可用性,因此需要考慮分布式存儲的問題。
(6) 所有的服務與存儲都可以看作資源,因此需要考慮分布式資源管理和調度。
(7) 設計分布式架構的目的是實現高性能和可用性。為了達到這個目的,一起來看看高性能與可用性的最佳實踐,例如緩存的應用、請求限流、服務降級等。
(8) 最后,系統上線以后需要對性能指標進行有效的監控才能保證系統穩定運行,此時指標與監控就是我們需要關注的問題。

(二)服務拆分方法
通過領域驅動設計的定義可以知道,應用服務的拆分源頭是需求。企業開發某款軟件時,會有一個目的,針對目的會生成需要解決的問題,這些問題就是業務需求。領域驅動設計就是將業務需求轉化為架構設計,最后落地到代碼。明確了目的,后面的參與者需要對業務比較精通,對系統需要解決的問題和需求有著透徹理解的領域專家,以及在類、接口、方法、設計模式、架構等很熟悉,能夠用面向對象的思想來思考問題的技術團隊一起參與。
之后雙方從企業目標、需解決的問題、業務需求出發,通過通用的語言進行溝通與分析,得到領域知識。這些領域知識是對業務的一般性描述,例如用戶通過瀏覽網站,選擇商品下單,付款以后收到確認付款和準備發貨的通知,其中參與者(實體)是用戶,業務流程是瀏覽、下單、付款、收到付款通知,命令有下單、付款,事件有已經下單、已經付款、已經發送通知。得到這些領域知識后,通過抽取的方式形成領域模型。
領域模型是一個抽象的概念,其具體形態是一個大的領域,其中包裹著的各種不同的子領域也稱為子域。這些子域通過限界上下文的方式進行分割,子域中間又包含領域對象,例如聚合、聚合根、實體、值對象;領域對象之間通過領域事件進行溝通。在抽取完領域模型之后,技術團隊會根據這個模型搭建軟件架構,并對架構分層,分別是用戶接口層、應用層、領域層和基礎層,再將每層用代碼實現。將上面這些思想整合到一張圖如下:

從上圖中可以看出,領域驅動設計對應用服務進行拆分的流程大致分為:分析、抽取、構建。
領域、子域和限界上下文:
領域從字面上理解就是從事某種專項活動或事情的范圍。這個“范圍”可以是業務的范圍、系統的范圍、服務的范圍,甚至是物理資源的范圍。只有確定好這些范圍,我們才能更好地對分布式架構進行拆分,做到“高內聚,低耦合”。回到業務分析上來,領域指的是業務邊界。我們要做的事情就是對業務沿著業務邊界進行劃分,形成一個個領域模型,再用架構和代碼實現這些領域模型,也就是解決從業務到技術的問題。
按照業務邊界將業務領域分割以后,形成了一個個子域,這些子域對內都有通用語言作為支撐。如果說領域與子域的概念是從業務角度出發告訴我們如何對業務定義邊界,那么該如何劃分這個邊界,又如何將業務邊界定義到技術上呢?答案是限界上下文。
領域中包含的子域就是用限界上下文的方法劃分出來的。限界上下文就像一把刀,將業務領域分割成不同的子域。這里需要注意,領域是給領域專家和技術團隊看的,因此一定要包含業務和技術兩個層面的東西。如果對領域從橫切面切一刀,就可以將其分為問題空間和解決方案空間。
● 問題空間是領域在業務層面的表現,從業務的角度會看到分割所得的子域,包括核心域、支撐域、通用域。
● 解決方案空間是領域在技術層面的表現,這里領域被限界上下文分割。

限界上下文是分布式架構和微服務架構拆分的依據,是業務從問題空間轉換到解決方案空間的工具。
(三) 領域驅動設計分層
領域驅動設計分層能夠幫助我們把領域對象轉化為軟件架構。在分解復雜的軟件系統時,分層是最常用的一種手段。在領域驅動設計的思想中,分層代表軟件框架,是整個分布式架構的“骨架”;領域對象是業務在軟件中的映射,好比“血肉”。如何將血肉填充到骨架中去,就是領域驅動設計中的分層架構的意義。
分層的原則:
● 高內聚:定義每層需要關注的重點,使復雜問題簡單化,讓整個架構清晰化。例如商店只負責賣好商品就行了,不需要考慮商品都是如何制作的。同樣,基礎設施層做好提供日志、通知服務的工作就好了,不用關注具體的業務流程是怎樣的。
● 低耦合:各層分工明確,層與層之間通過標準接口進行通信。一個層次不需要關心其他層次的具體實現過程,即便其他層次的內部結構或者流程發生改變了,只要接口不變化就不會影響自己的工作。
● 可擴展:由于每層都各司其職,層與層之間的溝通都通過接口完成,因此無論在哪層擴展功能,都是很方便的,只需要對其他層的功能進行組合即可。例如商店之前只賣面包,現在想擴展業務——賣蛋糕,于是就去聯系蛋糕廠。商店只需要知道如何經營和組合好這些商品就可以了。
● 可復用:每層都可以向一層或者多層提供服務,特別是基礎、通用功能會被多處使用。這樣的復用提高了應用服務的使用率,避免了重復造輪子的現象。
領域驅動設計將架構分成四層,從上往下分別是用戶接口層、應用層、領域層和基礎層。箭頭表示層和層之間的依賴與被依賴關系。例如,箭頭從用戶接口層指向應用層,表示用戶接口層依賴于應用層。從圖中可以看到,基礎層被其他所有層依賴,位于最核心的位置。

但這種分法和業務領導技術的理念是相沖突的,搭建分布式架構時是先理解業務,然后對業務進行拆解,最后將業務映射到軟件架構。這么看來,領域層才是架構的核心,上圖的依賴關系是有問題的。于是出現了 DIP(Dependency Inversion Principle,依賴倒置原則),DIP 的思想指出:高層模塊不應該依賴于底層模塊,這兩者都應該依賴于抽象;抽象不應該依賴于細節,細節應該依賴于抽象。【簡單來說就是高層不直接實現業務細節,而是調用抽象接口完成業務目標,至于抽象接口如何實現,則是由底層去實現這個接口】因此,作為底層的基礎層應該依賴于用戶接口層、應用層和領域層提供的接口。高層是根據業務展開的,通過對業務抽象產生了接口,底層依賴這些接口為高層提供服務。

分層代碼結構圖:

最右邊的其他服務通過基礎層中的 API 網關,將信息傳入用戶接口層。傳入的信息先通過 Assembler 轉換成 DTO 對象,再傳給 Facade。Facade 負責把信息傳遞給應用層,信息以命令的形式被傳遞給 Application Service。Application Service 會組合領域層中的 Aggregate 和 Service。領域層中的Entity 和值對象,配合 Aggregate 和 Service 完成業務邏輯,并且通過Repository 將 Entity 和值對象存儲到數據庫中。領域層中的 Event 會根據業務的發生,獲取事件信息,通過應用層中 Event 里的訂閱和發布,與其他服務進行通信。

浙公網安備 33010602011771號