架構設計思維

1

應用架構的演變

大家認真想一下,架構為什麼進行演化呢?這些年,微服務非常火,那你有沒想過微服務的動機是什麼?其實,最重要的動機就是業務變化太快了。特別是移動互聯網出現以後,各種各樣的業務:共享單車、支付寶、微信支付等等,業務經歷著飛速的變革與創新,所以就要求底層的應用技術能夠支撐得上業務的快速變化。那我們看一下應用架構的變遷,其實也是從另一個角度來印證上面說的“快”。

架構設計思維

1-1

單體架構

Web應用程序發展的早期,在開發服務端企業應用時,應用需要支持各種不同類型的客戶端,比如桌面瀏覽器、移動瀏覽器以及原生移動應用。應用還需要向第三方提供可訪問的API,並通過Web Service或者消息代理與其它應用實現集成。大部分web工程是將所有的功能模塊(service side)打包到一起並放在一個web容器中運行,很多企業的Java應用程序打包為war包。應用通過執行業務邏輯、訪問數據庫、與其它系統交換信息、並返回一條HTML/JSON/XML響應,來處理請求(HTTP請求與消息)。

應用採用多層架構或者六角架構,主要由以下幾類不同組件構成:

  • 展現組件——負責處理HTTP請求並響應HTML或者JSON/XML(對於web Services APIs)
  • 業務邏輯——應用的業務邏輯
  • 數據庫訪問邏輯——用於訪問數據庫的數據訪問對象

不同邏輯組件分別響應應用中的不同功能模塊。

單體架構的缺點

  1. 單體應用巨大的代碼庫可能會讓人望而生畏,特別是對那些團隊新成員來說。應用難以被理解和進行修改,進而導致開發速度減慢。由於沒有清晰的模塊邊界,模塊化會逐漸消失。另外,由於難以正確把握代碼變化,導致代碼質量逐步下滑,陷入惡性循環。
  2. 過載的IDE——代碼庫越大,IDE速度越慢,開發者的生產效率越低。
  3. 過載的Web容器——應用越大,Web容器啟動時間越長。容器啟動耗費時間,極大影響到開發者的生產效率。對部署工作也有負面影響。
  4. 持續部署困難——巨大的單體應用本身就是頻繁部署的一大障礙。為了更新一個組件,你必須重新部署整個應用。這會中斷那些可能與更改無關的後臺任務(例如Java應用中的Quartz任務),同時可能引發問題。另外,未被更新的組件有可能無法正常啟動。重新部署會增加風險,進而阻礙頻繁更新。因為用戶界面開發者經常需要進行快速迭代與頻繁重新部署,所以這對用戶界面開發者而言更加是個難題。
  5. 應用擴展困難——單體架構只能進行一維伸縮。一方面,它可以通過運行多個應用副本來增加業務容量,實現擴展。一些雲服務甚至可以根據負載量動態調整實例數量。但在另一方面,數據量增大會使得該架構無法伸縮。每個應用實例需要訪問所有數據,導致緩存低效,加大內存佔用和I/O流量。另外,不同的應用組件有不同的資源需求——有的是CPU密集型的,另外一些是內存密集型的。單體架構無法單獨伸縮每個組件。
  6. 難於進行規模化開發——單體應用是規模化開發的障礙。應用一旦達到特定規模,需要將現有組織拆分成多個團隊,每個團隊負責不同的功能模塊。舉例來說,我們可能需要設立UI團隊、會計團隊、庫存團隊等等。單體應用的問題在於它使團隊無法獨立展開工作。團隊需要在工作進度和重新部署上進行協調。對於各團隊而言,這使得變更和更新產品變得異常困難。

1-2

SOA架構

SOA架構,是一種粗粒度、開放式、松耦合的服務結構,要求軟件產品在開發過程中,按照相關的標準或協議,進行分層開發。通過這種分層設計或架構體系可以使軟件產品變得更加彈性和靈活,且儘可能的與第三方軟件產品互補兼容,以達到快速擴展,滿足或響應市場或客戶需求的多樣化、多變性。

