Redis:MySQL算老幾?
前言:上一篇《MySQL:緩存算什么東西?》里挖了一個坑,也有很多人說沒看過癮,今天接著寫,把坑填上,不過得把視角換一下,讓Redis上臺發(fā)言。
我知道MySQL看我不順眼,不就是他的好基友Tomcat不怎么搭理他了嗎? 這能怪我? 誰讓他那么慢?
張大胖把我Redis安排到這個系統(tǒng)中來,那就是為了提升系統(tǒng)的響應(yīng)速度,我把數(shù)據(jù)都暫時放到了內(nèi)存中,每當(dāng)Tomcat需要的時候直接拿走就是了,都不用聯(lián)系MySQL。只有我這里沒有數(shù)據(jù)的時候Tomcat才會給MySQL說一句:“哥們,把這個SQL執(zhí)行一下啊,把數(shù)據(jù)告訴我!”
MySQL不死心,不斷使壞,總想著把我給干掉,恢復(fù)他昔日的榮耀和地位。可歷史的車輪滾滾向前,想逆潮流而動,無異于螳臂擋車啊!
有時候我真想把我緩存中的數(shù)據(jù)刪除,讓高并發(fā)的訪問都壓到他那里去, 累死他! 可一想到自己的職業(yè)道德,尤其是張大胖那可憐樣,還是忍了吧。
黑客攻擊?
這一天中午,Tomcat發(fā)現(xiàn)流量有些異常,之前大部分的數(shù)據(jù)我都可以處理,這一次大量的請求在我Redis這里竟然獲取不到數(shù)據(jù)! Tomcat被迫向MySQL求援:“哥們,這兒有一個SQL啊, 這兒還有一個, 又來一個......”
MySQL剛開始非常高興,滿心歡喜地去執(zhí)行,可是他很快就發(fā)現(xiàn)事情不對, 執(zhí)行完這些SQL,在數(shù)據(jù)庫中也查不到數(shù)據(jù)。他不滿地對Tomcat說:“兄弟,你這是在折騰我嗎? 你看看你這個SQL中where ID = xxxx, 這些ID在數(shù)據(jù)庫都不存在嘛。”
Tomcat頭也不抬:“又來一個SQL, 還有一個......”
讓我比較佩服的是, MySQL還是比較職業(yè)的,盡管有怨氣,他還是不折不扣地執(zhí)行,很快他就累到了。
整個系統(tǒng)慢如蝸牛,連正常的請求也處理不了。
張大胖趕緊介入,經(jīng)過一番調(diào)查,他發(fā)現(xiàn)很多請求故意去查詢那些一定不存在的數(shù)據(jù),緩存中肯定沒有,于是請求一定會發(fā)到MySQL去執(zhí)行,在流量大時,MySQL就掛掉了。
換句話說:在黑客的精心算計之下,我這個緩存成了擺設(shè),緩存被穿透了!
張大胖把此事定性為黑客攻擊。
緩存空值
這一次,MySQL終于意識到了我的價值,他出了一個主意:“Redis同學(xué),你把那些不存在的key和對應(yīng)的空值也緩存下來不就行了?下次訪問,就直接返回一個null給這些黑客,別再來找我了。”
我一聽就知道這是個餿主意:“這些key在你那里都不存在,還讓我緩存,那不就是要浪費(fèi)我的空間嗎? 張大胖給我分配的空間是有限的啊。”
“你不是可以設(shè)定數(shù)據(jù)的有效期嘛,比如過3分鐘就過期,刪除它,空間不就騰出來了。”
“那在這三分鐘內(nèi),如果這個key對應(yīng)的數(shù)據(jù)真的被加入到了你MySQL當(dāng)中,那豈不就不一致了?!” 我問道。
MySQL說道:“如果發(fā)生這種情況,就可以想辦法清除掉緩存中的數(shù)據(jù),只是程序邏輯就變得復(fù)雜了......”
“退一步來說,假設(shè)我緩存了他們,那黑客完全可以換一些新的key來攻擊啊!緩存中還是沒有,還得去你那里查,這個辦法不妥!” 我下了結(jié)論。
布隆過濾器
MySQL說:“如果能事先得知這個key是不是在數(shù)據(jù)庫存在就好了,可是想知道是否存在,那就得把所有的key都放到緩存中,Redis,你能受得了嗎?”
我當(dāng)然受不了。
Tomcat眼前一亮:“你們聽說過布隆過濾器沒有?”
我說:“當(dāng)然知道了,這是個神奇的數(shù)據(jù)結(jié)構(gòu),只需要極少的空間就可以判斷一個元素是不是在一個集合之內(nèi),這正好是我們所需要的場景?。号袛鄈ey是否存在。”
(碼農(nóng)翻身注:布隆過濾器大家可以參考相關(guān)資料,這里不再展開。)
Tomcat說:“對,比如我們可以把所有的用戶ID建立一個布隆過濾器,這樣當(dāng)那些黑客的請求過來以后,先用這個過濾器攔截一下,如果黑客要訪問的用戶ID不在這個過濾器中,我們就直接把他踢出去了。”
MySQL也是經(jīng)驗(yàn)豐富:“可是這個Bloom Filter有誤報啊,即使某個用戶ID不在集合中,他也可能報告說在集合中。這個時候Tomcat就認(rèn)為這是一個合法的用戶ID,就去Redis中查,不存在,然后到我這里查,還是不存在。”
我說:“哎呀,一定的誤報也是允許的,沒有完美的事情,總要付出代價不是?”
大家都表示同意。
數(shù)據(jù)失效
黑客的攻擊的威脅解除了,日子又恢復(fù)了平靜,MySQL意識到了我的價值,也不再嘮嘮叨叨了。
我這個緩存的容量是有限的,不可能無限制地增加,所以張大胖添加到緩存的數(shù)據(jù)有一個有效期,過了有效期,我就會把他刪除,騰出空間,讓別的數(shù)據(jù)使用。
如果是普通的緩存數(shù)據(jù)失效,那就罷了,大不了從數(shù)據(jù)庫中再去一次就是了。
可是這一次,有個超級熱門的數(shù)據(jù)失效了,Tomcat組成的集群中有無數(shù)的線程都問我要數(shù)據(jù),當(dāng)我告訴他們這個數(shù)據(jù)已經(jīng)失效以后,他們扭頭便轉(zhuǎn)向MySQL,瘋狂地發(fā)出SQL語句,問MySQL要數(shù)據(jù)。
MySQL傻眼了,這么多的線程,每個要發(fā)出的SQL都是相同的,可是又不得不執(zhí)行。
MySQL又一次累倒了,我想他再次體會到了我的重要性。
他對Tomcat說道:“兄弟,給我發(fā)這么多的一模一樣的SQL,你想累死我啊! 你就不能控制一下?只讓一個線程發(fā)查詢過來,讓其他的等待一下? 那個線程取到數(shù)據(jù)以后,其他線程就可以從緩存取了!”
Tomcat覺得很有道理,可是現(xiàn)在系統(tǒng)中有多個Tomcat,每個都是平等的,怎么去選出那個唯一的線程呢?
如果是在同一個JVM中還好辦,輕輕松松用一把進(jìn)程內(nèi)的鎖搞定, 可是這分布式的Tomcat,每個都是一個JVM,每個都是一個進(jìn)程, 怎么搞?
我說:“這很簡單,我Redis這里可以提供一個分布式的鎖,誰獲得了這把鎖,誰就可以訪問數(shù)據(jù)庫。”
MySQL佩服地說:“老弟真是不錯,我服了你了,以后你一定要盡可能的把流量都給擋住,別往我這里發(fā)了,實(shí)在是太可怕了!”
Tomcat補(bǔ)充到:“是啊,這Redis緩存太重要了!”
突然間他注意到了我還只有一臺機(jī)器: “你現(xiàn)在怎么還是單臺機(jī)器? 一個實(shí)例? 萬一掛了怎么辦? 一定得像我一樣,搞集群,提高可用性啊!”
MySQL說:“啊? 這多嚇人,從今天開始,我將時時刻刻為你祈禱,上帝保佑,你千萬別掛掉。”
與此同時,張大胖開始著手Redis集群了......
【本文為51CTO專欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號coderising獲取授權(quán)】