高性能服務器架構思路(二)——緩衝清理策略

版權聲明:本文由韓偉原創文章,轉載請註明出處: 
文章原文連接:https://www.qcloud.com/community/article/164程序員

來源:騰雲閣 https://www.qcloud.com/community算法

 

雖然使用緩存思想彷佛是一個很簡單的事情,可是緩存機制卻有一個核心的難點,就是——緩存清理。咱們所說的緩存,都是保存一些數據,可是這些數據每每是會變化的,咱們要針對這些變化,清理掉保存的「髒」數據,卻可能不是那麼容易。數據庫

首先咱們來看看最簡單的緩存數據——靜態數據。這種數據每每在程序的運行時是不會變化的,好比Web服務器內存中緩存的HTML文件數據,就是這種。事實上,全部的不是由外部用戶上傳的數據,都屬於這種「運行時靜態數據」。通常來講,咱們對這種數據,能夠採用兩種創建緩存的方法:一是程序一啓動,就一股腦把全部的靜態數據從文件或者數據庫讀入內存;二就是程序啓動的時候並不加載靜態數據,而是等有用戶訪問相關數據的時候,纔去加載,這也就是所謂lazy load的作法。第一種方法編程比較簡單,程序的內存啓動後就穩定了,不太容易出現內存漏洞(若是加載的緩存太多,程序在啓動後馬上會因內存不足而退出,比較容易發現問題);第二種方法程序啓動很快,但要對緩存佔用的空間有所限制或者規劃,不然若是要緩存的數據太多,可能會耗盡內存,致使在線服務中斷。編程

通常來講,靜態數據是不會「髒」的,由於沒有用戶會去寫緩存中的數據。可是在實際工做中,咱們的在線服務每每會須要「馬上」變動一些緩存數據。好比在門戶網站上發佈了一條新聞,咱們會但願馬上讓全部訪問的用戶都看到。按最簡單的作法,咱們通常只要重啓一下服務器進程,內存中的緩存就會消失了。對於靜態緩存的變化頻率很是低的業務,這樣是能夠的,可是若是是新聞網站,就不能每隔幾分鐘就重啓一下WEB服務器進程,這樣會影響大量在線用戶的訪問。常見的解決這類問題有兩種處理策略:瀏覽器

第一種是使用控制命令。簡單來講,就是在服務器進程上,開通一個實時的命令端口,咱們能夠經過網絡數據包(如UDP包),或者Linux系統信號(如kill SIGUSR2進程號)之類的手段,發送一個命令消息給服務器進程,讓進程開始清理緩存。這種清理可能執行的是最簡單的「所有清理」,也有的能夠細緻一點的,讓命令消息中帶有「想清理的數據ID」這樣的信息,好比咱們發送給WEB服務器的清理消息網絡包中會帶一個字符串URL,表示要清理哪個HTML文件的緩存。這種作法的好處是清理的操做很精準,能夠明確的控制清理的時間和數據。可是缺點就是比較繁瑣,手工去編寫發送這種命令很煩人,因此通常咱們會把清理緩存命令的工做,編寫到上傳靜態數據的工具當中,好比結合到網站的內容發佈系統中,一旦編輯提交了一篇新的新聞,發佈系統的程序就自動的發送一個清理消息給WEB服務器。緩存

第二種是使用字段判斷邏輯。也就是服務器進程,會在每次讀取緩存前,根據一些特徵數據,快速的判斷內存中的緩存和源數據內容,是否有不一致(是否髒)的地方,若是有不一致的地方,就自動清理這條數據的緩存。這種作法會消耗一部分CPU,可是就不須要人工去處理清理緩存的事情,自動化程度很高。如今咱們的瀏覽器和WEB服務器之間,就有用這種機制:檢查文件MD5;或者檢查文件最後更新時間。具體的作法,就是每次瀏覽器發起對WEB服務器的請求時,除了發送URL給服務器外,還會發送一個緩存了此URL對應的文件內容的MD5校驗串、或者是此文件在服務器上的「最後更新時間」(這個校驗串和「最後更新時間」是第一次獲的文件時一併從服務器得到的);服務器收到以後,就會把MD5校驗串或者最後更新時間,和磁盤上的目標文件進行對比,若是是一致的,說明這個文件沒有被修改過(緩存不是「髒」的),能夠直接使用緩存。不然就會讀取目標文件返回新的內容給瀏覽器。這種作法對於服務器性能是有必定消耗的,因此若是每每咱們還會搭配其餘的緩存清理機制來用,好比咱們會在設置一個「超時檢查」的機制:就是對於全部的緩存清理檢查,咱們都簡單的看看緩存存在的時間是否「超時」了,若是超過了,才進行下一步的檢查,這樣就不用每次請求都去算MD5或者看最後更新時間了。可是這樣就存在「超時」時間內緩存變髒的可能性。服務器


WEB服務器靜態緩存例子網絡

上面說了運行時靜態的緩存清理,如今說說運行時變化的緩存數據。在服務器程序運行期間,若是用戶和服務器之間的交互,致使了緩存的數據產生了變化,就是所謂「運行時變化緩存」。好比咱們玩網絡遊戲,登陸以後的角色數據就會從數據庫裏讀出來,進入服務器的緩存(多是堆內存或者memcached、共享內存),在咱們不斷進行遊戲操做的時候,對應的角色數據就會產生修改的操做,這種緩存數據就是「運行時變化的緩存」。這種運行時變化的數據,有讀和寫兩個方面的清理問題:因爲緩存的數據會變化,若是另一個進程從數據庫讀你的角色數據,就會發現和當前遊戲裏的數據不一致;若是服務器進程忽然結束了,你在遊戲裏升級,或者撿道具的數據可能會從內存緩存中消失,致使你白忙活了半天,這就是沒有回寫(緩存寫操做的清理)致使的問題。這種狀況在電子商務領域也很常見,最典型的就是火車票網上購買的系統,火車票數據緩存在內存必須有合適的清理機制,不然讓兩個買了同一張票就麻煩了,但若是不緩存,大量用戶同時搶票,服務器也應對不過來。所以在運行時變化的數據緩存,應該有一些特別的緩存清理策略。memcached

在實際運行業務中,運行變化的數據每每是根據使用用戶的增多而增多的,所以首先要考慮的問題,就是緩存空間不夠的可能性。咱們不太可能把所有數據都放到緩存的空間裏,也不可能清理緩存的時候就所有數據一塊兒清理,因此咱們通常要對數據進行分割,這種分割的策略常見的有兩種:一種是按重要級來分割,一種是按使用部分分割。工具

先舉例說說「按重要級分割」,在網絡遊戲中,一樣是角色的數據,有些數據的變化可能會每次修改都馬上回寫到數據庫(清理寫緩存),其餘一些數據的變化會延遲一段時間,甚至有些數據直到角色退出遊戲纔回寫,如玩家的等級變化(升級了),武器裝備的得到和消耗,這些玩家很是看重的數據,基本上會馬上回寫,這些就是所謂最重要的緩存數據。而玩家的經驗值變化、當前HP、MP的變化,就會延遲一段時間才寫,由於就算丟失了緩存,玩家也不會太過關注。最後有些好比玩家在房間(地區)裏的X/Y座標,對話聊天的記錄,可能會退出時回寫,甚至不回寫。這個例子說的是「寫緩存」的清理,下面說說「讀緩存」的按重要級分割清理。

假如咱們寫一個網店系統,裏面容納了不少產品,這些產品有一些會被用戶頻繁檢索到,比較熱銷,而另一些商品則沒那麼熱銷。熱銷的商品的餘額、銷量、評價都會比較頻繁的變化,而滯銷的商品則變化不多。因此咱們在設計的時候,就應該按照不一樣商品的訪問頻繁程度,來決定緩存哪些商品的數據。咱們在設計緩存的結構時,就應該構建一個能夠統計緩存讀寫次數的指標,若是有些數據的讀寫頻率太低,或者空閒(沒有人讀、寫緩存)時間超長,緩存應該主動清理掉這些數據,以便其餘新的數據能進入緩存。這種策略也叫作「冷熱交換」策略。實現「冷熱交換」的策略時,關鍵是要定義一個合理的冷熱統計算法。一些固定的指標和算法,每每並不能很好的應對不一樣硬件、不一樣網絡狀況下的變化,因此如今人們廣泛會用一些動態的算法,如Redis就採用了5種,他們是:

  1. 根據過時時間,清理最長時間沒用過的

  2. 根據過時時間,清理即將過時的

  3. 根據過時時間,任意清理一個

  4. 不管是否過時,隨機清理

  5. 不管是否過時,根據LRU原則清理:所謂LRU,就是Least Recently Used,最近最久未使用過。這個原則的思想是:若是一個數據在最近一段時間沒有被訪問到,那麼在未來他被訪問的可能性也很小。LRU是在操做系統中很常見的一種原則,好比內存的頁面置換算法(也包括FIFO,LFU等),對於LRU的實現,仍是很是有技巧的,可是本文就不詳細去說明如何實現,留待你們上網搜索「LRU」關鍵字學習。

數據緩存的清理策略其實遠不止上面所說的這些,要用好緩存這個武器,就要仔細研究須要緩存的數據特徵,他們的讀寫分佈,數據之中的差異。而後最大化的利用業務領域的知識,來設計最合理的緩存清理策略。這個世界上不存在萬能的優化緩存清理策略,只存在針對業務領域最優化的策略,這須要咱們程序員深刻理解業務領域,去發現數據背後的規律。

相關文章
相關標籤/搜索