今天我們要討論資料一致性,尤其是快取和資料庫間的一致性。事實上,這是一個很重要的主題,特別是當組織變大,那麼對一致性的要求就會提高,這也連帶影響快取的實作。
舉例來說,一個新創的服務和一個成熟的服務相比不需要很高的SLA。對於一個新創服務來說,資料一致性的SLA也許四個九(99.99%)就算很高了,但是對於成熟服務例如AWS S3來說,SLA甚至是11個九。
我們都知道每增加一個九,實作複雜度會像指數級數般往上增加,所以對於新創服務來說,基本上沒有足夠資源來維護一個很高的SLA。
因此,如何盡可能有效的運用資源以提高一致性?這就是今天我們要討論的。再強調一次,這邊的一致性指的是快取和資料庫間的資料一致性。
我相信我們都同意把資料放在兩個不同地方無可避免地會不一致。那麼是什麼原因讓我們寧可背負不一致的風險也要使用快取?
綜上所述,我們需要快取。
因為快取不需要資料持久化,所以可以使用記憶體作為儲存媒介,不僅便宜也更有效率。正因為便宜,所以快取可以盡可能靠近使用者,以剛例子來說,我們雖然資料庫放在新加坡,但可以在東京放一個快取,讓日本使用者可以就近存取。
既然快取是必要的,那麼如何盡可能提高快取一致性呢?
為了避免失焦,這系列文提到的快取會以Redis為準,而資料庫則是MySQL。我們的目的是盡可能用有限資源(包含硬體和人力)提高資料一致性,因此一些大型組織的複雜架構不在解說範圍內,例如Meta的TAO。
TAO是一個分散式快取,並且具備超高SLA(10個九)。但是,要維運這個服務,背後有非常複雜的架構,甚至光是監控系統就大到不行,而這不是一般組織負擔得起的。
因此,我們會專注在以下模式,並闡明他們的問題以及說明該如何避免。
以下章節都會遵循這個流程。
在將資料寫入快取時,我們會加入一個TTL讓資料自動過期。
EXPIRE key seconds [ NX | XX | GT | LT]
當更新資料時,因為資料只有寫回資料庫,所以快取和資料庫的資料不一致。這個不一致會一直持續到快取過期,儘管如此,要選擇一個好的TTL是很困難的。
如果TTL設得太長,那不一致的持續時間就會變長,反之,太短則會讓快取沒有效益。
值得一提的是快取是為了減少資料庫的負載並且提升資料存取效能,如果一個超短的TTL會讓快取完全沒用。舉例來說,如果TTL設為1秒,但1秒間沒有人要讀取,那這個快取就毫無價值。
寫入路徑看起來很正常,但是當資料庫更新,必須要有一個機制能更新快取內的資料,而這也是旁讀的概念。
這個流程和「快取期限」一樣,但是TTL會設定的足夠長,這會使快取更有機會發揮效益。
這樣的寫入路徑和讀取路徑看起來沒問題,但還是會有幾個邊角案例無可避免。
A
想要更新資料,但是B
同時想要讀取資料。個別來看,A
和B
都是正確的,但放在一起就出錯了。在上面例子中,B
在A
清掉快取前就讀到資料了,因此B
取得的資料是不一致的。
當A
要更新資料,且已經完成資料庫更新,但在清掉快取前就因為「某些原因」被砍了,那麼快取就會維持不一致一段時間,直到下次更新或快取過期。
被砍了聽起來很嚴重也很少見,事實上,這遠比你想的容易發生。有幾種不同的情境會導致被砍。
當A
想要讀取資料,而B
想要更新資料,再一次強調,A
和B
個別來看都是正確的但放在一起就錯了。
首先,A
試著從快取讀,但是沒找到對應資料,因此改從資料庫讀。於此同時,B
試著更新資料庫並且將快取清除。接著,A
把資料寫回快取,結果不一致就發生了,而且這個不一致也會持續一段時間。
案例1和案例3可以靠著正確實作應用程式而將發生機率降到極低。以案例1為例,在更新完資料庫後別做多餘的事,趕快把快取清掉;而案例3,在從資料庫讀完資料後,別做太多資料轉換,盡快把資料寫回快取。透過這種方式,我們可以極大的降低機率,但即使如此,也有一些無可避免的情況,例如垃圾回收觸發的「世界暫停」,那應用程式怎麼改都無解。
至於案例2,可以透過實作優雅終止(graceful shutdown)來避免出現問題,但若是應用程式崩潰,那基本上沒救。
為了解決案例1和案例2,有些人會試著修改原來的流程。
這個過程與旁讀一模一樣。
這與旁讀的流程完全相反。
雖然旁讀的邊角案例1和案例2可以解決,但會產生新的問題。
當A
試著更新資料,而B
想要讀取資料,A
先把快取清了所以B
讀不到快取只能改從資料庫讀取。接著,A
繼續更新資料庫,最終B
把從資料庫讀取的舊資料寫回快取,不一致就這樣發生了。
事實上,案例1和案例2比起這個變體的邊角案例更難發生,尤其是正確實作應用程式可以極大減低案例1和案例2的發生機率。
另一方面,變體1的邊角案例很難有效改善。
因此,不建議使用變體。
一般來說,旁讀快取就可以達到相對高的資料一致性了,雖然只是簡單的實作,但也足夠可靠。
儘管如此,如果想要進一步提升一致性,那麼單靠旁讀是不夠的。總是會需要較複雜的機制,但也會帶來更高的實作負擔和成本。
因此,我將那些較複雜的方案留到明天。明天我們會繼續介紹如何用有限的資料盡可能達成更高的快取一致性。
再一次強調,雖然旁讀快取很單純,但只要正確實作,也足夠可靠了。