1. 程式人生 > 實用技巧 >面向資料的架構[轉]

面向資料的架構[轉]

  在軟體架構中,有一種模式雖鮮為人知的,但值得引起更多的關注。面向資料的架構(Data-Oriented Architecture)由 Rajive Joshi在RTI 的2007 年白皮書中首次提出,而維也納大學(University of Vienna)的Christian Vorhemus 和Erich Schikuta 在2017 年的iiWAS 論文中又再次對其進行了描述。 DOA 是對傳統二分法的顛覆,它介於單體架構和微服務(Microservices)、面向服務的架構(Service-Oriented Architecture)之間。單體架構由一個單體二進位制檔案(binary)和資料儲存組成;微服務、面向服務的架構由許多小型的、分散式的、獨立的二進位制檔案組成,並且每個二進位制檔案都有自己的資料儲存。在面向資料的架構中,單體資料儲存是系統中狀態的唯一來源,並由鬆耦合無狀態的微服務對其進行操作。

  我很幸運,我的前僱主也採用了這種非同尋常的架構選擇。它提醒我們,事情可以用不同的方式來做。無論如何,面向資料的架構都不是銀彈。它有自己獨特的成本和收益。不過,我確實發現,許多大型公司和生態系統都陷入了某種種類型的瓶頸,而這種型別的瓶頸正是面向資料的架構能解決的。

單體架構簡介

  由於許多架構通常都是在與單體架構(Monolithic Architecture)進行對比的情況下定義的,因此,花一些時間來介紹單體架構是值得的。畢竟,它是服務端軟體開發傳說中的自然狀態

  在單體(monolithic)服務中,大部分服務端程式碼都在一個程式中,該程式與一個或多個數據庫通訊,並處理功能計算的各個方面。假設有一個交易系統,它接收客戶購買或出售某種證券的請求,為它們定價,並完成訂單。

  在單體服務中,仍然可以將程式碼元件化並分離到各個模組中,但是程式中不同元件之間的API 邊界不是強制的。程式中唯一經過嚴格定義的API 通常是:**(a)UI 和服務端之間的 API(可以使用任何它們約定好的 REST/HTTP 協議);(b)服務端和資料儲存之間的 API(可以使用任何它們約定好的查詢語言);或(c)** 服務端與其外部依賴之間的 API。

面向服務的架構和微服務

  另一方面,面向服務的架構(Service-oriented architectures,SOA)將單體程式分解成各個相互獨立的、元件化的功能服務。在我們的交易應用程式中,我們可能需要一個單獨的服務來作為外部 API 接收請求並處理客戶響應;第二個單獨的系統來接收報價和其他市場相關的資訊;第三個系統來跟蹤訂單和風險等。這些服務之間的介面都是一個個形式化定義的 API 層。服務之間通常通過 RPC 進行點對點的通訊,此外,通過其他通訊技術(如,訊息傳遞和釋出訂閱模式)進行通訊也是很常見的。

  面向服務的架構允許根據需要對不同的服務進行獨立(並行)開發和推理。這些服務是鬆耦合的,這就意味著一個全新的服務現在可以重用其他服務了。

  由於 SOA 中的每個服務都定義了自己的 API,因此可以獨立訪問每個服務並與之互動。開發人員如果要除錯或模擬各個功能部分,可以分別呼叫各個元件,並且新流程可以重新組合這些單獨的服務以啟用新的行為。

  微服務是面向服務的架構的一種形式。根據服務物件的不同,它們可能與 SOA 不同,因為這些服務本應特別小巧輕量,或者它們只是 SOA 的同義詞。

