本篇主要介紹下樓主平常專案中,快取使用經驗和遇到過的問題。
基本寫法
快取雪崩
全域性鎖,例項鎖
字串鎖
快取穿透
再談快取雪崩
總結為了方便演示,這裡使用runtime.cache做快取容器,並定義個簡單操作類。如下:
public class cachehelperpublic static void add(string cachekey, object obj, int cacheminute)
}
簡單讀取:
public object getmembersignindays1()
在專案中,有不少這樣寫法,這樣寫並沒有錯,但在併發量上來後就容易出問題。
快取雪崩是由於快取失效(過期),新快取未到期間。
這個中間時間內,所有請求都去查詢資料庫,而對資料庫cpu和記憶體造成巨大壓力,前端連線數不夠、查詢阻塞。
這個中間時間並沒有那麼短,比如sql查詢1秒,加上傳輸解析0.5秒。 就是說1.5秒內所有使用者查詢,都是直接查詢資料庫的。
碰到這種情況,使用最多的解決方案就是加鎖排隊。
publicstatic
object obj1 = new
object
();
public
object
getmembersignindays2()
lock (this
)
return
cachevalue;
}
第一種:
lock (obj1)
是全域性鎖可以滿足,但要為每個函式都宣告乙個obj,不然在a、b函式都鎖obj1時,必然會讓其中乙個阻塞。
第二種:lock (this) 這個鎖當前例項,對其他例項無效,那這個鎖就沒什麼效果了,當然使用單例模式的物件可以鎖。
在當前例項中:a函式鎖當前例項,其他也鎖當前例項的函式的讀寫,也被阻塞,這種做法也
不可取。
既然鎖物件不行,利用字串的特性,直接鎖快取的key呢
publicobject
getmembersignindays3()
lock
(lockkey)
return
cachevalue;
}
第一種:lock (cachename)
有問題,因為字串也是共享的,會阻塞其他使用這個字串的操作行為。
具體請參考之前的博文 c#語言-多執行緒中的鎖系統(一)。
因為字串被公共語言執行庫 (clr)暫留,這意味著整個程式中任何給定字串都只有乙個例項,所以才會用下面第二種方法。
第二種:lock (lockkey) 可以滿足。其目的就是為了保證鎖的粒度最小並且全域性唯一性,
只鎖當前快取的查詢行為。
先舉個簡單例子:一般**經常會快取使用者搜尋的結果,如果資料庫查詢不到,是不會做快取的。但如果頻繁查這個空關鍵字,會導致每次請求都直接查詢資料庫了。
例子就是快取穿透,請求繞過快取直接查資料庫,這也是經常提的快取命中率問題。
publicobject
getmembersignindays4()
if (cachevalue == null
)
cachehelper.add(cachekey, cachevalue, cachetime);
}return
cachevalue;
}
如果把查詢不到的空結果,也給快取起來,這樣下次同樣的請求就可以直接返回null了,即可以避免當查詢的值為空時引起的快取穿透。
可以單獨設定個快取區域儲存空值,對要查詢的key進行預先校驗,然後再放行給後面的正常快取處理邏輯。
前面不是用加鎖排隊方式就解決了嗎?其實加鎖排隊只是為了減輕資料庫的壓力,本質上並沒有提高系統吞吐量。
假設在高併發下,快取重建期間key是鎖著的,這是過來1000個請求999個都在阻塞的。導致的結果是使用者等待超時,這是非常不優化的體驗。
這種行為本質上是把多執行緒的web伺服器,在此時給變成單執行緒處理了,會導致大量的阻塞。對於系統資源也是一種浪費,因快取重建而阻塞的執行緒本可以處理更多請求的。
這裡提出一種解決方案是:
publicobject
getmembersignindays5());}
return
cachevalue;
}
從**中看出,我們多使用了乙個快取標記key,並使用雙檢鎖校驗保證後面邏輯不會多次執行。
快取標記key: 快取標記key只是乙個記錄實際key過期時間的標記,它的快取值可以是任意值,比如1。 它主要用來在實際key過期後,觸發通知另外的執行緒在後台去更新實際key的快取。
實際key: 它的過期時間會延長1倍,例:本來5分鐘,現在設定為10分鐘。 這樣做的目的是,當快取標記key過期後,實際快取還能以髒資料返回給呼叫端,直到另外的執行緒在後台更新完成後,才會返回新快取。
關於實際key的過期時間延長1倍,還是2、3倍都是可以的。只要大於正常快取過期時間,並且能保證在延長的時間內足夠拉取資料即可。
還乙個好處就是,如果突然db掛了,髒資料的存在可以保證前端系統不會拿不到資料。
這樣做後,就可以一定程度上提高系統吞吐量。
文中說的阻塞其他函式指的是,併發情況下鎖同一物件,比如乙個函式鎖a物件,另外的函式就必須等待a物件的鎖釋放後才能再次進鎖。
關於更新快取,可以單開乙個執行緒去專門跑快取更新,圖方便的話扔執行緒池裡面即可。
實際專案中,快取層框架的封裝往往要複雜的多,如果併發量比較小,這樣寫反而會增加**的複雜度,具體要根據實際情況來取捨。
那些年我們一起追過的快取寫法(一)
那些年我們一起追過的快取寫法(二)
那些年我們一起追過的快取寫法(三)
那些年我們一起追過的快取寫法 三
上篇介紹了多級快取,本章詳細介紹下記憶體快取該如何設計。閱讀目錄 分析設計 o 1 lru實現 過期刪除策略 總結假設有個專案有比較高的併發量,要用到多級快取,如下 在實際設計乙個記憶體快取前,需要考慮的問題 2 記憶體容量的限制,需要控制快取數量。3 熱點資料更新不同,需要可配置單個key過期時間...
那些年我們一起追過的Shell Script
原本這是自己在幾個月前為公司的乙個分享活動寫的乙個投影片,今天趁大腦負荷比較小,把這個話題拿到blog上面來分享一下。從知道shell算起至今也就幾個年頭而已,如今勉強算是入門了。對某乙個新事物的掌握總是乙個循序漸近的過程,只是根據不一樣事物的特點,其學習的曲線也不盡一致。shell script算...
那些年我們一起追過的流量
前一段的熱播電影 那些年我們一起追過的女孩 很多人討論問什麼最後男女主角沒有在一起,這個麼仁者見仁,智者見智。我借這個東風,聊一聊那些年我們追過的流量,分析一下問什麼客戶不願意和自己 在一起。這些都是筆者以前做過的和看到的例子。1.關於程式設計客棧問答平台 搜搜問問 天涯問答等 以dtaye知道為例...