場景
假設我們正在使用微服務架構模式開發一個在線商店應用。大多數的服務都需要持久化數據到某些數據庫中。例如,訂單服務存儲訂單信息,用戶服務存儲用戶信息。
問題
微服務應用中的數據庫架構是什么樣的?
約束條件
服務必須是松耦合的,這樣才能夠獨立地開發、部署和擴展這些微服務。
必須保持某些跨多個服務的業務事務的不變性。例如,下訂單用例必須驗證新訂單不會超出用戶信用額度限制。某些業務事務必須更新由多個服務擁有的數據。
某些業務事務需要查詢由不同服務擁有的數據。例如,查看可用信用額度用例必須查詢用戶服務來查找信用額度限制以及訂單服務來計算未完成訂單總數量。
某些查詢必須關聯由多個服務擁有的數據。例如,查找屬于特定區域的用戶以及他們最近一段時間的訂單,需要關聯用戶和訂單。
有時候為了擴展,數據庫必須可以被復制和共享。
不同服務有不同的數據存儲要求。對于某些服務,關系型數據庫是最好的選擇。其它服務可能需要NoSQL數據庫比如MongoDB(擅長存儲復雜、非結構化數據)或Neo4J(能高效地存儲以及查詢圖形數據)。
解決方案
保持每個微服務的持久化數據是私有的并且只能通過該服務的API訪問。下圖展示了該模式的結構:
服務的數據庫實際上作為該服務實現的一部分。它不能直接地被其它服務訪問。
保持服務持久化數據的私有有一些不同的方式。為每個服務提供一個數據庫服務器不是必要。例如,如果使用關系型數據庫,可以使用下面的三種方式:
Private-tables-per-service —— 每個服務擁有一個必須只能被該服務訪問的表集
Schema-per-service —— 每個服務都有一個私有的數據庫schema
Database-server-per-service —— 每個服務都有自己的數據庫服務器
Private-tables-per-service和schema-per-service的開銷是最低的。使用schema-per-service比較有吸引力,因為該方式讓所有權比較清楚。某些高吞吐量服務可能需要它們自己的數據庫服務器。
創建一些邊界壁壘來加強模塊化是個好主意。例如,可以給每個服務分配不同的數據庫用戶ID,并且使用數據庫訪問控制機制比如授權。沒有某種邊界壁壘來實施封裝,開發人員總會被誘惑去繞過某個服務的API而去直接訪問它的數據。
影響
每個服務一個數據庫的好處:
有利于確保服務是松耦合的。某個服務的數據庫發生改變不影響其它服務。
每個服務能夠使用最合適它們需求的數據庫類型。例如,某個服務處理文本搜索,可以使用ElasticSearch。處理社交圖數據的服務可以使用Neo4j。
弊端:
實現跨不同服務的事務很復雜。由于CAP理論,最好避免分布式事務。此外,很多現代(NoSQL)數據庫不支持分布式事務。最好的解決方案是使用Saga模式(事件履歷模式)。當服務更新數據時,服務發布事件。其它的服務訂閱這些事件并且更新它們的數據作為回應。
目前在多個數據庫中實現關聯查詢是很有挑戰的。
一些解決方案:
API組合 —— 應用處理關聯而不是數據庫。例如,每個服務(或者API網關)可以檢索某個用戶和他的訂單,首先從用戶服務檢索用戶,然后查詢訂單服務,返回用戶最近一段時間的訂單。
命令與查詢職責分離(CQRS)—— 維護一個或多個包含不同服務的數據的物化視圖(materialized view)。這些視圖由訂閱事件的服務保有。例如,通過維護一個關聯用戶和訂單的視圖,在線商店能夠執行某個查詢去找出某個特定區域的用戶和他們最近一段時間的訂單。該視圖由訂閱用戶和訂單事件的服務更新。
管理不同SQL和NoSQL數據庫的復雜性