SOA架構的缺點

  1. 可靠性(Reliability):SOA還沒有完全為事務的最高可靠性——不可否認性(nonrepudiation)、消息一定會被傳送且僅傳送一次(once-and-only-once delivery)以及事務撤回(rollback)——做好準備,不過等標準和實施技術成熟到可以滿足這一需求的程度並不遙遠。
  2. 安全性(Security):在過去,訪問控制只需要登錄和驗證;而在SOA環境中,由於一個應用軟件的組件很容易去與屬於不同域的其他組件進行對話,所以確保迥然不同又相互連接的系統之間的安全性就複雜得多了。
  3. 編排 (Orchestration):統一協調分佈式軟件組件以便構建有意義的業務流程是最複雜的,但它同時也最適合面向服務類型的集成,原因很顯然,建立在SOA上面的應用軟件被設計成可以按需要拆散、重新組裝的服務。作為目前業務流程管理(BPM)解決方案的核心,編排功能使IT管理人員能夠通過已經部署的套裝或自己開發的應用軟件的功能,把新的元應用軟件 (meta-application)連接起來。 事實上,最大的難題不是建立模塊化的應用軟件,而是改變這些系統表示所處理數據的方法。
  4. 遺留系統處理(Legacy support):SOA中提供集成遺留系統的適配器, 遺留應用適配器屏蔽了許多專用性API的複雜性和晦澀性。一個設計良好的適配器的作用好比是一個設計良好的SOA服務:它提供了一個抽象層,把應用基礎設施的其餘部分與各種棘手問題隔離開來。一些廠商就專門把遺留應用軟件“語義集成”到基於XML的集成構架中。 但是集成遺留系統的工作始終是一種挑戰。
  5. 語義 Semantics:定義事務和數據的業務含義,一直是開發面臨的最棘手的問題。語義關係是設計良好SOA架構的核心要素。 就目前而言,沒有哪一項技術或軟件產品能夠真正解決語義問題。為針對特定行業和功能的流程定義並實施功能和數據模型是一項繁重的任務,它最終必須由業務和開發者共同承擔。不過,預製組件和經過實踐證明的諮詢技能可以簡化許多難題。

1-3

微服務架構

微服務是一種用於構建應用的架構方案。微服務架構有別於更為傳統的單體式方案,可將應用拆分成多個核心功能。每個功能都被稱為一項服務,可以單獨構建和部署,這意味著各項服務在工作(和出現故障)時不會相互影響。是應用的各項核心功能,而且這些服務均可獨立運行。但是,微服務架構不只是應用核心功能間的這種鬆散耦合,它還涉及重組開發團隊、涉及如何進行服務間通信以應對不可避免的故障、滿足未來的可擴展性並實現新的功能集成。

微服務的挑戰

  1. 構建:您必須花時間明確各個服務間的依賴關係。要知道,由於存在這些依賴關係,當您完成一個構建時,可能會觸發多個其他構建。您還需要考慮微服務對於數據的影響。
  2. 測試:集成測試和端到端測試可能會前所未有的難以實施,但卻更加重要。根據您在架構相互支撐的服務時所採用的不同方式,架構中的一個部分出現故障,很可能會導致其他部分也隨之出現故障。
  3. 版本管理:在更新到新版本時,請記住:向後兼容性可能會因更新操作而失效。要解決這一問題,您可以利用條件邏輯來進行構建,但是構建會變得繁複、難以控制且快速。或者,您也可以為不同的客戶端維護多個活躍版本,但是相關的維護和管理工作會變得更加龐雜。
  4. 部署:沒錯,這也是一大挑戰,至少是首次設置時所要面臨的一大挑戰。為了簡化部署,您必須先大量投資自動化,因為人工部署無法應對微服務的複雜性。請好好思考您要以何種方式以及怎樣的順序來部署各項服務。
  5. 日誌記錄:使用分佈式系統時,您需要利用集中式日誌將所有相關信息集中到一處。否則,積累的日誌數量將讓您難以招架。
  6. 監控:您必須通過一個集中式視圖來了解整個系統的情況,以便找出問題的根源。
  7. 調試:無法進行遠程調試,因為這種方式無法涵蓋數十個或數百個服務。不幸的是,關於應該如何進行調試,目前還沒有標準答案。
  8. 連接:請考慮使用服務探索功能,無論是集中式的還是集成式。

2

集成方式

SOA體系下,服務之間通過企業服務總線(Enterprise Service Bus)通信,許多業務邏輯在中間層(消息的路由、轉換和組織)。

微服務架構傾向於降低中心消息總線(類似於ESB)的依賴,將業務邏輯分佈在每個具體的服務終端。大部分微服務基於HTTP、JSON這樣的標準協議,集成不同標準和格式變的不再重要。另外一個選擇是採用輕量級的消息總線或者網關,有路由功能,沒有複雜的業務邏輯。下面就介紹幾種常見的架構方式。

點對點方式

點對點方式中,服務之間直接用。每個微服務都開放REST API,並且調用其它微服務的接口。

架構設計思維

明顯,在比較簡單的微服務應用場景下,這種方式還可行,隨著應用複雜度的提升,會變得越來越不可維護。這點有些類似SOA的ESB,儘量不採用點對點的集成方式。

網關方式

API網關方式的核心要點是,所有的客戶端和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能個。通常,網關也是提供REST/HTTP的訪問API。服務端通過API-GW註冊和管理服務。

架構設計思維

用曾經參與項目的例子,所有的業務接口通過API網關暴露,是所有客戶端接口的唯一入口。微服務之間的通信也通過API網關。

採用網關方式有如下優勢:

  1. 有能力為微服務接口提供網關層次的抽象。比如:微服務的接口可以各種各樣,在網關層,可以對外暴露統一的規範接口。
  2. 輕量的消息路由、格式轉換。
  3. 統一控制安全、監控、限流等非業務功能。
  4. 每個微服務會變得更加輕量,非業務功能個都在網關層統一處理,微服務只需要關注業務邏輯

目前,API網關方式應該是微服務架構中應用最廣泛的設計模式。

消息代理方式

微服務也可以集成在異步的場景下,通過隊列和訂閱主題,實現消息的發佈和訂閱。一個微服務可以是消息的發佈者,把消息通過異步的方式發送到隊列或者訂閱主題下。作為消費者的微服務可以從隊列或者主題共獲取消息。通過消息中間件把服務之間的直接調用解耦。

架構設計思維

通常異步的生產者/消費者模式,通過AMQP、MQTT等異步消息規範。

03

集成難點

微服務系統架構變得越來越流行了,是模塊化的一種方法,把一整塊應用拆分成一個個服務,讓團隊在開發大型複雜的應用時更快地交付出高質量的系統,團隊成員們可以輕鬆地接受到新技術,因為他們可以使用最新且推薦的技術棧來實現各自的服務,微服務架構也通過讓每個服務都被部署在最佳狀態的硬件上而改善了應用的擴展性。但是在微服務集成過程中,數據服務、事務、查詢這些我們需要特別的處理,不像過去的單體架構、基於ESB的SOA系統架構一樣,微服務遇到了之前不存在的問題,現在許多應用大到一個人根本無法完成,而且複雜到光靠一個人去理解是不可能的。這種情況下,應用就必須被拆分成一個個模塊。在單體應用中,模塊被定義為比方一個java package。然而,這種做法在實踐中並不是很理想,時間長了,單體應用就變得越來越龐大,微服務架構把服務作為一個模塊單元,每個服務都有一個不可滲透且很難違反的邊界。也就是每個微服務要提供一種單獨而獨立的能力。這樣的話,應用程序的模塊化就更容易隨時間保存。接下來我們就看看怎麼解決這個三個問題。

3-1

數據服務設計

從來沒有一個 one-size-fits-all 的架構,所以在微服務架構下面,我們需要了解的,一樣是幾個關鍵的架構考量點。然後針對自己的實際應用,選擇哪些考量點是更加重要。現在我主要就是跟大家來討論從哪幾個角度著手來設計一個符合微服務架構原則的數據架構。比如說,我們可以從一系列的問題來開始這個討論:

  1. 這麼多微服務之間,是否可以用一個數據庫,還是多個數據庫來支持多個微服務?
  2. 如果是多個數據庫,是否為每一個微服務挑選一個最合適的數據庫,還是選擇同一種類型的數據庫?
  3. 如何在微服務架構下擴展我的數據庫?
  4. 當一個依賴的服務需要修改數據庫 Schema 的時候,是否會影響到現有系統?
  5. 當微服務應用不斷衍變的時候,數據庫是否可以快速的響應應用需求變化?

這些就是我們在微服務數據架構時候要關注的地方。

一庫一服or一庫多服

無論是單體應用,還是微服務應用,有一點是肯定的:應用的各個模塊之間都需要進行較為頻繁的通信,通過一起協同合作,來實現應用的整體價值。在單體應用中,這種通信是通過方法調用來完成的。在微服務中,則通過 API 調用來完成。這些模塊或者服務間調用,大部分時候是為了共享數據。 共享數據最賤的方式當然就是採用一種共享數據庫的模式,也就是單體應用常用的方式 - 應用可以有多個系統模塊,但一般都是隻有一個數據庫。

