高性能服务器架构(二):缓存清理策略
原文鏈接:https://mp.weixin.qq.com/s/OopSWbLrzT-V11VDZOpxJw
雖然使用緩存思想似乎是一個很簡單的事情,但是緩存機制卻有一個核心的難點,就是——緩存清理。我們所說的緩存,都是保存一些數據,但是這些數據往往是會變化的,我們要針對這些變化,清理掉保存的“臟”數據,卻可能不是那么容易。
首先我們來看看最簡單的緩存數據——靜態數據。這種數據往往在程序的運行時是不會變化的,比如Web服務器內存中緩存的HTML文件數據,就是這種。事實上,所有的不是由外部用戶上傳的數據,都屬于這種“運行時靜態數據”。一般來說,我們對這種數據,可以采用兩種建立緩存的方法:一是程序一啟動,就一股腦把所有的靜態數據從文件或者數據庫讀入內存;二就是程序啟動的時候并不加載靜態數據,而是等有用戶訪問相關數據的時候,才去加載,這也就是所謂lazy load的做法。第一種方法編程比較簡單,程序的內存啟動后就穩定了,不太容易出現內存漏洞(如果加載的緩存太多,程序在啟動后立刻會因內存不足而退出,比較容易發現問題);第二種方法程序啟動很快,但要對緩存占用的空間有所限制或者規劃,否則如果要緩存的數據太多,可能會耗盡內存,導致在線服務中斷。
一般來說,靜態數據是不會“臟”的,因為沒有用戶會去寫緩存中的數據。但是在實際工作中,我們的在線服務往往會需要“立刻”變更一些緩存數據。比如在門戶網站上發布了一條新聞,我們會希望立刻讓所有訪問的用戶都看到。按最簡單的做法,我們一般只要重啟一下服務器進程,內存中的緩存就會消失了。對于靜態緩存的變化頻率非常低的業務,這樣是可以的,但是如果是新聞網站,就不能每隔幾分鐘就重啟一下WEB服務器進程,這樣會影響大量在線用戶的訪問。常見的解決這類問題有兩種處理策略:
第一種是使用控制命令。簡單來說,就是在服務器進程上,開通一個實時的命令端口,我們可以通過網絡數據包(如UDP包),或者Linux系統信號(如kill SIGUSR2進程號)之類的手段,發送一個命令消息給服務器進程,讓進程開始清理緩存。這種清理可能執行的是最簡單的“全部清理”,也有的可以細致一點的,讓命令消息中帶有“想清理的數據ID”這樣的信息,比如我們發送給WEB服務器的清理消息網絡包中會帶一個字符串URL,表示要清理哪一個HTML文件的緩存。這種做法的好處是清理的操作很精準,可以明確的控制清理的時間和數據。但是缺點就是比較繁瑣,手工去編寫發送這種命令很煩人,所以一般我們會把清理緩存命令的工作,編寫到上傳靜態數據的工具當中,比如結合到網站的內容發布系統中,一旦編輯提交了一篇新的新聞,發布系統的程序就自動的發送一個清理消息給WEB服務器。
第二種是使用字段判斷邏輯。也就是服務器進程,會在每次讀取緩存前,根據一些特征數據,快速的判斷內存中的緩存和源數據內容,是否有不一致(是否臟)的地方,如果有不一致的地方,就自動清理這條數據的緩存。這種做法會消耗一部分CPU,但是就不需要人工去處理清理緩存的事情,自動化程度很高。現在我們的瀏覽器和WEB服務器之間,就有用這種機制:檢查文件MD5;或者檢查文件最后更新時間。具體的做法,就是每次瀏覽器發起對WEB服務器的請求時,除了發送URL給服務器外,還會發送一個緩存了此URL對應的文件內容的MD5校驗串、或者是此文件在服務器上的“最后更新時間”(這個校驗串和“最后更新時間”是第一次獲的文件時一并從服務器獲得的);服務器收到之后,就會把MD5校驗串或者最后更新時間,和磁盤上的目標文件進行對比,如果是一致的,說明這個文件沒有被修改過(緩存不是“臟”的),可以直接使用緩存。否則就會讀取目標文件返回新的內容給瀏覽器。這種做法對于服務器性能是有一定消耗的,所以如果往往我們還會搭配其他的緩存清理機制來用,比如我們會在設置一個“超時檢查”的機制:就是對于所有的緩存清理檢查,我們都簡單的看看緩存存在的時間是否“超時”了,如果超過了,才進行下一步的檢查,這樣就不用每次請求都去算MD5或者看最后更新時間了。但是這樣就存在“超時”時間內緩存變臟的可能性。
?
上面說了運行時靜態的緩存清理,現在說說運行時變化的緩存數據。在服務器程序運行期間,如果用戶和服務器之間的交互,導致了緩存的數據產生了變化,就是所謂“運行時變化緩存”。比如我們玩網絡游戲,登錄之后的角色數據就會從數據庫里讀出來,進入服務器的緩存(可能是堆內存或者memcached、共享內存),在我們不斷進行游戲操作的時候,對應的角色數據就會產生修改的操作,這種緩存數據就是“運行時變化的緩存”。這種運行時變化的數據,有讀和寫兩個方面的清理問題:由于緩存的數據會變化,如果另外一個進程從數據庫讀你的角色數據,就會發現和當前游戲里的數據不一致;如果服務器進程突然結束了,你在游戲里升級,或者撿道具的數據可能會從內存緩存中消失,導致你白忙活了半天,這就是沒有回寫(緩存寫操作的清理)導致的問題。這種情況在電子商務領域也很常見,最典型的就是火車票網上購買的系統,火車票數據緩存在內存必須有合適的清理機制,否則讓兩個買了同一張票就麻煩了,但如果不緩存,大量用戶同時搶票,服務器也應對不過來。因此在運行時變化的數據緩存,應該有一些特別的緩存清理策略。
在實際運行業務中,運行變化的數據往往是根據使用用戶的增多而增多的,因此首先要考慮的問題,就是緩存空間不夠的可能性。我們不太可能把全部數據都放到緩存的空間里,也不可能清理緩存的時候就全部數據一起清理,所以我們一般要對數據進行分割,這種分割的策略常見的有兩種:一種是按重要級來分割,一種是按使用部分分割。
先舉例說說“按重要級分割”,在網絡游戲中,同樣是角色的數據,有些數據的變化可能會每次修改都立刻回寫到數據庫(清理寫緩存),其他一些數據的變化會延遲一段時間,甚至有些數據直到角色退出游戲才回寫,如玩家的等級變化(升級了),武器裝備的獲得和消耗,這些玩家非常看重的數據,基本上會立刻回寫,這些就是所謂最重要的緩存數據。而玩家的經驗值變化、當前HP、MP的變化,就會延遲一段時間才寫,因為就算丟失了緩存,玩家也不會太過關注。最后有些比如玩家在房間(地區)里的X/Y坐標,對話聊天的記錄,可能會退出時回寫,甚至不回寫。這個例子說的是“寫緩存”的清理,下面說說“讀緩存”的按重要級分割清理。
?
? 假如我們寫一個網店系統,里面容納了很多產品,這些產品有一些會被用戶頻繁檢索到,比較熱銷,而另外一些商品則沒那么熱銷。熱銷的商品的余額、銷量、評價都會比較頻繁的變化,而滯銷的商品則變化很少。所以我們在設計的時候,就應該按照不同商品的訪問頻繁程度,來決定緩存哪些商品的數據。我們在設計緩存的結構時,就應該構建一個可以統計緩存讀寫次數的指標,如果有些數據的讀寫頻率過低,或者空閑(沒有人讀、寫緩存)時間超長,緩存應該主動清理掉這些數據,以便其他新的數據能進入緩存。這種策略也叫做“冷熱交換”策略。實現“冷熱交換”的策略時,關鍵是要定義一個合理的冷熱統計算法。一些固定的指標和算法,往往并不能很好的應對不同硬件、不同網絡情況下的變化,所以現在人們普遍會用一些動態的算法,如Redis就采用了5種,他們是:
1.根據過期時間,清理最長時間沒用過的
2.根據過期時間,清理即將過期的
3.根據過期時間,任意清理一個
4. 無論是否過期,隨機清理
5.無論是否過期,根據LRU原則清理:所謂LRU,就是Least Recently Used,最近最久未使用過。這個原則的思想是:如果一個數據在最近一段時間沒有被訪問到,那么在將來他被訪問的可能性也很小。LRU是在操作系統中很常見的一種原則,比如內存的頁面置換算法(也包括FIFO,LFU等),對于LRU的實現,還是非常有技巧的,但是本文就不詳細去說明如何實現,留待大家上網搜索“LRU”關鍵字學習。
?
數據緩存的清理策略其實遠不止上面所說的這些,要用好緩存這個武器,就要仔細研究需要緩存的數據特征,他們的讀寫分布,數據之中的差別。然后最大化的利用業務領域的知識,來設計最合理的緩存清理策略。這個世界上不存在萬能的優化緩存清理策略,只存在針對業務領域最優化的策略,這需要我們程序員深入理解業務領域,去發現數據背后的規律。
?
總結
以上是生活随笔為你收集整理的高性能服务器架构(二):缓存清理策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 上传项目到git
- 下一篇: Heroku创始人Adam Wiggin