規模問題

  在 SOA 中,各個元件通過每個元件各自定義的特定 API 直接相互通訊。為了通訊,每個元件都可以單獨定址(即,使用 IP 地址、服務地址或其他內部識別符號來相互發送請求 / 訊息)。這意味著架構中的每個元件都需要了解它們的依賴關係,並且需要專門與它們的依賴進行整合。

  依賴於架構的拓撲結構,這可能意味著需要一個額外的元件來跟蹤瞭解所有之前的元件。此外,這可能還意味著要替換一個已經與其他 N 個元件通訊的單個服務也是一種挑戰:我們需要注意保留我們定義的任何點對點的 API,並確保有一個遷移計劃,用於將每個元件從老的定址服務移動到新的定址服務上。由於服務到服務的 API 是點對點的(ad-hoc)(1),這通常意味著元件之間的 RPC 可以是任意複雜的,這可能會增加將來 API 變更的影響面。因為如果要對服務中被其他服務依賴的每個 API 進行變更都將是一項艱鉅的任務。

  

  我要說的是,隨著微服務生態系統的發展,在規模上,它變得很容易受到如下問題的影響:

  1. 隨著元件數量的增長(2),整合的複雜度也以 N^2 的級別增加。
  2. 網路的形狀變得很難用先驗來推理;即,建立或維護測試環境或沙箱將需要進行大量的推理才能確保圖中的任何元件都不具有外部依賴性

  我的一些朋友也提出了一些他們在使用大規模面向服務的架構時遇到的問題:

隨著 SOA 規模的增長,我發現的另一個問題是服務之間的迴圈依賴。由於我們是單獨釋出各個服務的,很少從頭開始構建整個系統,因此很容易引入迴圈並破壞 DAG。

大規模 SOA 另一個值得注意的問題是:它們要求我們提前瞭解所有未來的客戶工作流。假設我們需要跨多個垂直領域來隔離單個工作流的資料,如果沒有做到提前瞭解,那麼我們要麼會遇到效能問題,因為它將試圖保證跨多個持久化儲存的事務性;要麼需要重新定義要用哪些垂直主伺服器來複制(快取,但實際上是持久化到資料庫中的)資料。

面向資料的架構

  在面向資料的架構( Data-Oriented Architecture,DOA)中,系統仍然圍繞小型的、鬆耦合的標準來組織元件,就像在 SOA、微服務中一樣。但是 DOA 與微服務的區別主要體現在兩個方面:

  1. 元件通常是無狀態的

DOA 沒有對每個相關元件的資料儲存進行元件化和聯合,而是要求按照集中管理的全域性模式來描述資料或狀態層。

  1. 最小化了元件之間的互動,並通過資料層的互動來替代

在我們的交易系統中,接收不同證券報價的元件在我們的資料儲存中只是以一種規範的形式來發布價格。系統可以通過查詢資料層的價格來使用這些報價,而無需通過特定的 API 向某個特定的服務(或一組服務)請求價格。

這裡,整合的代價是線性的。變更 DOA 模式意味著最多隻需要更新 N 個元件,而不是它們之間互聯的最大值 N^2。

真正令人矚目的地方在於不同的提供者可以填充獨立的高階資料型別。如果我們用一張表來替換一個服務,這並不會帶來太大的簡化。但是當同一個通用資料型別有多個源時,這樣做就會有很大的幫忙。假設交易系統需要連線到多個市場,每個市場都會將客戶的請求釋出到詢價(RFQ)表中,那麼下游系統就可以查詢這個表,而無需關心客戶請求到底來自何處。

元件通訊型別

由於在 DOA 中最小化了元件之間的互動,那麼如何通過資料層的互動來代替當今 SOA 中元件之間的通訊呢?

1. 資料生產和消費

設計 DOA 系統的主要方法是將元件組織成資料的生產者和消費者。

如果我們能夠在較高層次上將業務邏輯編寫為一系列的 map、filter、reduce、flatMap 和其他一元(monadic)操作,那麼我們就可以將 DOA 系統編寫成一系列的元件,每個元件都查詢或訂閱其輸入併產生其輸出。 DOA 面臨的挑戰在於這些中間步驟是可見的、可查詢的資料,這意味著需要對其進行良好的封裝和表示,並且需要將其與特定的業務邏輯概念對應。不過,它的優勢在於系統的行為是可從外部觀察、跟蹤和稽核的。

