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