緩存是一個常談常新的話題,做爲一名服務端的技術,若是你入行一年都還沒用過memcached類產品,那隻能說你的公司實在過小了,或者你乾的活實在太邊緣了。html
提及緩存,可能你們最直接想到的就是:「在數據庫前面擋一層」。這是緩存最原始的意義,同時也引伸出了緩存最廣泛的用法。數據庫
//從緩存中獲取數據[較快的方式] data = getfromcache(id) if data == null then //從數據庫中獲取數據[較慢的方式] data = getfromdb(id) //緩存1天 setintocache(id, data, 86400) return data end return data
上面這種狀況下,當同時有N個請求到達,都同時執行getfromcache,那麼都會發現data在緩存中不存在,而後都會去調用getfromdb,以及setintocache。這是沒必要要的,那麼咱們有沒有辦法減小這些併發呢。json
最直接的想法是加鎖,當進入if條件中時,加一把鎖,讓其餘進程再也不執行下面的邏輯,而是等第一個進程的setintocache執行完成後,再從新執行一次getfromcache。緩存
那這個鎖如何加呢?這裏推薦一種省時省力的方法。經過直接在緩存value中設置過時時間來實現。安全
好比緩存的value值爲data,那咱們修改一下,把它放到一個json中,改爲併發
{data:data,atime:1429618765}
咱們增長了一個atime來記錄緩存生成的時間。而邏輯就變成下面這樣。memcached
//從緩存中獲取數據[較快的方式] data = getfromcache(id) data = json.decode(data) //若是經過檢查緩存生成時間,發現緩存已通過於陳舊,那麼就將緩存過時時間設置爲如今開始的5分鐘之後(這樣其餘併發進程就會覺得此緩存還未過時,還會繼續使用5分鐘,只讓當前這一個請求去重建緩存) if data != null && data.atime+86400 < now then data.atime = now+300-86400 data = json.encode(data) //對真正的cache來講,緩存10天或者更長時間 setintocache(id, data, 864000) //這裏把data設置成null是爲了走到下面的if中去重建緩存 data = null end if data == null then //從數據庫中獲取數據[較慢的方式] data = getfromdb(id) data = {data:data, atime:now} data = json.encode(data) //對真正的cache來講,緩存10天或者更長時間 setintocache(id, data, 864000) return data end return data
你能夠會發現,這裏也會存在併發啊,和上面例1同樣,第一個getfromcache到setintocache之間,若是同時有N個請求到來,不仍是都會執行這段操做,都會去查庫嗎。調試
沒錯,是這樣的。可是咱們仔細看一下,例1中,從getfromcache到setintocache之間,經歷了一次漫長的getfromdb操做,這個時間耗費多是上百毫秒的。而咱們例2中,並無進行什麼操做,這個時間耗費只在毫秒甚至微秒級的。code
因此例1中getfromcache到setintocache之間的併發是遠大於例2中的。例2中經過減少時間窗口,有效的模擬了鎖機制。同時尚未加強額外的存儲複雜度。因此是推薦的一種方式。htm
能夠說,咱們全部的緩存都應該是例2的方式,他在各方面都優於例1(多保存的一個atime字段耗費的內存基本能夠忽略不計。且atime不少時候對於調試程序還頗有用)。
那這樣就夠了嗎?對於被動過時型的緩存,這樣基本就能夠了。可是現實中還有一種緩存,是主動更新的。試想有一種緩存,咱們要求必須和數據庫中的數據一致,不能出現陳舊數據。那麼上面的緩存方式就不合適了。
咱們必然會添加一個流程:即當數據庫有更新時,同時更新緩存,由於緩存會本身重建,也能夠修改成當數據庫有更新時,同時刪除緩存。
這裏提到刪除或者更新緩存,就有點意思了。咱們上面講到的都是很是簡單的緩存,即一個id對應一個key。那麼試想,若是咱們有一個分頁緩存,緩存了某一個文章最新的前10頁數據。分別的key是page_1,page_2...page10。
那麼當咱們有一條新數據產生,這10頁就都失效了,須要更新或者刪除10次。這顯然是不太科學的作法。
那麼咱們應該怎麼作呢。咱們能夠借用上面例2中的方法,例2中,咱們在緩存中增長了一個atime字段,標識爲緩存的生成時間。咱們既然知道緩存何時生成的,那問題就好解決了。咱們在每次有新數據產生時,都去更新一個updatetime字段。而後獲取分頁緩存的時候,看一下這個updatetime字段是否是在atime以後,若是是,那麼說明這份緩存太舊了,須要走更新流程。
//從緩存中獲取數據[較快的方式][這裏的兩次get普通的緩存系統都支持一個請求完成] data = getfromcache(id) updatetime = getupdatetime(id) data = json.decode(data) //若是經過檢查緩存生成時間,發現緩存已通過於陳舊,那麼就將緩存過時時間設置爲如今開始的5分鐘之後(這樣其餘併發進程就會覺得此緩存還未過時,還會繼續使用5分鐘,只讓當前這一個請求去重建緩存) if data != null && (data.atime+86400 < now || date.atime < updatetime) then data.atime = now+300-86400 data = json.encode(data) //對真正的cache來講,緩存10天或者更長時間 setintocache(id, data, 864000) //這裏把data設置成null是爲了走到下面的if中去重建緩存 data = null end if data == null then //從數據庫中獲取數據[較慢的方式] data = getfromdb(id) data = {data:data, atime:now} data = json.encode(data) //對真正的cache來講,緩存10天或者更長時間 setintocache(id, data, 864000) return data end return data
這僅僅是在代碼示例2的基礎上增長了下面這一個條件判斷而已
date.atime < updatetime
這樣,不管是緩存保存時間過時了,仍是緩存自己有更新,都會觸發帶鎖機制的緩存更新。
好了,先說到這裏,回頭有想起來的再作更新。原文地址
順便插播一則招聘廣告。(碼字不易,求別刪招聘廣告,謝!)
易手機座標深圳,作一款易用安全的老年智能手機,作老年手機第一品牌。如今灰常須要服務端同窗入夥。有興趣的同窗請私信或簡歷發郵箱:ligang#pingyijinren.com