這種架構模式通常會被認為是微服務架構下的反範式,它的問題在於:

  • 單點故障一個數據庫倒下,整批服務全部停止。
  • 數據在同一個地方,會給貪圖方便的開發或者 DBA 工程師編寫很多數據間高度依賴的程序或者工具;
  • 無法針對某一個服務進行精準優化或擴展

所以一般推薦的做法,是為每一個微服務準備一個單獨的數據庫,也即一庫一服 (database per service) 模式。這種模式更加適合微服務架構 - 它滿足每一個服務是獨立開發、獨立部署、獨立擴展的特性。當需要對一個服務進行升級或者數據架構改動的時候,無須影響到其他的服務。需要對某個服務進行擴展的時候,也可以手術式的對某一個服務進行局部擴容。另外,如果某些服務對數據庫有特殊的需求,這種模式也為下文所講的混合持久化 (Polyglot Persistence) 提供了可能性。

混合持久化

混合持久化在大型互聯網公司是一個比較風行的模式,它秉承的原則就是為特別的任務提供最好的工具。比如說某個系統提供一個高併發低延遲的共享用戶會話方案 (shared session storage), Redis 可能是一個非常理想的選擇。如果是實現一個產品目錄,涉及到大量不定結構的商品數據及屬性的建模管理,系統可能會採用模式靈活,動態 schema 的 MongoDB 來作為我的數據庫解決方案。如果系統希望支持非常強大的全文搜索,ElasticSearch 則是行業中的佼佼者。

混合持久化的優勢很明顯,可以讓每個單獨的服務使用到最佳的工具和技術。但是它的弊端也是不容忽視。部署、監控、備份、升級等數據庫管理工作從來都是一件困難但是重要的任務。引入多個不同的數據庫,也意味著對系統管理維護的複雜度和成本提高了很多。這種情況下可能需要比較有資源的公司或者團隊才可以使用。這也解釋了這個模式在大型互聯網公司得到較多的採用與推廣。針對於其他小型規模的用戶,或者是缺乏足夠掌握各種新型技術人才的公司來說,另一種更為可行的模式可能是多模數據庫 (Multi-model)。

多模數據庫的特徵是:

  • 依然是一庫一服務(為一個服務部署一個單獨的數據庫);
  • 但是使用的是同一種類型,支持多種場景的數據庫;
  • 雖然是多實例,但是隻需維護一種類型的數據庫,管理上和人員配備上都較為簡單。

如果你在開發的應用是一款企業級產品,會交付到客戶環境部署安裝,則運維管理的簡單性將在技術選型中佔據非常重要的一個比重,無疑這種情況下多模數據庫更加適用。

3-2

數據一致性

一個傳統的單體應用可以通過ACID事務來強制業務規則從而實現一致性。想象一下,比如,金融系統的用戶都有信用額度,就是在創建訂單之前必須先看信用如何。應用程序必須確保潛在的多個併發嘗試去創建訂單不超過客戶的信用限額。如果Orders和Customers都在同一個庫中,那麼就可以使用ACID事務來搞定:不幸的是,在微服務架構中我們無法通過這種方式管理數據的一致性。Orders和Customers表被不同的服務所擁有,只能通過各自的服務API訪問。他們甚至可能在不同的數據庫。

一種比較常見的做法就是使用分佈式事務來搞定,比如2PC等。但是這種做法對於現代應用來說也許不是一種可行的方案。CAP定理要求你必須在可用性和一致性之間選擇,可用性通常是較好的選擇。而且,許多現代技術,例如大多數NoSQL數據庫,甚至不支持ACID事務,更不用說2PC。所以管理數據的一致性需要使用其他的方式。我們使用事件驅動架構中的一種技術叫事件源(event sourcing)來解決分佈式事務。

Event sourcing (事件源)通過使用根本不同的事件中心方式來獲得不需2PC的原子性,保證業務實體的一致性。 這種應用保存業務實體一系列狀態改變事件,而不是存儲實體現在的狀態。應用可以通過重放事件來重建實體現在狀態。只要業務實體發生變化,新事件就會添加到時間表中。因為保存事件是單一操作,因此肯定是原子性的。

為了理解事件源工作方式,考慮事件實體作為一個例子。傳統方式中,每個訂單映射為ORDER表中一行,例如在ORDER_LINE_ITEM表中。但是對於事件源方式,訂單服務以事件狀態改變方式存儲一個訂單:創建的,已批准的,已發貨的,取消的;每個事件包括足夠數據來重建訂單狀態。

事件是長期保存在事件數據庫中,提供API添加和獲取實體事件。事件存儲跟之前描述的消息代理類似,提供API來訂閱事件。事件存儲將事件遞送到所有感興趣的訂閱者,事件存儲是事件驅動微服務架構的基幹。

事件源方法有很多優點:解決了事件驅動架構關鍵問題,使得只要有狀態變化就可以可靠地發佈事件,也就解決了微服務架構中數據一致性問題。另外,因為是持久化事件而不是對象,也就避免了object relational impedance mismatch problem。

數據源方法提供了100%可靠的業務實體變化監控日誌,使得獲取任何時點實體狀態成為可能。另外,事件源方法可以使得業務邏輯可以由事件交換的松耦合業務實體構成。這些優勢使得單體應用移植到微服務架構變的相對容易。

事件源方法也有不少缺點,因為採用不同或者不太熟悉的變成模式,使得重新學習不太容易;事件存儲只支持主鍵查詢業務實體,必須使用 Command Query Responsibility Segregation (CQRS) 來完成查詢業務,因此,應用必須處理最終一致數據。

3-3

分佈式查詢

在傳統的單體應用中,我們通常使用join來實現跨表查詢。比如,我們可以通過sql輕鬆的查詢出最近客戶所訂的大額訂單:但我們無法在微服務架構中實現這樣的查詢。就像前面提到的那樣,Orders和Customers表分屬不同的服務,只能通過服務API來訪問。而且他們可能使用了不同的數據庫。而且,即使你使用事件源(Event Sourcing )處理查詢問題可能更麻煩。有一種解決方案就是通過一種叫CQRS(Command Query Responsibility Segregation)做法來解決分佈式查詢問題。

實現查詢的好方法是使用稱為命令查詢責任分離(CQRS)的體系結構模式: Command Query Responsibility Segregation。如名稱所示,CQRS將應用程序分為兩部分。第一部分是命令側(command-side),其處理命令(例如,HTTP POST,PUT和DELETE)以創建,更新和刪除聚合。前提是這些聚合是使用事件源實現的。應用程序的第二部分是查詢側(query-side),其通過查詢聚合的一個或多個物化視圖(materialized views)來處理查詢(例如HTTP GET)。查詢側通過訂閱由命令側發佈的事件來保持視圖(view)與聚合(aggregate)同步。查詢側(query-side)視圖可以使用任何類型的能滿足需求的數據庫來實現。根據需求,應用程序的查詢端可能使用一個或多個以下數據庫;在很多場合,CQRS是一個以事件為基礎(event-based)的綜合體,比如使用RDBMS作為記錄系統再使用比如Elasticsearch來處理文本查詢。CQRS的查詢側可以使用其它類型的數據庫,支持多種類型的數據庫,不僅僅是文本搜索引擎。而且,它通過訂閱事件準實時地去更新查詢側的視圖。

CQRS的優點

優點一:在微服務架構中實現查詢,特別是使用事件源的架構。它使應用程序有效地支持一組不同的查詢。

優點二:是把命令側和查詢側分離,達到了解耦的作用。

CQRS的缺點。

缺點一:就是需要額外的工作來開發和維護這套系統。你需要開發和部署更新和查詢視圖的查詢端服務。還有就是你需要部署視圖數據庫(view store)。

缺點二:是處理命令側和查詢側視圖之間的“滯後”。查詢層相比命令側存在一定的時延。更新聚合,然後立即查詢視圖的客戶端應用程序可能會看到聚合的以前版本。所以必須通過一些手法來避免暴露這些潛在的不一致性給用戶。

04

總結

有句話說的是架構師的工作就是每天做不斷的取捨 (trade off),因為選擇往往是讓人很糾結。微服務越來越流行,但是微服務系統架構種,集成不是架構師關注的唯一的技術點還有:服務發現、服務配置、服務框架和治理、監控體系、微服務的限流熔斷、容器部署技術&持續交付流水線,微服務能更快速的配合業務的快速發展,但是同時也出現了以前從未遇到的問題,希望後續能為大家提供更多的設計思路。

如果你依然在編程的世界裡迷茫,不知道自己的未來規劃,可以關注下我。

同時我經過多年的收藏目前也算收集到了一套完整的學習資料,希望對想成為架構師的朋友有一定的參考和幫助。

下面是資料部分截圖,誠意滿滿 : 特別適合有1-5年開發經驗的Java程序員們學習。

獲取方式:轉發收藏+關注+私信"架構"即可。


分享到:


相關文章: