引子
web頁面在服務器端進行渲染,需要根據模板和數據庫數據經過運算生成和渲染出最終的頁面,這些運算和渲染過程會耗費大量時間和資源,如果將這些運算結果緩存起來放在內存中,下次再請求頁面就會直接從緩存中直接讀取數據,節省了頁面渲染計算時間,從而大大提高了頁面的相應速度。目前內存價格相對便宜,通過緩存帶來性能方面的收益往往比單純從代碼層面優化性能,高效的和經濟實惠的多。
什麼是緩存
什麼是緩存,緩存其實也是一種代理,作為網站內容展示的代理,用戶直接訪問緩存內容,而緩存內容來源於後端程序計算, 緩存實際為一箇中間代理層。
緩存存在基礎和場景
緩存的存在基礎是內容的相對不變性,在相對的時間、相對的條件內容不發生變化,這樣不用每次都動態計算生成內容。內容變化的頻率相對不高,應用對內容實時性要求相對不高,是引入緩存的場景基礎。讀的操作遠大於寫是實際的表現。緩存不適合高交互性,對內容實時性要求高的場景。
緩存主要是解決變與不變的問題,變什麼時候變的問題,也就是緩存的同步問題。
緩存策略
緩存策略就是緩存如何生成和如何過期的規則定義。
緩存策略定義和業務場景有很大關係如內容展示類網站(新聞類)就會更適合緩存的使用場景。緩存也是種藝術,不同的場景會適合不同的緩存策略,有時候需要拿將幾種策略在實際應用中進行對比,通過實際觀測和相關數據統計(緩存命中率 緩存停留時間等)來決定哪種策略更適合。
下邊就具體介紹下集中常用的緩存策略
基於時間的緩存策略
通過設置cache過期時間來同步緩存內容,預定的時間到了緩存就會自動過期,優點是簡單,缺點是會有一定的時間數據不同步。
基於cache key的緩存策略
對於動態網站,網站頁面是基於後端數據源結合預定義模板動態渲染生成的,後端數據源一般為數據庫(關係型數據庫 非關係型數據庫),後端的數據變化帶來頁面內容的的變化,當後端數據發生變化時就會生成新的cache_key從而生成新的cache內容,老的cahe_key和對應的內容會駐留在緩存服務中,只有當緩存服務的空間滿了才會觸發緩存服務的驅逐操作,緩存才會被清除。
基於cache key的緩存策略的關鍵點就是如何檢測到數據的變化,如何構建cache key。頁面的數據源是基於數據庫的,可以利用數據庫的id和update time時間戳(id為數據唯一標識 update time時間戳為變化標識)組成cache key,數據的任何變化都會改變update time這個時間戳字段,從而生成新的key值,並根據key值重新緩存內容。像memcache和redis等緩存服務會有自己的驅逐策略,會按照最近最少使用的策略自動驅逐緩存,緩存驅逐並不是當緩存空間滿了以後才執行的,以memcache為例,他會根據緩存內容的大小分成不同的slab,當前slab空間滿的時候就會觸發驅逐,這就是為什麼你會發現緩存還有大量空間的時候就有cache被驅逐。
對於文檔行數據庫如elasticsearch等,提供了一個版本字段,當文檔數據有任何修改,版本字段都會進行改變(版本號遞增),可以利用文檔的id和版本字段來組成cache key
基於cache key的緩存策略是每次對象變化就重新寫入緩存,舊的緩存對象時在slab滿的情況下由緩存服務根據最近最少訪問策略來驅逐舊的緩存,這種策略很簡單將緩存的清理工作交給了緩存服務,缺點就是有場景會將一些正在使用的cache給驅逐了,反而當前緩存的舊版本的對象會長期滯留在緩存服務中從而引發緩存震盪(同一cache頻繁換入換出)。
網頁模板會隨著開發工作的進行而變動,當模板變化時需要重新生成cache。cache key的值的一部分是基於模板內容的degist md5值來計算的,只有當模板內容改變了,才會生成新的key和緩存內容。計算模板內容degist值也是需要耗費時間的,也可以將其緩存起來,將模板的名稱和路徑當做key來把degist值緩存起。在實際操作過程中,程序重新啟動時(新的relase發生)會檢測一次模板內容是否改變來決定是否生成新的模板內容degist。
實際應用中會結合數據庫數據值和模板digest值來生成cache key和cache content。
可回收的cache key緩存策略
簡單理解就是根據cache key在緩存服務中存儲對象,如果對象被修改就用新版本的緩存對象替換舊版本的對象,從而做到基於當前key的緩存對象空間循環利用。他的核心就是基於被緩存對象定義的cache_key和cache_version,
名詞解釋:cache_key: 為被緩存對象的唯一標識,如數據庫對象的idcache_vertion:保存對象的版本信息,追蹤對象的變化,如關係數據庫對象的update_at 時間戳,文檔型數據庫的vertion字段。被緩存的類:需要被緩存的對象。緩存類:對於緩存內容封裝
被緩存的類中需要同時定義cache_key方法和cache_vertion方法,構建一個緩存類來包裹被緩存的對象,設置一個字段為vertion用來存被緩存對象的vertion,一個字段為value存儲實際的對象,將這個緩存對象按照cache_key存入緩存服務中(memcahe redis),在讀取緩存對象時,先將緩存對象按照cache_key從緩存服務中取出,然後對比當前對象的vertion和緩存對象的vertion是否一致,如果一致就會直接讀取緩存對象中的value值,如果不一致就是對象的內容發生了變化從而緩存服務中的緩存對象就是過期緩存,需要根據新的vertion和內容重新構建緩存對象,然後根據cache_key存入緩存服務中。因為是同一個cache_key所以就等於根據cache_key把上一個老版本的緩存對象替換為新版本的緩存對象供讀取使用,這樣做的優點就是是節省緩存空間,提高緩存駐留時間,預防緩存震盪
動態更新cache內容
通過程序來手動控制緩存的更新,如通過數據庫變化調用回調(after save)來重新生成緩存。這樣做好處是可以實時改變緩存,讓緩存變得實時可控,缺點是大大增加了程序的複雜性。
總結
這些策略的實際使用。往往不是單一的,要根據具體情況,如業務場景、應用場景、程序邏輯、技術框架等結合使用,通過實際的工程試驗來實現最適合自己的緩存方案。
閱讀更多 簡Mark 的文章