翻越快取的三座大山

騰訊技術工程發表於2020-06-30

前言

在網際網路和移動網際網路兩波浪潮的推動下,儲存技術有了飛速發展。移動網際網路使用者在過去十年增長了 10 倍,使用者的增長帶動了資料量的指數級增長,因為激烈的市場競爭,企業和使用者對應用程式的響應效能要求越來越高,在完美應對龐大的使用者規模和海量資料集的同時保證優秀的產品體驗,是資料庫面臨的挑戰。在機械硬碟普及的時代,企業需要透過快取技術加速資料的訪問,在 SSD 儲存介質普及後,企業需要快取技術支撐高併發和大吞吐,透過引入分散式快取方案,提升應用程式效能,消除資料庫熱點。但是快取技術的引入增加了業務架構的複雜度,降低了開發效率,同時還面臨著快取一致性、快取擊穿、快取雪崩等挑戰

快取的三座大山

翻越快取的三座大山

快取一致性

快取一致性是指業務在引入分散式快取系統後,業務對資料的更新除了要更新儲存以外還需要同時更新快取,對兩個系統進行資料更新就要先解決分散式系統中的隔離性和原子性難題。目前大多數業務在引入分散式快取後都是透過犧牲小機率的一致性來保障業務效能,因為要在業務層嚴格保障資料的一致性,代價非常高,業務引入分散式快取主要是為了解決效能問題,所以在效能和一致性面前,通常選擇犧牲小機率的一致性來保障業務效能。

翻越快取的三座大山

快取擊穿

快取擊穿是指查詢請求沒有在快取層命中而將查詢透傳到儲存 DB 的問題,當大量的請求發生快取擊穿時,將給儲存 DB 帶來極大的訪問壓力,甚至導致 DB 過載拒絕服務。空資料查詢(駭客攻擊)和快取汙染(網路爬蟲)是常見的引發快取擊穿的原因。什麼是空資料查詢?空資料查詢通常指攻擊者偽造大量不存在的資料進行訪問(比如不存在的商品資訊、使用者資訊)。快取汙染通常指在遍歷資料等情況下冷資料把熱資料驅逐出記憶體,導致快取了大量冷資料而熱資料被驅逐。快取汙染的場景我們目前還沒有發現較好的解決方案,但是在空資料查詢問題上我們可以改造業務,透過以下方式防止快取擊穿:

  • 透過 bloomfilter 記錄 key 是否存在,從而避免無效 Key 的查詢;

  • 在 Redis 快取不存在的 Key,從而避免無效 Key 的查詢;

翻越快取的三座大山

快取雪崩

快取雪崩是指由於大量的熱資料設定了相同或接近的過期時間,導致快取在某一時刻密集失效,大量請求全部轉發到 DB,或者是某個冷資料瞬間湧入大量訪問,這些查詢在快取 MISS 後,併發的將請求透傳到 DB,DB 瞬時壓力過載從而拒絕服務。目前常見的預防快取雪崩的解決方案,主要是透過對 key 的 TTL 時間加隨機數,打散 key 的淘汰時間來儘量規避,但是不能徹底規避。

翻越快取的三座大山

傳統分散式快取方案

在引入分散式快取後,我們的業務架構由原有兩層架構(應用+資料庫)變成了三層架構(應用+快取+儲存),快取層快取熱資料,儲存層負責全量資料持久化儲存。儲存架構的變化要求業務對資料的存取邏輯進行相應調整,而且這個調整是巨大的。在快取系統的選擇上,常見的快取資料庫包括 Memcached、Redis,目前使用最廣泛的是 Redis,儲存資料常見的包括關係型資料庫 MySQL、PG、Oreacle、SQLServer 等,NoSQL 資料庫 MongoDB、Hbase 等。在引入分散式快取後,業務邏輯需要做三個點的變化,快取讀取、快取更新、快取淘汰。

翻越快取的三座大山

快取讀取

引入快取層後,讀資料就變得不是那麼簡單直接了,APP 需要先去快取讀取資料,如果快取 MISS(資料沒有被快取),則需要從儲存中讀取資料,並將資料更新到快取系統中,整個流程和程式碼如下所示:

翻越快取的三座大山

示例程式碼

快取更新

我們把常見的快取更新方案總結為兩大類,業務層更新和外部元件更新,比較常見的是透過業務更新的方案。

業務層更新快取
快取更新的難點

剛開始接觸快取方案的同學可能會糾結幾個點,先更新快取還是先更新儲存,快取的處理是透過刪除來實現還是透過更新來實現。這裡我們面臨的問題本質上是一個資料庫的分散式事務的問題,需要處理資料可靠性的挑戰,併發更新帶來的隔離性挑戰,和資料更新原子性的挑戰。

  • 資料可靠性

如果要保證資料的可靠性,在業務邏輯成功之前,必須保障有一份資料落地,我們有以下兩個選擇:

  • 先更新成功儲存,再更新快取;

  • 先更新成功快取,再跟新儲存,如果儲存更新失敗,刪除快取;

  • 操作隔離性。

一條資料的更新涉及到儲存和快取兩套系統,如果多個執行緒同時操作一條資料,並且沒有方案保證多個操作之間的有序執行,就可能會發生更新順序錯亂導致資料不一致的問題。

翻越快取的三座大山
  • 更新原子性

引入快取後,我們需要保證快取和儲存要麼同時更新成功,要麼同時更新失敗,否則部分更新成功就會導致快取和儲存資料不一致的問題。

翻越快取的三座大山

業務層快取更新方案

我們看到大多數的常見是選擇以下方案,保障資料可靠性,儘量減少資料不一致的出現,透過 TTL 超時機制在一定時間段後自動解決資料不一致現象。

  • Step1:更新儲存,保證資料可靠性;

  • Step2:更新快取,2 個策略怎麼選:


    • 惰性更新:刪除快取,等待下次讀 MISS 再快取(推薦方案);

    • 積極更新:將最新的值更新到快取(不推薦);

翻越快取的三座大山

積極更新策略,快取資料實時性更高,但是在快取側帶來了更多的更新操作,這會提高更新衝突導致髒資料機率。

外部元件更新快取
  • 快取 MISS 處理方案

在透過第三方元件更新的方案中,為了保障資料的一致性,避免對單條資料的並行更新,快取的所有更新操作都需要交給同步元件,因此快取 MISS 場景下的邏輯:

翻越快取的三座大山
  • 快取更新方案


    • 第一:需要監控儲存的日誌,或者透過 Triger 來監控儲存資料的變更,需要對儲存系統非常熟悉;

    • 第二:需要對更新進行過濾,我們的目的是快取熱資料,但是像 DDL、批次更新這一系列的操作是不需要更新快取的,要把非業務更新操作過濾;

    • 第三:同步元件需要理解資料,不通用;

    • 先更新儲存,由第三方元件非同步更新快取;

    • 該方案投入較大,只適合特定的場景,並且有以下 3 個難點:

翻越快取的三座大山
  • 其他快取更新方案

在實際的生產中,我們還會看到很多先更新快取,然後透過第三方元件更新儲存的場景,但是這個方案也會面臨資料一致性和資料可靠性的挑戰,雖然不推薦,但是確實還是能看到有在使用這個方案的,我們拿出來探討下。

  • 這個場景資料可靠性,不及先更新儲存的方案,但是寫入效能高,延遲低;

  • 這個方案 APP 和第三方元件都會更新 Cache,會存在資料一致性的問題,因為很難保障兩個元件更新的時序。

翻越快取的三座大山

快取淘汰

快取的作用是將熱點資料快取到記憶體實現加速,記憶體的成本要遠高於磁碟,因此我們通常僅僅快取熱資料在記憶體,冷資料需要定期的從記憶體淘汰,資料的淘汰通常有兩種方案:

  • 主動淘汰,這是推薦的方式,我們透過對 Key 設定 TTL 的方式來讓 Key 定期淘汰,以保障冷資料不會長久的佔有記憶體。TTL 的策略可以保證冷資料一定被淘汰,但是沒有辦法保障熱資料始終在記憶體,這個我們在後面會展開;

  • 被動淘汰,這個是保底方案,並不推薦,Redis 提供了一系列的 Maxmemory 策略來對資料進行驅逐,觸發的前提是記憶體要到達 maxmemory(記憶體使用率 100%),在 maxmemory 的場景下快取的質量是不可控的,因為每次快取一個 Key 都可能需要去淘汰一個 Key。

相關文章