微服務裏的事件驅動數據管理

微服務下的分佈式數據管理問題

單體應用程序通常具有單個關係數據庫。 使用關係數據庫的主要好處是應用程序可以使用ACID事務,這提供了一些重要的保證:

  • 原子性–原子地進行更改
  • 一致性–數據庫狀態始終是一致的
  • 隔離–即使事務是同時執行的,看起來它們還是串行執行的
  • 持久性–事務一旦提交,便不會撤消

有了這些,應用程序就可以可以很容易的地開啓事務,執行更改(插入,更新和刪除)多行的操作並提交事務。

使用關係數據庫的另一個巨大好處是它提供了SQL,這是一種豐富,聲明式和標準化的查詢語言。我們可以輕鬆地編寫將多個表中的數據合併在一起的查詢。然後,RDBMS query planner確定執行查詢的最佳方法。不必擔心底層細節,例如如何訪問數據庫。而且,由於您所有應用程序的數據都在一個數據庫中,因此查詢起來很容易。

然後,當我們轉向微服務架構時,數據訪問變得更加複雜。這是因爲每個微服務擁有的數據是該微服務專用的,並且只能通過其API訪問。封裝數據可確保微服務鬆散耦合,並且可以彼此獨立發展。如果多個服務訪問相同的數據,則schema更新需要對所有服務協調更新。

更糟糕的是,不同的微服務通常使用不同種類的數據庫。現代應用程序存儲和處理各種數據,而關係數據庫並不總是最佳選擇。對於某些用例,特定的NoSQL數據庫可能具有更方便的數據模型,並提供更好的性能和可伸縮性。例如,對於存儲和查詢文本的服務來說,使用諸如Elasticsearch之類的文本搜索引擎是有意義的。同樣,存儲社交圖數據的服務可能應使用圖數據庫,例如Neo4j。因此,基於微服務的應用程序經常使用SQL和NoSQL數據庫的混合,即所謂的多語言持久性方法。

用於數據存儲的分區,多語言持久性架構具有許多優點,包括鬆散耦合的服務以及更好的性能和可伸縮性。但是,它確實帶來了一些分佈式數據管理方面的挑戰。

第一個挑戰是如何實現在多個服務之間保持業務transaction的一致性的。要了解爲什麼會出現問題,我們可以來看一個在線B2B商店的示例。客戶服務維護有關客戶的信息,包括他們的信用額度信息。訂單服務負責管理訂單,並且必須驗證新訂單沒有超出客戶的信用額度。如果用單體應用程序實現此功能的話,訂單服務可以簡單地使用ACID transaction來檢查可用的信用額度並創建訂單。

相反,在微服務體系結構中,ORDER和CUSTOMER表是它們各自的服務專用的,如下圖所示。

Each service in a microservices architecture maintains a private database table

訂單服務無法直接訪問CUSTOMER表。它只能使用客戶服務提供的API。訂單服務可能會使用分佈式事務,也稱爲兩階段提交(2PC)。但是,在現代應用中2PC通常不是可行的選擇。 CAP定理要求您在可用性和ACID樣式一致性之間進行選擇,而可用性通常是更好的選擇。而且,許多現代技術,例如大多數NoSQL數據庫,都不支持2PC。維護服務和數據庫之間的數據一致性至關重要,因此我們需要另一個解決方案。

第二個挑戰是如何實現從多個服務檢索數據的查詢。例如,假設應用程序需要顯示客戶及其最近的訂單。如果訂單服務提供了用於檢索客戶訂單的API,那麼您可以使用應用程序側聯接來檢索此數據。該應用程序從客戶服務中檢索客戶,並從訂單服務中檢索客戶的訂單。但是,假設Order Service僅支持按主鍵查找訂單(也許它使用的NoSQL數據庫僅支持基於主鍵的檢索)。在這種情況下,沒有直接的方法來檢索所需的數據。

事件驅動架構

對於許多應用程序而言,解決上述問題的方案是使用事件驅動的體系結構。 在這種體系結構中,微服務會在某些操作執行時(例如更新業務實體)發佈事件publish event。 其他微服務訂閱則這些事件。 當微服務收到事件時,它就更新自己的業務實體,這可能會發布更多事件。

