《軟件架構》緩存技術

本文源自併發編程網的翻譯邀請,翻譯的是 Jakob Jenkov 的 《軟件架構》 中關於緩存技術的內容,雖然是 2014 年的文章,但是從軟件架構層面上,並不過時。

緩存

緩存是一種加速數據查找(數據讀取)的技術,直接讀取本地緩存的數據,而不是從數據源讀取數據,數據源包括數據庫、其他遠程系統。caching

緩存是比源數據更靠近使用方的一塊存儲空間,可以更快的讀取操作。緩存的存儲介質一般是內存或磁盤,很多時候會選擇內存作爲緩存介質,但是內存緩存會在系統重啓時丟失數據。

在軟件系統中,數據緩存存在多層緩存級別或多層緩存系統。在 web 應用中,緩存至少有 3 種存儲位置,如下圖所示:

caching

在 web 應用中,我們會使用各種各樣的數據庫存儲數據,這些數據庫可以將數據存放在內存中,以便我們直接讀取,而不需要從磁盤中讀取數據。web 服務器可以在內存中緩存圖片、css 文件、js 文件等,不需要每次需要的時候從硬盤中訪問文件。web 應用可以將從數據庫讀取的數據緩存起來,這樣就不需要每次使用的時候都通過網絡從數據庫中讀取數據了。最後,瀏覽器也可能存儲靜態文件和數據。在支持 HTML5 的瀏覽器中,有 localstorage 存儲空間、應用數據緩存、本地 sql 存儲等技術支持緩存。

當我們提到緩存的時候,有下面幾項內容需要考慮:

  • 寫緩存
  • 保持緩存和遠程系統數據同步
  • 管理緩存大小

我會在接下來的內容中討論這幾項內容。

寫緩存

第一項挑戰是從遠程系統中讀取數據寫到緩存中,一般有兩種方式:

  • 提前寫緩存
  • 用時寫緩存

提前寫緩存是在系統啓動的時候,就將需要的數據緩存起來。要做到這一點,需要提前知道哪些數據需要緩存。但是我們有時候並不知道哪些數據需要在系統啓動時候就緩存起來。

用時寫緩存是說,在第一次使用數據的時候,將數據緩存起來,之後就可以使用緩存中的數據了。這種操作的方式是,首先檢查緩存中是否有數據,有就直接使用,如果沒有,就從遠程系統讀取數據,然後寫入緩存中。

下表中我列出了提前寫入和用時寫入的優缺點:

優點 缺點
提前寫緩存 比用時寫入減少了第一次緩存數據的延遲 系統啓動初始化緩存數據的時候,需要比較長的時間。而且,有可能緩存的數據永遠不會被用到。
用時寫緩存 緩存的數據都是需要被用到的數據,而且沒有啓動延遲 在第一次緩存數據的時候,用的時間比較長,可能導致用戶體驗不一致

當然,在真正實踐過程中,我們可能兩種方式並用:我們可以對熱點數據使用提前緩存的方式,對其他數據使用用時緩存的方式。

保持緩存和遠程系統數據同步

緩存數據的一個巨大挑戰是保持緩存數據與遠程系統數據保持同步,也就是數據一致。根據系統結構的不同,一般有不同的方式實現這個,我們來聊聊這幾種方式。

直接式緩存

直寫式緩存是允許讀寫緩存的一種方式,這種方式是,保存緩存數據的計算機,在將數據寫入緩存的同時,將數據寫到遠程系統中。簡單說就是,寫入操作被寫到遠程系統中。

只有遠程系統的數據只能被直寫式緩存修改時,這種方式才起作用。如果所有的數據讀寫都要經過直寫式緩存系統,那就很容易將寫入的數據更新到遠程系統中,保持緩存與遠程系統數據的一致性。

基於過期時間

如果遠程系統可以不依賴遠程系統進行數據更新,那緩存和遠程系統之間數據同步就很難通過直寫式緩存方式保證了。

保持緩存數據同步的一種方法是,爲數據設置一個緩存時間。當數據過期時,就把這些數據從緩存中清除。如果再次需要讀取這些數據,可以從遠程系統中讀取最新的數據緩存起來。

數據過期時間取決於系統需要,有些類型的數據(比如文章),可能不需要隨時的完全更新,可以設置 1 小時的過期時間。對於某些文章,你甚至可以忍受 24 小時的過期時間。

需要注意的是,如果過期時間比較短,可能會頻繁讀取遠程系統,降低緩存的作用。

主動過期

還有一種方式是主動過期,是指主動更新緩存數據。比如,遠程系統數據更新時,發送一條消息到緩存系統中,指示系統數據已被更新,可以將數據設置爲過期。

主動過期的優點是,可能保證遠程系統數據更新後,緩存數據被儘快的更新。還有一個附加好處是“基於過期時間”方式沒有辦法是實現的,就是不會頻繁更新沒有修改的數據。

主動過期的缺點是,需要能夠檢測遠程系統數據的變化。如果遠程系統是一個關係型數據庫,可以被不同的機制更新數據,那每種更新機制都需要報告他們更新了哪些數據,否則,就沒有辦法向緩存數據的系統通知過期消息了。

管理緩存大小

管理緩存大小,是一個重要的方面。許多系統存儲了大量數據,以至於不可能將所有數據都存儲在緩存中。因此,需要一種機制來管理緩存的數據量。管理緩存大小通常是將不需要的緩存數據清除,來騰出足夠的空間。一般有下面幾種方式:

  • 基於時間清理
  • 先進先出(FIFO)
  • 先進後出(FILO)
  • 最少被使用
  • 最小訪問間隔

基於時間清理方式是類似於前面提到的基於時間過期。除了可以保持數據與遠程系統同步,還能夠減少緩存數據的大小。可以開啓一個單獨的監聽線程,也可以在讀寫新值的時候清理數據。

先進先出清理方式意味着,當寫入一個新的緩存的時候,就需要刪除最早插入的緩存值。如果空間足夠,也是可以不刪除任何數據的。

先進後出的方式正好和先進先出相反,這種方式對於先存儲的數據時熱點數據的情況比較有用。

最少被使用清理方式是首先清理訪問次數最少的緩存數據。這種方式的目的是避免清理熱點數據,爲了實現這種方式,需要記錄緩存數據被訪問的次數。需要注意一個問題,緩存中的舊值可能有較高的訪問次數,這樣就意味着這些舊值不會被清理。比如一篇舊文章的緩存,以前被訪問過很多次,但是最近很少訪問了,但是因爲原來的訪問量很高,儘管目前訪問量較低,也不會被清理。爲了避免這種情況,訪問次數可以是針對 N 個小時統計。

最小訪問間隔清理方式是將訪問時間間隔考慮在內。訪問某個緩存數據時,就需要標記訪問該數據的時間並增加訪問次數。第二次訪問這個緩存數據時,就增加訪問次數,並計算平均訪問時間。那些曾經是熱點數據,被頻繁訪問,但是最近訪問時間間隔變長,訪問頻率下降的數據,其平均訪問時間會降低,當降到足夠低的時候,就會被清理。

有一種變化方式是,只計算最後 N 次訪問的時間。N 可以是 100、1 或者其他任何有意義的數。每當訪問計數到 N 時,訪問計數被重置爲 0,記錄下來訪問時間。這種方式可以更快的清理熱度下降的數據。

還有一種變化方式是,定期重置訪問計數,並且只使用最小訪問的清理方式。比如,每緩存一個小時的數據,前一個小時的訪問計數會存儲在另一個變量中,以便決策清理時使用。下一個小時訪問計數重置爲 0。這種機制具有上次變化相同的效果。

最後兩個變體之間的差異總結起來就是在每次緩存檢查時,訪問計數是否已達到 N,或者時間間隔是否已超過 Y。第一種方式是每隔 N 次訪問一次系統時鐘,而第二種方式在每次訪問時都讀取一次系統時鐘(查看時間間隔是否已過期)。因爲檢查一個整數通常比讀取系統時鐘快,所以我會選擇第一種方式。

請記住,即使使用緩存大小管理系統,也需要清理、讀取和存儲數據,以保證他們能夠與遠程系統保持一致。儘管緩存的數據被大量訪問而駐留在系統中,有時候也需要與遠程系統同步。

服務器集羣中的緩存

單一服務中的緩存設計更加簡單,因爲你能夠保證,所有寫入操作都通過一個服務器,可以使用直寫式緩存方式。但是在分佈式集羣中,情況會比較複雜,下圖說明了這種情況:

caching

簡單的使用直寫式緩存只會更新寫操作的服務器上的緩存,集羣中其他服務器對此完全不知情,也就不會更新數據。

在服務器集羣中,可以使用基於時間的過期策略或者主動過期策略,來保證緩存數據與遠程系統的同步。

緩存產品

實現自己的緩存系統並不難弄,取決於是否需要深度定製。如果沒有必要自己實現緩存系統,可以用已經現成的緩存產品。比如:

我不知道這些產品是否能夠滿足需要,但是我知道他們用的比較廣泛。


個人主頁:https://www.howardliu.cn
個人博文:軟件架構-緩存技術

公衆號:看山的小屋

原創文章,轉載請註明: 轉載自併發編程網 – ifeve.com本文鏈接地址: 《軟件架構》緩存技術

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