在 SOA 交易系統中,從市場上接收訂單的元件可能會使用 RPC 呼叫來確定如何對訂單進行定價、報價或交易。在 DOA 中,微服務接收來自市場的請求(通常是通過 SOA 的方式)並生成詢價(RFQ),而其他生產者則生產定價資料,等等。另一個微服務通過請求來查詢 RFQ,該 RFQ 會結合它們的所有定價以輸出報價、訂單或任何其他需要響應的自定義資料。

2. 觸發動作和行為

有時,RPC 是元件之間通訊的最簡單方式。雖然在設計良好的 DOA 系統中(3),其大部分元件間的通訊採用的是生產者 / 消費者模式,但是我們可能仍需要採用直接的方式來讓元件 X 告訴 Y 去做 Z。

首先,必須考慮是否可以將 RPC 重組為事件(event)及其影響(effect)。即,不是讓元件 X 向發生事件 E 的元件 Y 傳送 RPC 請求,而是詢問 X 是否可以生成事件 E,並讓元件 Y 通過消費這些事件來驅動響應?

這種方法,我稱之為基於資料的事件(data-based events),它可以很好地逆轉我們通常使用的元件通訊方式。它之所以如此強大是因為它使我們可以將“鬆耦合”這個術語提升到一個全新的層次。系統不需要知道誰在消費它的事件(即,系統不是一個絕對需要知道他們在呼叫誰的 RPC 呼叫方),生產者也無需擔心事件的來源,只需知道這些事件的業務邏輯語義即可。

當然,存在一種簡單的方法可以實現基於資料的事件,在這種方法中,每個事件都是以與 RPC 請求序列化版本 1:1 的對應關係持久化到自身表的資料庫中。在這種情況下,基於資料的事件根本不會使系統解耦合。為了使基於資料的事件能正常執行,則要求將請求 / 響應轉換成的持久化事件必須是有意義的業務邏輯結構。

基於資料的事件有時可能不太合適。例如,我們實際上要觸發某個特定元件中的行為。在這些情況下,可能仍然需要保留少量的實際元件到元件的 RPC。

面向資料的架構的成功案例研究

高整合問題空間

我之所以一直以交易 / 財務軟體為例,部分原因在於財務通常需要較大的整合表面積。一個典型的允許較小客戶進行交易的賣方公司,通常會與許多市場進行整合,以與客戶進行互動,而許多流動資金提供者則會通過其獲取價格並下訂單。在請求進入市場到對客戶做出響應之間需要處理的業務邏輯是一個複雜的、多階段的流程。

在高整合問題空間中,單個服務可能需要了解許多其他服務。為了避免 O(N^2) 的整合成本及具有高扇出比率的複雜獨立服務的出現,圍繞資料生產者和消費者的重新配置系統可以使整合更加簡單。假設要進行一個新的整合,不能編寫 N 個新系統,也不能編寫一個具有向 N 個其他系統進行復雜扇出的系統,那麼整合過程可能需要編寫一個介面卡,該介面卡以通用的 DOA 模式生產資料、消費最終的輸出並以正確的線性格式來呈現。

隱含地是,整合中出現了一種新的複雜性:需要考慮模式。任何新的整合對我們的系統而言都應該是原生的,並且我們的模式應該能在不新增補充、修改和特殊用例的情況下擴充套件。這本身就是一項艱鉅的任務。但是,當整合的數量足夠多時,難度就會降低,而且往往是值得的。

沙箱資料以及資料隔離的推理

如果我們要手動建模或測試,則希望最好能在生產之外進行。但是,某些 SOA 生態系統的架構方式通常意味著,想要知道某個服務所處的環境或特定環境是否完全獨立並不那麼容易。

