簡體   English   中英

數據非規范化如何與微服務模式一起工作?

[英]How does data denormalization work with the Microservice Pattern?

我剛讀了一篇關於微服務和PaaS架構的文章。 在那篇文章中,大約三分之一的時間,作者說(在像Denzy的Denormalize下):

重構數據庫模式,並對所有內容進行反規范化,以實現數據的完全分離和分區。 也就是說,不要使用提供多個微服務的基礎表。 不應共享跨多個微服務的基礎表,也不應共享數據。 相反,如果多個服務需要訪問相同的數據,則應通過服務API(例如已發布的REST或消息服務接口)共享它。

雖然這在理論上聽起來很棒,但在實踐中它還有一些需要克服的嚴重障礙。 其中最大的一點是,數據庫經常緊密耦合,每個表與至少一個其他表有一些外鍵關系。 因此它可能是不可能的分區的數據庫到由n個微服務控制n個子數據庫。

所以我要問: 給定一個完全由相關表組成的數據庫,如何將其歸一化為較小的片段(表組),以便片段可以由單獨的微服務控制?

例如,給定以下(相當小但是示例性)數據庫:

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime
user_id

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
product_id
order_id
quantity_ordered

不要花太多時間批評我的設計,我在飛行中做了這個。 關鍵是,對我來說,將這個數據庫分成3個微服務是合乎邏輯的:

  1. UserService - 用於系統中的CRUDding用戶; 應該最終管理[users]表;
  2. ProductService - 用於系統中的CRUDding產品; 應該最終管理[products]表;
  3. OrderService - 用於系統中的CRUDding訂單; 應該最終管理[orders][products_x_orders]

但是,所有這些表都具有彼此的外鍵關系。 如果我們將它們歸一化並將它們視為整體,它們就會失去所有的語義:

[users] table
=============
user_id
user_first_name
user_last_name
user_email

[products] table
================
product_id
product_name
product_description
product_unit_price

[orders] table
==============
order_id
order_datetime

[products_x_orders] table (for line items in the order)
=======================================================
products_x_orders_id
quantity_ordered

現在沒有辦法知道是誰訂購了什么,數量或時間。

那么這篇文章是典型的學術喧囂,還是這種非規范化方法有一個真實世界的實用性,如果是這樣,它看起來是什么樣的(在答案中使用我的例子的獎勵積分)?

這是主觀的,但以下解決方案適用於我,我的團隊和我們的數據庫團隊。

  • 在應用層,微服務被分解為語義功能。
    • 例如, Contact服務可能是CRUD聯系人(有關聯系人的元數據:姓名,電話號碼,聯系信息等)
    • 例如, User服務可能使用登錄憑據,授權角色等CRUD用戶。
    • 例如, Payment服務可以CRUD付款並在第三方PCI兼容服務(如Stripe等)下工作。
  • 在DB層,可以組織表,但是devs / DBs / devops人們希望組織表

問題在於級聯和服務邊界:付款可能需要用戶知道誰在付款。 而不是像這樣建模您的服務:

interface PaymentService {
    PaymentInfo makePayment(User user, Payment payment);
}

這樣建模:

interface PaymentService {
    PaymentInfo makePayment(Long userId, Payment payment);
}

這樣,屬於其他微服務的實體僅通過ID在特定服務內引用 ,而不是通過對象引用引用。 這允許DB表在所有地方都有外鍵,但在app層,“外來”實體(即生活在其他服務中的實體)可通過ID獲得。 這可以阻止對象級聯失控,並清晰地描述服務邊界。

它產生的問題是它需要更多的網絡呼叫。 例如,如果我給每個Payment實體一個User參考,我可以通過一次通話讓用戶獲得特定支付:

User user = paymentService.getUserForPayment(payment);

但是使用我在這里建議的內容,你需要兩個電話:

Long userId = paymentService.getPayment(payment).getUserId();
User user = userService.getUserById(userId);

這可能是一個交易破壞者。 但是如果你聰明並實現緩存,並實現精心設計的微服務,每次調用響應50-100毫秒,我毫不懷疑這些額外的網絡調用可以精心設計, 不會給應用程序帶來延遲。

這確實是微服務中的關鍵問題之一,在大多數文章中都很容易省略。 幸運的是有解決方案。 作為討論的基礎,我們提供了您在問題中提供的表格。 在此輸入圖像描述 上圖顯示了表格在整體中的外觀。 只有幾個表連接。


要將其重構為微服務,我們可以使用很少的策略:

Api加入

在這種策略中,微服務之間的外鍵被破壞,微服務暴露出模仿這個密鑰的端點。 例如:Product findProductById將公開findProductById端點。 訂單微服務可以使用此端點而不是連接。

在此輸入圖像描述 它有一個明顯的缺點。 它比較慢。

只讀視圖

在第二個解決方案中,您可以在第二個數據庫中創建表的副本。 復制是只讀的。 每個微服務都可以在其讀/寫表上使用可變操作。 當只讀取從其他數據庫復制的表時,它們(顯然)只能使用讀取 在此輸入圖像描述

高性能讀取

通過在read only view解決方案之上引入諸如redis / memcached之類的解決方案,可以實現高性能讀取。 連接的兩側應復制到優化用於閱讀的平面結構。 您可以引入全新的無狀態微服務,可用於從此存儲中讀取。 雖然看起來很麻煩,但值得注意的是,它將比關系數據庫上的單片解決方案具有更高的性能。


幾乎沒有可能的解決方案。 最簡單的實現性能最低。 高性能解決方案需要幾周時間才能實施。

我意識到這可能不是一個好的答案,但是到底是什么。 你的問題是:

給定一個完全由相關表組成的數據庫,如何將其反規范為較小的片段(表組)

WRT數據庫設計我會說“你不能沒有刪除外鍵”

也就是說,推送具有嚴格無共享數據庫規則的微服務的人要求數據庫設計者放棄外鍵(並且他們正在隱式或顯式地執行此操作)。 當他們沒有明確說明FK的丟失時,你會想知道他們是否真的知道並認識到外鍵的價值(因為它經常根本沒有提到)。

我看到大型系統被分成幾組表。 在這些情況下,可以有A)組之間不允許FK或B)一個特殊組,其中包含可由FK引用到其他組中的表的“核心”表。

...但是在這些系統中,“表組”通常是50多個表,因此不足以嚴格遵守微服務。

對我而言,微服務分割數據庫方法需要考慮的另一個相關問題是它報告的影響,即所有數據如何匯總在一起以便報告和/或加載到數據倉庫中的問題。

有些相關的還有忽略內置數據庫復制功能以支持消息傳遞(以及核心表/ DDD共享內核的基於數據庫的復制如何)影響設計的傾向。

編輯:(通過REST調用JOIN的成本)

當我們按照微服務的建議拆分數據庫並刪除FK時,我們不僅失去了強制聲明性業務規則(FK),而且我們也失去了DB跨這些邊界執行連接的能力。

在OLTP中,FK值通常不是“UX友好”,我們經常想加入它們。

在示例中,如果我們獲取最后100個訂單,我們可能不希望在UX中顯示客戶ID值。 相反,我們需要再次致電客戶來獲取他們的名字。 但是,如果我們還想要訂單行,我們還需要再次調用產品服務來顯示產品名稱,sku等而不是產品ID。

一般來說,我們可以發現,當我們以這種方式分解數據庫設計時,我們需要做很多“通過REST連接”調用。 那么這樣做的相對成本是多少?

實際故事:“通過REST加入”與數據庫連接的示例成本

有4個微服務,它們涉及很多“通過REST加入”。 這4項服務的基准負載達到約15分鍾 這4個微服務轉換為1個服務,4個模塊對共享DB(允許連接)在~20秒內執行相同的負載。

遺憾的是,這並不是DB連接與“JOIN via REST”的直接對比,因為在這種情況下我們也從NoSQL DB更改為Postgres。

與具有基於成本的優化器等的DB相比,“通過REST加入”表現相對較差,這是一個意外嗎?

在某種程度上,當我們像這樣分解數據庫時,我們也放棄了“基於成本的優化器”以及為我們執行查詢執行計划所做的一切,有利於編寫我們自己的連接邏輯(我們在某種程度上相對編寫了我們自己的不復雜的查詢執行計划)。

我會將每個微服務視為一個對象,就像任何ORM一樣,您使用這些對象來提取數據,然后在您的代碼和查詢集合中創建連接,微服務應該以類似的方式處理。 這里的區別僅在於每個微服務一次代表一個對象而不是完整的對象樹。 API層應該使用這些服務,並以必須呈現或存儲的方式對數據建模。

為每個事務多次調用服務不會產生影響,因為每個服務都在一個單獨的容器中運行,並且所有這些calles都可以並行執行。

@ ccit-spence,我喜歡交叉服務的方法,但它是如何被其他服務設計和使用的? 我相信它會為其他服務創造一種依賴。

有什么意見嗎?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM