緩存是優化系統性能最常用的方式之一,通過在耗時部件(如數據庫)之前添加緩存,可以減少實際調用次數,降低響應時間。但是在引入緩存之前,務必三思而後行。本文通過一些引入緩存時的常見錯誤,對如何用好緩存提供了一些建議。
常見錯誤
啟動時緩存
有時候,我們會發現應用程序啟動很慢,最終發現是其中一個依賴的服務響應時間很長,這時該怎麼辦?
通常來說,遇到這類問題,說明這個依賴服務無法滿足需求。如果這是一個第三方服務,控制權不在自己手上,這時我們可能會引入緩存。
此時引入緩存的問題,是緩存失效策略難以生效,因為緩存設計的本意就是盡可能少的請求依賴的服務。
過早緩存
這裡提到“早”,不是應用程序的生命周期,而是開發的周期。有的時候我們會看見,一些開發者在開發初期就已經估算出系統瓶頸,並引入緩存。
事實上,這樣的做法掩蓋了可能進行性能優化的點。反正到時候這個服務的返回值會被緩存住,我干嘛還要花時間去優化這部分代碼呢?
集成緩存
SOLID原則中的“S”代表——單一功能原則(Single responsibility principle)。當應用程序集成緩存模塊之後,緩存模塊和服務層就有了強耦合,無法在沒有緩存模塊的參與下單獨運行。
緩存所有內容
有的時候為了降低響應延遲,可能會盲目的對外部調用都加上緩存。事實上,這樣的行為很容易讓開發者和維護者無法意識到緩存模塊的存在,最終對底層依賴模塊的可靠性做出了錯誤的評估。
級聯緩存
緩存所有內容,或者只是緩存了大部分內容,可能會導致緩存數據中包含其他緩存數據。
如果應用程序中包含這種級聯的緩存結構,可能導致的情況是緩存失效時間不可控。最上層的緩存需要等每一級緩存都失效更新之後,最終返回的數據才會徹底更新。
不可刷新緩存
通常情況下,緩存中間件會提供一個刷新緩存的工具。例如Redis,維護人員可以通過其提供的工具,刪除部分數據,甚至刷新整個緩存。
但是,一些臨時緩存,可能不會包含這樣的工具。例如簡單的將數據保存在內容中的緩存,通常不會允許外部工具來修改或者刪除緩存內容。這時,如果發現緩存數據異常,維護人員只能采取重啟服務的方式,這將大大增加運維成本和響應時間。更有甚者,一些緩存可能會將緩存內容寫在文件系統中進行備份。此時除了重啟服務,還需要確保應用程序啟動之前刪除文件系統上的緩存備份。
緩存帶來的影響
上面提到了引入緩存可能導致的常見錯誤,這些問題在無緩存系統中通過不會考慮。
部署一個重度依賴緩存的系統,可能會因為等待緩存失效而花費大量時間。例如通過CDN緩存內容,系統發布之後去刷新CDN配置、CDN緩存的內容,可能需要幾個小時。
另外,出現性能瓶頸優先考慮緩存,會導致性能問題被掩蓋,得不到真正的解決。事實上,很多時候調優代碼花費的時間,和引入緩存組件不會相差太多。
最後,對於包含緩存組件的系統,調試成本會大大增加。經常會發生追蹤半天代碼,結果數據來自緩存,和實際邏輯上應該依賴的組件沒有任何關系。同樣的問題也可能出現在執行了所有相關測試用例之後,修改到的代碼實際沒有被測試到。
如何用好緩存?
放棄緩存!
好吧,很多時候緩存是無法避免的。基於互聯網的系統,很難完全避免使用緩存,甚至連http協議頭,都包含緩存配置:Cache-Control: max-age=xxx
。
了解數據
如果要將數據訪問緩存,首先需要了解數據更新策略。只有明確了解數據何時需要更新,才能通過If-Modified-Since
頭來判斷客戶端請求的數據是否需要更新,是簡單返回304 Not Modified
響應讓客戶端復用之前的本地緩存數據,還是返回最新數據。另外,為了更好利用http協議中的緩存,建議給數據區分版本,或者利用eTag來標記緩存數據的版本。
優化性能而不是使用緩存
前文提到過,使用緩存往往會將潛在性能問題掩蓋。盡可能利用性能分析工具,找到應用程序響應緩慢的真實原因並且修復它。例如減少無效代碼調用,根據SQL執行計劃優化SQL等。
總結
緩存是非常有用的工具,但極易被濫用。不到最後一刻不要使用緩存,優先考慮使用其他方式優化應用程序性能。
如果讀者還需要其他因為緩存引起的問題,請在下方留言,以便將這些問題添加到列表中。