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