iT邦幫忙

2022 iThome 鐵人賽

DAY 29
2
Software Development

軟體架構師的自我修養系列 第 29

[Day 29] 快取一致性實戰(下)

  • 分享至 

  • xImage
  •  

昨天,我們介紹了為什麼需要快取也介紹了旁讀的流程和潛在問題,當然也解釋了如何改善一致性。儘管如此,旁讀快取仍不足以滿足高一致性需求。

旁讀會產生問題的其中一個原因是因為所有使用者都能夠存取快取和資料庫,當使用者們同時操作資料時,就會因為各種不同的操作順序引發不一致。

那麼,我們乾脆限制操作的行為來避免不一致好了,這就是之後介紹的幾種方案的核心概念。

透過快取讀

讀取路徑

  • 先從快取讀資料
  • 如果快取資料不存在
  • 「由快取」從資料庫讀
  • 並且寫回快取

寫入路徑

  • 不是重點,往往會搭配透過快取寫(Write Through)或背景寫(Write Ahead)

潛在問題

這個方案最大的問題是,並不是所有的快取都支援這種作法,我們最常使用的Redis就不支援。

當然,還是有些快取可以,例如NCache,但是NCache也有他的問題。

首先,NCache沒有支援很多種程式語言,.NET Core是他原生支援的語言,其他語言沒幾種可選了。

此外,NCache分為開源版和企業版,但我們都知道當沒多少人用開源版時,出了任何問題都是悲劇。就算如此,企業版的收費很高,不僅有授權費,還需要負擔基礎建設的成本,甚至每個客戶端都要支付憑證。

如何改善

既然NCache很貴,那我們可以自己實作嗎?答案是肯定的。

對於應用程式來說,我們不需要在意快取的背後是什麼實作,只要他能很快提供資料就足夠了。因此,我們可以將Redis包裝成一個獨立快取服務,稱為資料存取層(DAL),他有一個API負責接受請求並協調快取和資料庫。

應用程式只需要透過定義好的API就可以從DAL取得資料,且不需要在意快取是怎麼實作的,甚至也不需要知道資料庫在哪。

透過快取寫

讀取路徑

  • 不是重點,往往會搭配透過快取讀。

寫入路徑

  • 資料僅寫入快取
  • 「由快取」更新資料庫

潛在問題

就像透過快取讀一樣,並不是每個快取都支援這功能,必須得要自己實作。

除此之外,快取不是設計來操作資料的,許多資料庫的能力快取都無法提供,尤其是關聯式資料庫的ACID保證。

更重要的是,快取不適合做資料持久化。當應用程式把資料寫進快取並且認為更新完成,若是快取因為「某些原因」崩潰了,那資料就遺失了,並且更新會再也找不回來。

如何改善

如同透過快取讀,必須要實作一個DAL,但是ACID和持久化等議題依然無法解決,因此「背景寫」誕生了。

背景寫

讀取路徑

  • 不是重點,往往會搭配透過快取讀。

寫入路徑

  • 資料僅寫入快取
  • 「由快取」更新資料庫

這流程與透過快取寫一樣,但實作並不相同。

潛在問題

與透過快取寫一樣,沒有什麼快取支援這方案。

背景寫是為了解決透過快取寫的問題,所以讓我們先介紹他的實作。

我們也會實作一個DAL就像透過快取讀寫般,但在DAL內是一個訊息佇列而不是快取。從圖中可以看出,整個DAL的架構變得更加複雜。

為了要能正確使用訊息佇列,開發者需要具備更多領域知識也需要更多人力才能設計和實作。

如何改善

透過使用訊息佇列,更新的持久化可以有效確保,而訊息佇列也能保證某種程度的原子性和隔離性,雖然沒有辦法完整支援ACID,但也具備基本的可靠度。

更有甚者,訊息佇列可以將許多零碎的更新合併成一個批次更新,舉例來說,當應用程式想要更新三筆資料所以發出三個請求,DAL的訊息處理者可以將這三個請求合併成為一個SQL語法以減少資料庫的存取。

有一點很重要,使用訊息佇列必須能夠確保訊息的順序,因為對於資料庫更新來說,先插入後刪除和先刪除後插入意義完全不一樣。每個訊息佇列都有不同確保順序的作法,以Kafka來說,可以透過設定相同分區鍵來達成。

儘管如此,背景寫的實作複雜度很高,如果無法負擔這種複雜度,那旁讀快取是比較好的選擇。

刪除兩次

我們已經聊過兩種快取的主要模式了。

  • 旁讀
  • 透過快取讀寫、背景寫

這兩種模式最基本的差異是實作複雜度。

對於旁讀來說,非常容易實作,並且也很容易做對。但是,旁讀也容易因為各種交互碰到許多邊角案例。

另一方面,透過實作DAL可以有效避免邊角案例,但是要把DAL做對很困難,開發者必須擁有豐富的領域知識,卻也使DAL難以達成。

那麼,要避免邊角案例只能透過DAL這一種作法嗎?不,不盡然。

這就是「刪除兩次」這個模式想要解決的。

讀取路徑

  • 先從快取讀資料
  • 如果快取資料不存在
  • 改從資料庫讀
  • 並且寫回快取

這流程與旁讀完全一樣。

寫入路徑

  • 先清除快取
  • 接著更新資料庫
  • 等一下後再一次清除快取

潛在問題

刪除兩次的目的是為了減少旁讀快取邊角案例的影響時間。

整個不一致的期間完全取決於那個「等一下」的時間。

但是怎麼等待實際上也是一個困難的實作問題,如果我們讓更新資料的客戶端直接處理再次刪除,那麼就與旁讀變體的邊角案例一樣了。

但如果由其他人透過非同步的方式處理,那要怎麼定義溝通的合約並且管理工作流程會非常惱人。

如何改善

刪除兩次依然無法解決旁讀的邊角案例2,但再次強調,如果有實作優雅終止(graceful shutdown),那大部分情況都可以避免。

結論

在這兩天,我們介紹了能提升快取一致性的幾種方法。大體來說,當一致性不是必要條件時,使用快取期限就足夠了,也不太會造成實作負擔。事實上,在後端開發中被廣泛使用的CDN,就是使用快取期限的一個經典例子。

當使用情境變得複雜,對於一致性的要求會越來越高,那麼就可考慮改成旁讀或刪除兩次來達成。只要能夠正確實作,那麼這兩種方法就能滿足多數情境了。

但是,當一致性需求變得更多,就有必要採取更複雜的實作,例如透過快取讀寫,甚至背景寫快取。雖然這些做法都能提升一致性,但是成本也很高。首先,要正確實作必須要有足夠的人力和領域知識,其次,實作的時間成本和後續的維護成本亦甚高,更重要的是,還有額外的基建成本。

為了更進一步提升一致性,就需要使用更專業的技術,例如透過多數決演算法來決定快取和資料庫的資料是否一致。這也是Meta的TAO背後所用的概念,但我不打算介紹這麼複雜的架構,畢竟,我們都不是Meta,至少,我不是。

在一個普通的組織中,對於一致性的需求沒有這麼嚴格,例如要到10個九以上,而且一般的組織也無法負擔複雜且龐大的架構。

因此,我選了一些我們都可以做到的實踐來解說。儘管這些實踐都很單純,但正確實作下也足夠可靠了。


上一篇
[Day 28] 快取一致性實戰(上)
下一篇
[Day 30] 特性開關妙無窮
系列文
軟體架構師的自我修養31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言