環境是指內部一致、連線一致的服務集合,通常或理想情況下,它應與生產的拓撲結構相同。由於 SOA 服務通常是可獨立定址的,因此,環境一致性斷言要求環境中的每個服務必須與環境中的其他服務就呼叫哪個地址能達成共識。RPC、訂閱模式(pubsub)和資料流不能從一個環境洩漏到另一個環境中。

很明顯,在 SOA 中有很多方法可以解決這個問題,比如轉換到能為服務生成正確配置的服務註冊中心(4),或者,如果是通過 URI 訪問服務,則隱藏直接的服務地址,以支援某個環境字首下的不同路徑(5)。

然而,在 DOA 中,環境的概念要簡單得多。知道元件連線到哪個資料儲存層就足以描述它所處的環境了。由於所有元件都不在內部儲存任何狀態,因此資料是根據定義來隔離的。元件僅通過資料儲存進行通訊,因此不存在將資料從一種環境洩漏到另一種環境的危險。

面向資料架構比你想象的更接近現實

如今,有很多類似於面向資料的架構的通用案例。將所有(或大部分)資料儲存在一個大型資料儲存中的資料單體,在系統架構上就非常接近於 DOA。

例如,知識圖譜(Knowledge Graphs)就是一個廣義的資料單體。也就是說,它們通常不是很通用;許多與業務邏輯相關的狀態可能會丟失。

GraphQL通常被用作標準化的資料儲存層,就像資料單體一樣。 GraphQL 是否能成功地成為 DOA 系統的後端,在很大程度上取決於系統對模式設計的選擇:選擇與業務邏輯概念相關的通用模式和表,而不是選擇特定於該資料特定源的模式和表。

權衡取捨

這種架構也不是萬能的。當面向資料的架構消除了某些型別的問題時,就會出現新的問題:它要求設計人員需要認真考慮資料的所有權。當多個寫程式修改同一記錄時,可能會很麻煩,它通常會鼓勵系統仔細劃分記錄的寫入所有權。而且,由於元件間的 API 是在資料中編碼的,因此必須採用需要謹慎考慮的共享全域性模式。

我記得 Google 的 Protocol Buffers 文件,在討論如何根據需要將模式中的欄位標記為 required 時,它會警告說:“Required Is Forever”。在 Broadway Technology,首席技術官(CTO)Joshua Walsky 曾對 DOA 模式說過類似的話:資料是永遠存在(Data Is Forever)。事實證明,出於與 Protobuf 警告類似的原因,在鬆耦合的分散式系統中,從表中刪除列確實非常困難。

我的建議是:如果您擔心自己的架構存在水平擴充套件問題,那麼就可以考慮以資料單體為中心來進行設計了。

備註

(1)服務到服務的 API 不一定是點對點的,但是元件到元件的直接通訊通常意味著,為了達到某個給定的目的,需在兩者之間傳遞引數。

(2)一個架構的整合複雜度增長是否真能達到 N^2,實際上取決於架構的拓撲結構。如果在我們使用的系統中整合是主要的瓶頸之一,則可能會遇到這個問題。

例如,集成了各種流動資金提供者和場外交易(OTC)市場的交易系統,在理想情況下不應處於這樣的場景中:每個管理市場訂單的元件都需要了解每個提供流動資金的元件。

(3)非常適合的 DOA 就是精心設計的。

(4)假設服務呼叫對方是基於直接地址的(例如,IP 或正在執行的程序的某些內部地址模式),並且服務基於命令列引數能知道在何處訪問特定的服務,那麼就可能需要使用更適合的邏輯來包裝這些服務了,對應的邏輯需要根據環境來構造正確的標誌。

(5)例如,與其通過 IP 地址或特定於某個服務的內部 URI 來訪問該特定服務,不如將每個服務構造在一個服務端路由的“路徑”下。例如,使用 ://env.namespace.company.com/Employees/* 而不是 ://process1.namespace.company.com/*

原文連結:https://blog.eyas.sh/2020/03/data-oriented-architecture/