面向數據的架構

面向數據的架構

譯自:Data-Oriented Architecture

2007年,Rajive Joshi在RTI 白皮書中首次提出了面向數據的架構,後在2017年,Christian Vorhemus 和 Erich Schikuta 在維也納大學的這篇iiWAS論文中再次進行了闡述。DOA是對傳統二元架構(即一體式架構和微服務、面向服務的架構)進行翻轉的結果。在面向數據的架構中,一體式數據存儲是系統的唯一狀態源,作用於松耦合、無狀態微服務。

面向數據的構架並不是萬能的,它也有其成本和好處。但在現實中,很多大公司和生態系統都受困於一類瓶頸,而面向數據的構架正好可以解決這類瓶頸。

一體式架構

儘管很多架構的定義都會與一體式架構進行對比,但本質上,它們是服務側軟件開發的自然狀態

在一個一體式服務中,大量服務側代碼會與一個或多個數據庫進行交互,並處理功能運算的方方面面。想像一個交易系統,它會接收用戶的請求,進行購買或出售證券,對其定價以及填寫訂單等。

在一體式服務中,代碼仍然是組件化的,並劃分到不同的模塊中,但程序的不同組件之間並沒有明確的API邊界。程序中唯一剛性定義的API通常是(a)UI和服務之間的交互協議(即是否使用了REST/HTTP協議);(b)服務和數據存儲使用的請求語言;(c)服務和外部依賴。

面向服務的架構和微服務

面向服務的架構(SOA)將一體式程序打散為功能獨立、組件化的服務。在我們的交易APP中,可能會使用一個獨立的服務作為對外API,接收請求並響應客戶,並使用另一個系統來接收定價和市場的其他信息,再使用一個系統來跟蹤訂單和風險等。這些服務之間的接口是正式定義的API層,服務間通常會使用RPCs來一對一進行交互,其他系統也會使用如消息傳遞和訂閱等方式。

面向服務的架構允許獨立(且並行)開發和實現不同的服務。服務間是松耦合的,這意味着一個新的服務可以重用其他服務。

由於SOA中的每個服務都有其各自的API,因此可以獨立與各個服務進行交互。開發者可以通過調試和模擬不同的組件,將不同的服務重組成新的流程,進而表現出不同的行為。

微服務是面向服務架構的一種類型。取決於問問題的人,他們認為的微服務可能和SOA不同,可能僅僅指代一些小型且輕量的服務,也可能與SOA的意思完全一樣。

擴展問題

在SOA中,組件間通過特定的API進行交互。為了交互,每個組件需要可達,即使用IP地址、服務地址或其他內部標識來不斷發送請求/消息。這意味着架構中的每個組件必須要了解它的外部依賴,並與之集成到一起。

根據架構的拓撲,當需要添加新的組件時,需要了解前面所有的組件。意味着移除一個已經集成到架構中(以及與其他多個服務建立連接)的服務可能具有一定的挑戰:需要預留臨時定義的API,並制定一個遷移計劃來轉移各個組件(移除舊服務,並接入新服務)。由於服務到服務的API是特定的,這也意味着組件間的RPC可能會異常複雜,進而增加了未來可能變化的API的範圍。變更一個其他服務依賴的API將是一項艱巨的任務。

隨着微服務的生態的增長,在進行擴展時,會對如下問題愈發敏感:

  1. 隨着組件數目的增加,集成複雜度為N2
  2. 難以根據經驗來理解網絡結構,即創建或維護一個測試環境或沙箱將需要確保沒有外部依賴的組件。

一些SOA使用者反饋的問題如下:

  • 服務間的循環依賴:由於服務是滾動式發佈的,且無法在一開始就掌控整個系統,因此很容易引入循環並打破DAG
  • SOA擴展的另外一個問題是,它要求提前了解所有未來的客戶工作流。如果事先不知道,並在多個垂直維度上對數據進行隔離,那麼後續可能會面臨如何保證跨多個持久化存儲的事務處理性能,以及如何定義哪些存儲需要備份數據。

面向數據的架構

面向數據的架構(DOA)同SOA一樣,圍繞小的、松耦合的組件進行組織,但DOA使用兩種主要的方式來劃分微服務。

  1. 組件總是無狀態的

    相比為每個相關的組件創建組件化以及聯合的數據存儲,在管理全局schema時,DOA託管描述數據或狀態層。

  2. 最小化組件間的交互,盡量通過數據層來交互。

    在我們的交易系統中,一個組件接收到不同證券的報價後,會以一種標準格式將報價發佈到我們的數據存儲中。一個系統可以通過數據層來請求並消費這些報價,而非通過特定服務的特定API來獲取。

    因此,集成成本是線性化的。一個DOA架構的變更可能設計N個組件的更新,而非N2個組件。

DOA架構真正的亮點在於,它允許不同的供應商發佈獨立的高層數據類型。如果我們移除了使用某個表的某個服務,那麼後續將不需要做其他變動,特別是當相同的數據類型有多個數據源時。例如一個交易系統連接到多個市場,每個市場都會將客戶的請求發佈到一個RFQ表,後續下游系統就可以請求該表,而無需關心客戶請求來自哪裡。

組件通信類型

由於DOA最小化了組件間的交互,那麼該如何使用數據層來移除SOA中的內部組件通信?

1.數據生產和消費

將組件組織成生產者和消費者是一種主要的DOA系統設計方式。

如果方便的話,可以在上層使用一系列mapfilterreduceflatMap以及其他一元運算來組織業務邏輯,可以將DOA系統分解為一系列組件,每個組件會請求或訂閱業務邏輯輸入,併產生輸出。DOA的挑戰在於這些中間步驟都是可見的,這意味着請求的數據要求封裝良好、表現良好,且能夠對應到特定的業務邏輯。當然,好處是系統的行為是外部可觀察的,可追溯和可審計的。

在一個SOA交易系統中,一個組件接收到一個市場的訂單後,可能會通過RPCs來確定如何進行定價、報價和交易。在DOA中,一個微服務會從市場(通常以SOA的方式)接收請求,併產生RFQs,而其他生產者則產生定價等等。另外一個服務請求RFQs時,會聚合所有需要的定價、產品配額、訂單以及其他自定義響應數據需要的數據。

2.觸發動作和行為

有時,最簡單的組件間通信方式是RPC,而設計周到的DOA系統可能會使用生產者/消費者模式來替換內部組件間的交互,但也可能會使用直接的方式來讓組件X通知組件Y執行動作Z。

首先,最重要的是考慮是否可以將RPCs重新組織為事件以及事件結果,即相比組件X通過RPCs通知組件Y哪裡發生了事件E,是否可以讓X產生事件E,然後讓組件Y通過消費這些事件來作出響應。

這種方式也被稱為基於數據的事件,跟我們通常使用的方式相反,但很強大。這種方式強大的原因是它升級了松耦合的概念。系統不需要了解誰會消費它們的事件(相反,RPC調用者需要明確知道正在調用的一方),且生產者不需要擔心事件是哪裡產生的,事件僅代表了業務邏輯。

實現基於數據的事件的一種不恰當的方式是將事件持久化到數據庫表中,數據庫表與序列化版本的RPC請求維持1:1的關係。這種情況下,基於數據的事件並不會與系統解耦。為了讓基於數據的事件正常運作,需要將一個請求/響應轉換為持久化的事件,但前提是這些請求/響應具有業務邏輯意義。

有時可能並不適用基於數據的事件。例如,如果想要觸發一個特定組件的行為,這類場景下可能會使用(有限的)組件間的RPCs方式。

面向數據架構的亮點

高集成問題空間

我一直提到交易/金融軟件的原因是它們的集成範圍通常會比較大。一個(允許用戶進行交易的)典型銷售端的公司,通常會集成很多市場來與用戶進行交互、以及其他流動性提供者來進行定價和下單。業務邏輯涉及到市場的請求以及返回給用戶的響應,是一個複雜、多階段的過程。