我們可以使用事件來實現跨多個服務的業務交易。 交易的transactions 包括一系列步驟。 每個步驟都包含一個微服務對該微服務更新業務實體併發布觸發下一步的事件。 下面的圖表序列顯示了在創建訂單時如何使用事件驅動的方法來檢查可用信用。 微服務通過消息代理交換事件。

  1. 訂單服務order service創建狀態爲NEW的訂單,併發布Order Created事件。
    In step 1 of a credit check in a microservices architecture, the Order Service publishes an 'Order Created' event
  2. 客戶服務customer service監聽消費“Order Created”事件,爲訂單保留信用,併發布“Credit Reserved”事件。
    In a microservices architecture, the second step in a credit check is for the Customer Service to generate a 'Credit Reserved' event
  3. 訂單服務consume “Credit Reserve”事件,並將訂單狀態更改爲“open”
    In a microservices architecture, the third step in a credit check is for the Order Service to set the order status to 'Open'

更復雜的情況下可能會涉及其他步驟,例如在上述場景當中在檢查客戶信用的同時可以保留庫存。

假設每個服務保證自動更新數據庫併發布事件,並且Message Broker保證事件至少被傳遞一次,則我們就可以實現跨多個服務的業務交易。 重要的是要注意,這些操作不滿足ACID transactions。 它們提供的保證要弱得多,例如系統可能只會保證最終的一致性。 該交易模型已被稱爲BASE模型。

您還可以使用事件來維護實例化視圖,這些視圖預先加入了多個微服務擁有的數據。 維護視圖的服務訂閱相關事件並更新視圖。 例如,維護客戶訂單視圖的客戶訂單視圖更新程序服務訂閱由客戶服務和訂單服務發佈的事件。

In a microservices architecture, a service can subscribe to event notifications published by other services as triggers for action

客戶訂單視圖更新程序服務收到客戶或訂單事件時,將更新客戶訂單視圖數據存儲。您可以使用文檔數據庫(例如MongoDB)實現客戶訂單視圖,併爲每個客戶存儲一個文檔。客戶訂單視圖查詢服務通過查詢客戶訂單視圖數據存儲來處理對客戶和最近訂單的請求。

事件驅動的體系結構有這樣幾個優點和缺點。好處是首先,它使跨多個服務的事務的實現成爲可能,並提供最終的一致性。另一個好處是它還使應用程序能夠維護實例化視圖。一個缺點是,編程模型比使用ACID事務時更復雜。通常,我們必須實現補償事務以從應用程序級故障中恢復;例如,如果信用檢查失敗,則必須取消訂單。此外,應用程序必須處理不一致的數據。這是因爲各個transaction之間用到的數據對彼此來說是可見的。如果應用程序從尚未更新的實例化視圖中讀取數據,則會看到不一致之處。另一個缺點是事件的訂閱方必須檢測並忽略重複的事件。

實現原子性

在事件驅動的體系結構中,還存在原子更新數據庫併發布事件的問題。 例如,訂購服務必須在ORDER表中插入一行併發布訂購創建事件。 這兩個操作必須保證原子性。 如果服務在更新數據庫之後但在發佈事件之前崩潰,則系統會變得不一致。 確保原子性的標準方法是使用涉及數據庫和Message Broker的分佈式事務。 但是,由於上述原因以及CAP定理,這正是我們不希望做的

使用本地事務發佈事件

一種實現原子性的方法是,應用程序使用僅涉及本地事務的多步驟過程來發布事件。 關鍵訣竅是在存儲業務實體狀態的數據庫中創建一個EVENT表,該表的作用就是充當消息隊列。 應用程序開始(本地)數據庫事務,更新業務實體的狀態,將事件插入EVENT表,然後提交事務。 提供一個單獨的應用程序線程或進程查詢EVENT表,將事件發佈到Message Broker,然後使用本地事務將事件標記爲已發佈published的狀態。 下圖是該設計的示例。

In a microservices architecture, achieve atomicity by using only local transactions to publish events

上圖做的事情是:訂單服務將一行插入到ORDER表中,並將訂單創建事件插入到EVENT表中。事件發佈者線程或進程在EVENT表中查詢未發佈的事件,發佈事件,然後更新EVENT表以將事件標記爲已發佈。

這種方法有這樣幾個優缺點。一個好處是,它可以確保每次更新都發佈一個事件,而無需依賴2PC。此外,該應用程序還發布業務級別的事件,從而無需解析它們。這種方法的一個缺點是,由於開發人員必須記住要發佈事件,因此它很容易出錯。這種方法的侷限性在於,在使用某些NoSQL數據庫時難以實施,因爲NoSQL數據庫它們的事務和查詢功能有限。

通過讓應用程序使用本地事務來更新狀態和發佈事件,該方法消除了對2PC的需求。現在讓我們看一下一種通過使應用程序簡單地更新狀態來實現原子性的方法。

挖掘數據庫事務日誌

在沒有2PC的情況下實現原子性的另一種方法是,事件由挖掘數據庫事務或提交日誌的線程或進程發佈。 該應用程序更新數據庫,從而將更改記錄在數據庫自身的事務日誌中。 事務日誌挖掘器線程或進程讀取事務日誌並將事件發佈到Message Broker。 下圖是設計說明
In a microservices architecture, achieve atomicity by mining the transaction log for events

這種方法的一個示例是開源LinkedIn Databus項目。 Databus挖掘Oracle事務日誌併發布與更改相對應的事件。 LinkedIn使用Databus來保持各種派生數據存儲與記錄系統一致。

另一個示例是AWS DynamoDB中的流機制,這是一個託管的NoSQL數據庫。 DynamoDB流包含過去24小時內對DynamoDB表中的項目進行的按時間順序排列的更改(創建,更新和刪除操作)序列。應用程序可以從流中讀取這些更改,例如,將其作爲事件發佈。

事務日誌挖掘具有一些優缺點。一個好處是,它保證了每次更新都可以發佈事件,而無需使用2PC。事務日誌挖掘還可以通過將事件發佈與應用程序的業務邏輯分開來簡化應用程序。一個主要的缺點是事務日誌的格式是每個數據庫專有的,甚至可以在數據庫版本之間進行更改。同樣,要從事務日誌中記錄的相對底層的更新中對更高層業務事件進行轉化也是很難的。

事務日誌挖掘通過讓應用程序執行一件事來消除對2PC的需求:更新數據庫。現在讓我們看一下消除更新並僅依賴事件的另一種方法。

使用事件源

通過使用不同的,以事件爲中心的方法來持久化業務實體,事件無需2PC就可以實現原子性。 該應用程序不是存儲實體的當前狀態,而是存儲一系列狀態更改事件。 應用程序通過重播事件來重建實體的當前狀態。 只要業務實體的狀態發生變化,就會在事件列表中附加一個新事件。 由於保存事件是單個操作,因此它本質上是原子的。

事件源如何工作,可以Order實體爲例。 在傳統方法中,每個訂單都映射到ORDER表中的一行以及例如ORDER_LINE_ITEM表中的行。 但是,在使用事件源時,訂單服務以其狀態更改事件的形式存儲訂單:已創建,已批准,已發貨,已取消。 每個事件都包含足夠的數據來重構訂單狀態。

In a microservices architecture, achieve atomicity with event sourcing

事件保留在事件的存儲中,該事件存儲是事件的數據庫。該商店具有用於添加和檢索實體事件的API。在我們之前描述的體系結構中,事件存儲的行爲也類似於消息代理。它提供了使服務能夠訂閱事件的API。事件存儲將所有事件傳遞給所有感興趣的訂戶。事件存儲是事件驅動的微服務體系結構的骨幹。

事件源有幾個好處。它解決了實現事件驅動的體系結構中的關鍵問題之一,並使得在狀態改變時可靠地發佈事件成爲可能。結果,它解決了微服務體系結構中的數據一致性問題。另外,由於它保留事件而不是域對象,因此它主要避免了對象關係阻抗不匹配的問題。事件源還提供了對業務實體所做的更改的100%可靠的審覈日誌,並使得可以實施臨時查詢來確定實體在任何時間點的狀態。事件源的另一個主要優點是您的業務邏輯由交換事件的鬆散耦合的業務實體組成。這使得從單片應用程序遷移到微服務架構變得容易得多。

事件源也有一些缺點。這是一種不同且陌生的編程風格,因此存在學習曲線。事件存儲區僅直接支持通過主鍵查找業務實體。您必須使用命令查詢職責隔離(CQRS)來實現查詢。結果是,應用程序必須處理最終一致的數據。

總結

在微服務架構中,每個微服務都有自己的私有數據存儲。 不同的微服務可能使用不同的SQL和NoSQL數據庫。 儘管此數據庫體系結構具有顯着的優勢,但它帶來了一些分佈式數據管理方面的挑戰。 第一個挑戰是如何實現在多個服務之間保持一致性的業務交易。 第二個挑戰是如何實現從多個服務檢索數據的查詢。

對於許多應用程序,解決方案是使用事件驅動的體系結構。 實現事件驅動的體系結構的一個挑戰是如何原子更新狀態以及如何發佈事件。 有幾種方法可以做到這一點,包括將數據庫用作消息隊列,事務日誌挖掘和事件源。

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章