Kafka不是數據庫

理解流式基礎設施的使用和濫用,這一點很重要。

Kafka 是一種消息代理,在過去幾年中迅速流行起來。消息代理已經存在很長時間了,它們是一種專門用於在生產者和消費者系統之間“緩衝”消息的數據存儲。Kafka 已經相當流行,因爲它是開源的,並且能夠支持海量的消息。

消息代理通常用於解耦數據的生產者和消費者。例如,我們使用一個類似 Kafka 的消息代理來緩衝客戶生成的 Webhook,然後將它們批量加載到數據倉庫中。

image

在這個場景中,消息代理提供了從客戶發送事件到 Fivetran 將它們加載到數據倉庫之間的事件持久存儲。

但是,Kafka 有時候也被描述爲是一種比消息代理更大的東西。這個觀點的支持者將 Kafka 定位爲一種全新的數據管理方式,Kafka 取代了關係數據庫,用於保存事件的最終記錄。與讀寫傳統數據庫不同,在 Kafka 中,先是追加事件,然後從表示當前狀態的下游視圖中讀取數據。這種架構被看成是對“數據庫的顛覆”。

原則上,以一種同時支持讀和寫的方式實現這個架構是有可能的。但是,在這個過程中,最終會遇到數據庫管理系統幾十年來遇到的所有難題。你或多或少需要在應用程序層開發一個功能齊全的 DBMS,而你可能不會做得太好,畢竟一個數據庫需要很多年才能做好。你需要處理髒讀、幻讀、寫偏移等問題,還要應付匆忙實現的數據庫存在的所有其他問題。

ACID 困境

將 Kafka 作爲數據存儲的一個最基本的問題是它沒有提供隔離機制。隔離意味着在全局內,所有事務 (讀和寫) 都是沿着某些一致的歷史記錄發生的。Jepsen 提供了一個隔離級別指南 (https://jepsen.io/consistency)

我們舉一個簡單的例子來說明爲什麼隔離很重要:假設我們正在運營一個在線商店。當用戶結賬時,我們要確保他們下的訂單都有足夠的庫存。我們是這樣做的:

  1. 檢查用戶購物車中每個物品的庫存水平。
  2. 如果某個物品沒有庫存,則中止結賬。
  3. 如果所有物品都有庫存,從庫存中減去它們,並確認。

假設我們使用 Kafka 來實現這個流程。我們的架構可能看起來像這樣:

image

Web 服務器從 Kafka 下游的庫存視圖讀取庫存,但它只能在 Checkouts 主題的上游提交事務。問題在於併發控制:如果有兩個用戶爭着購買最後一件商品,那麼只有一個用戶可以購買成功。我們需要讀取庫存視圖,並在一個單獨的時間點確認結帳。但是,在這個架構中沒有辦法做到這一點。

我們現在遇到的問題叫做寫偏移。當結賬事件被處理時,從庫存視圖中讀取的數據可能已經過時。如果兩個用戶同時嘗試購買相同的物品,他們都將購買成功,那麼我們便沒有足夠的庫存供應給他們。

這種基於事件溯源的架構存在很多類似這樣的隔離異常,讓用戶感到很困惑。更糟糕的是,研究表明,允許異常存在的架構也存在安全漏洞,給了黑客竊取數據的機會。

將 Kafka 作爲傳統數據庫的補充

如果你只是將 Kafka 作爲傳統數據庫的補充,這些問題就可以避免:

image

OLTP 數據庫負責執行消息代理不太擅長的關鍵任務:事件的准入控制。與將消息代理作爲“觸發並遺忘”事件的容器不同,OLTP 數據庫可以拒絕衝突性事件,確保只接收一個具有一致性的事件流。OLTP 數據庫在這一核心併發控制任務上做得非常出色——可擴展到每秒處理數百萬個事務。

當使用數據庫作爲數據入口,從數據庫讀取事件的最佳方法是通過 CDC(變更數據捕獲)。市場上有幾個很棒的 CDC 框架,例如 Debezium(http://debezium.io/)和 Maxwell(http://maxwells-daemon.io/),以及來自現代 SQL 數據庫的原生 CDC。CDC 還提供了優雅的運維解決方案。在進行數據恢復時,可以清除下游的所有內容,並從 (持久化的)OLTP 數據庫重新構建。

不要隨意構建錯誤的數據庫

幾十年來,數據庫社區已經總結了一些重要的經驗教訓。這些教訓都是在造成數據損壞、數據丟失和讓用戶遭受損失的情況下獲得的,併爲此付出了慘重的代價。如果你不小心構建了一個錯誤的數據庫,那麼你會發現自己只不過是在重新經歷這些經驗教訓。

實時流式消息代理是管理快速變化的數據的一個很好的工具,但你仍然需要一個傳統的 DBMS 來實現事務隔離。要實現一個“顛覆性的數據庫”,可以使用 OLTP 數據庫進行准入控制,使用 CDC 進行事件生成,並將數據的下游副本變成物化視圖。

 

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