在一個高集成的問題空間中,一個服務可能需要了解很多其他服務。為了避免複雜度為O(N2)的集成成本以及服務的高扇出比率(一個服務對應多個服務),圍繞數據生產者和消費者來重新設計系統可以簡化集成度。在使用新的集成方式時,相比於修改N個新的系統,或將某個系統扇出到N個其他系統,新的集成過程只需編寫一個適配器,將產生的數據存儲到通用的DOA schema中,消費最終的數據並以正確的格式進行呈現。

DOA中隱含了一種新的複雜性:數據schema。在集成新的服務時不需要對系統做大的變動,且在擴展架構時不需要添加新的中間層、方法或特殊的處理場景。因此數據schema的設計也是一個艱難的過程。但隨着集成複雜度的提升,分攤的難度反而會減少。

沙盒數據,以及對數據隔離的響應

如果你正在手動調試或測試某些功能,可能會希望在生產之外的環境中做這些事情。然而某些SOA生態架構中經常很難區別哪些服務處理哪些環境以及哪些環境是自包含的。

一個環境是一個內部一致的、始終連接的服務的集合,通常(理想情況下)使用與生產相同的拓撲來組織這些服務。由於SOA服務通常是獨立尋址的,環境的一致性要求每個服務與環境中調用的其他服務保持一致。RPC以及pubsub不能從一個環境泄露到另一個環境中。

SOA中有一些方法來避免發生上述問題,如使用服務註冊來為服務生成正確的配置,當使用URI訪問服務時,可以使用帶環境前綴的不同地址來隱藏(直接的)服務地址。

在DOA中,環境的概念更加簡單。通過組件連接的存儲層就足以描述它所在的環境。由於所有組件存儲內部是無狀態的,數據通過定義進行隔離。由於組件間只會通過數據存儲進行通信,因此環境之間不存在數據泄露的風險。

面向數據的架構比您想像的更近

現今有很多近似面向數據的架構的例子。一體式數據,即所有數據持久化到一個大型數據存儲的方式,通常透露出該系統的架構正在朝着DOA發展。

例如,知識圖譜是一個概括式的一體式數據,但往往不夠通用,缺少很多與業務邏輯有關的狀態。

與一體式數據類似,GraphQL通常作為范化數據存儲層。使用GraphQL作為DOA系統的後端的程度更多與系統架構設計的選擇有關:使用與業務邏輯概念相關的概括性架構和表,而非特定數據源的架構和表。

權衡

該架構並不是萬能的。雖然面向數據的架構消除了很多問題,但也產生了新的問題:它需要設計者更多地關注數據所有者。例如,在處理多個編輯者同時修改相同記錄的情況時,通常對該記錄的寫所有權進行劃分。且由於數據中編碼了內部組件API,因此需要認真考慮共享的全局schema。

提到Google的Protocol Buffers文檔,其中有對schema中的字段設置required時進行警告的討論:Required Is Forever。Broadway Technology的CTO Joshua Walsky 說過類似DOA schema的話:Data Is Forever。出於與Protobuf的告警類似的原因,從一個松耦合的分佈式系統的表中移除一列是非常非常困難的。

總結

DOA其實是一種事件驅動架構的變種,服務(或組件)使用事件進行交互,而事件來源於數據,因此面向數據的架構準確地說是面向業務邏輯數據的架構,它將數據存儲作為整個架構的中心,服務之間通過數據存儲和訪問簡介進行交互。

使用DOA可以將SOA的集成複雜度從N2 降低到N,大大減低了服務的新增或移除對系統造成的影響。

但DOA也不是萬能的,它同樣帶來了新的複雜度,即數據schema設計上的複雜度。

與所有EDA相同,有些場景下並不適合使用DOA,特別是延遲敏感性服務,可能會導致響應不及時或影響QPS。

Tags: