MySQL:緩存算什么東西?!
十年前,我們還是一個企業內部的應用,用戶不多,數據也不多。
Tomcat一天也處理不了多少請求,閑得無聊的時候只能和我聊天,這是沒有辦法的事情,因為整個系統只有我們兩個:
沒錯,我就是大名鼎鼎的MySQL ,我和Tomcat位于不同的機器上,每次通信都是一次網絡的請求。
這樣的情況持續了三年,我們倆把話都快要說盡了的時候,人類終于送來了一個新家伙:緩存。
從外表看來,這個緩存就是一個Map而已, 保存的都是一些(key,value)這樣的東西。
從內部看,他還真是個Map,是那個叫做張大胖的人類寫的、一個線程安全的、可以設定過期時間的Map。
Tomcat和我都有點瞧不上他,覺得他實在是簡陋,甚至難以成為一個獨立的組件。
更讓Tomcat不爽的是,這個簡陋的家伙竟然和自己一起,共享JVM進程。
慢慢地事情發生了變化,張大胖改變了程序邏輯:在之前,用戶的請求發到Tomcat這里,如果需要訪問數據庫的數據, Tomcat直接就把SQL語句扔給我來執行。
現在, 先要到那個Map,不,是緩存中查一下,看看有沒有相關數據,如果有,直接就返回了,根本不用和我打交道; 如果緩存中沒有,那才發出SQL查詢,并且把緩存也給填充上,這樣下次就不用訪問數據庫了。
Tomcat整天和緩存打交道,聊得熱火朝天。 我觀察了幾天,終于明白這小子把我這個好基友給拋棄了。
Tomcat得意地對我說: “這緩存和我在一個進程中,訪問起來速度快得很,立刻就能返回數據,哪里像你MySQL,慢慢悠悠地執行半天?! ”
說完他又做了一個總結:進程內調用就是好啊。
其實吧,緩存這小子的本質我比誰都清楚,我內部就有緩存啊,就是為了避免頻繁地訪問硬盤, 大家利用的都是程序的局部性原理嘛,有什么神秘的?!
我耐心蟄伏,等待機會,準備一舉把這個不知好歹的Map干掉。
從進程內到進程外
過了幾個月,張大胖把系統的架構做了升級,為了應對高并發的訪問,他用一個nginx來搞負載均衡,分發用戶的請求,在后面搞了很多Tomcat和很多進程內的緩存,我們的系統變成了這個樣子:
我一看就意識到我的機會來了:這緩存之間很容易出現不一致啊。
比如: 用戶的請求在JVM 1 中進行處理,MySQL做了更新,JVM 1中相關的緩存也做了更新或者被刪除, 可是JVM 2和JVM 3中緩存的數據還是舊的啊。
不出我的所料,數據不一致的問題非常嚴重,用戶頻繁抱怨,緩存這小子這下要完蛋了!
可是緩存還想垂死掙扎,他說:”可以這樣嘛,如果一個JVM中的緩存發生了變化,就通知其他JVM。”
可是通知總會有延遲,如果JVM 1還沒來得及通知JVM 2和JVM 3, 而用戶的請求已經在這兩臺機器上開始處理了,數據不一致還是存在。
特別是各個JVM之間需要來回交互,緩存的更新需要你通知我,我通知你,麻煩得要死。
Tomcat出了一個餿主意:“別讓緩存互相更新,讓緩存定時從MySQL那里更新!”
可是既然是定時更新,那緩存中的數據和我這里在某些時間段內還是會出現不一致。
除非數據的變化頻率極低,否則這幾乎是個無解的問題。
終于,張大胖如我所愿, 把進程內緩存給刪除了!
我整打算好好跟Tomcat敘敘舊(這么多Tomcat啊!), 可是第二天他便弄來了一個新的家伙:Redis,還是緩存!
和之間那簡陋的Map相比,Redis可是強大得太多太多了,這個緩存獨自霸占了一臺機器,讓幾個Tomcat都可以共享訪問。
換句話說,緩存從進程內搬到了進程外!
我對Redis說:“你小子也需要網絡才能訪問了,和我差不多,有存在的必要嗎?”
Redis說:“當然有了,雖然都是網絡訪問,但是我這里所有的數據可都在內存中啊,訪問起來還是比你快。”
我承認,他說的是對的。
數據不一致
這天晚上,訪問量突然間特別的大,是平時的百倍,不,千倍。 據Redis說,這是張大胖那家伙在搞壓力測試了。
壓力測試過后,一地雞毛。 一盤點就發現,Redis的數據和我的數據居然發現了不一致。
Redis傻眼了,這是怎么回事?數據不一致,人類肯定以我MySQL的數據庫數據為準啊。
Tomcat提示Redis:“估計是高并發惹的禍,我們看看是怎么更新數據的。”
Redis說:“簡單啊,先更新MySQL,然后更新我的數據。”
Tomcat說:“這是兩步操作,如果有兩個線程都在這么干,就出問題了! 比如MySQL的有個值是100,現在線程1想把它改成200, 線程2想把它改成300。”
Redis說:“看來這里有個大漏洞啊,那怎么辦?”
看著他們倆一籌莫展的樣子,我忍不住說道:“這還不簡單,當需要更新數據的時候,不要去更新緩存,把緩存中相關數據刪除就行了。”
Redis說:“你這是官報私仇吧,把數據從我這里刪除了,下次用戶訪問的時候沒有,還得找你去要,對不對?”
我說:“是得找我要,但是能解決你的問題啊,兩個線程同時寫,不會出現數據庫和緩存不一致啊。“
再說了,這其實不是我們能管的事情,咱們走著瞧,看看張大胖怎么做。”
第二天,張大胖果然按照我說的邏輯修改了程序,還美名其曰:Cache Aside Pattern。
雖然我一直想把緩存干掉,可是,幾天后的經歷卻深刻地教育了我,緩存還是必不可少的......
(唉,再挖個坑,主題估計你也想到了,就是緩存穿透,擊穿,雪崩......)
【本文為51CTO專欄作者“劉欣”的原創稿件,轉載請通過作者微信公眾號coderising獲取授權】