一般來說,網站隨著訪問量以及資料庫的增大,訪問速度將會越來越慢,如何優化這個響應速度,增大使用者支援容量是網站從小到中,到大的必經之路。
你也可能聽說過對於大型web站點一般嚴重依賴於cache來彈性放大其基礎設施的能力。雖然已知大型網站都使用了cache,但是很少有比較嚴謹的探討性文章來說說他們是如何來做這個cache的。其中的原因可能有幾個,一方面很多cache的策略都是特定於特定網站的,很難泛化成理論。另一方面可能過於複雜,並無放之四海而皆準的策略存在,特別是cache的過時更新問題到現在為止應該還沒有一個非常好的解決方案。
本文介紹幾種應用廣泛的快取策略,如果使用得當,應該可以保證永遠不會有過期資料訪問的問題存在。其中第一個策略叫做:write-through策略,第二種叫做:generational caching.
writethrough是最簡單有效的策略。當你向db寫的時候,你就順便向cache更新相應key的資料。這樣的好處是,任何隨後的請求總是會擊中cache,而不用再打到db上返回資料。只有一種情況會打到db: cache記憶體已經佔滿,而你訪問的key的value已經被清場。該策略也有一些需要注意的地方:
一般人們都會使用object id作為cache的key,但是不同的表,其id可能相同,因此在構造cache key時需要注意這一點,嚴格定義並且遵循一個統一的key定義規範就很重要了,比如User/17
另外,任何put/delete操作時,首先得確認該操作是否成功執行了,只有確認成功執行了,你才能去更新cache,
否額,可能就會出現db並未更新,但是cache卻被更改的情況,從而db和cache就不再一致!
雖然這個策略對單個的object做cache非常有效,但是大部分應用會從db中拉取多個物件,比如(返回所有屬於張三的書籍).對於操作包含多個物件的資料,我們來談談另一種策略"generational caching"
設想你在設計一個blog app,可能需要一個簡單的posts表格包含每一個Post的資訊,比如title, createdat等。一般部落格型別網站需要有兩種頁面,一個是單部落格頁面,一個是部落格聚類頁面。比如posts/3和posts/兩個頁面。問題就來了,我們如何有效地對posts/這個頁面的資料做好cache呢?
對單部落格頁面的caching前面介紹的策略非常適合:write through即可,每一個post都將對映為一個cache key(一般都是基於object type和object id,比如Post/3),而每次對一個post的更新都將write-through回cache中,保持資料的一致性。
更困難的問題是:我們如何處理部落格聚類列表頁面,比如home page或者category page的資料快取?為了保證db和cache的一致性,非常重要的一點是:任何時候一個Post被update,那麼所有包含該Post引用的keys都必須expired掉。而這是並不容易的事情,因為一個post可能在多個cache key中被引用,比如: latest ten posts, posts in php category, posts favorited by user 15等。雖然理論上你可以通過寫程式碼來找到包含已更新post的所有cache key,並且手工去expire掉他,但是必須指出這個過程是非常費事費力也非常容易錯誤的!我們推薦另外一種思路,這個思路就是genrational caching.
這種generational caching對每種型別的object去維護一個"generation" value。每次一個object被更新,則該generation value就會被增加。依然使用post作為例子,任何時候有人更新了一個post object,我們就增加the post generation object as well. 然後,任何時候我們從cache中讀或者寫一組boject時,我們就包含這個generation key.
通過包含這個generation value在cache key中,任何時候一個Post被更新,那麼接下來的訪問就不會擊中cache,因此強制回源db獲取資料。這種策略的一個結果是:任何時候一個post object被更新或者刪除,所有包含多個posts的keys都會隱含expired掉。之所以說是隱含,因為我們永遠不會主動去刪除這些objects,僅僅通過增加這個generation value,我們就能保證所有old keys永遠不會被訪問,下面的事情就留給cache系統本身的垃圾回收機制去處理。
在多個實際application中應用該策略後,對於cache的效能提高有以下幾點認識:總的來說該策略可以大大提高應用的效能減少資料庫的負載。可以節約大量的表格掃描等高密度計算能力要求。同時由於減少了對資料庫的請求,其他必須訪問資料庫的請求也能大大加快效能。
為了保持cache的一致,這個策略相對有些保守,也就是說有可能部分不該過期的cache entry也被強制過期了。比如,如果你更新了一個特定類目下的一個Post,本策略會將所有類目的key都過期。而這看起來會有些低效率和過優化嫌疑。而我也發現大多數應用是read-heavy(重讀請寫)的因此這種過優化的快取策略本身不會帶來大的問題。相反,如果不適用這種略顯“過優化”的簡易策略,那麼我們的程式碼實現完全是和應用,model繫結在一起的,是非常難以維護的。
我前面提過在這個策略中任何model都不會被顯式地從cache中刪除。這也隱含著和caching tool以及庫滿驅趕策略有關。一般這個cache策略會使用LRU(least recent used) eviction policy來配合使用。一個LRU策略會對那些老的keys優先從記憶體中移除。