建議前面文章沒看過的同窗先看下前面的文章:java
「老司機帶你玩轉面試(1):緩存中間件 Redis 基礎知識以及數據持久化」git
「老司機帶你玩轉面試(2):Redis 過時策略以及緩存雪崩、擊穿、穿透」github
「老司機帶你玩轉面試(3):Redis 高可用之主從模式」面試
「老司機帶你玩轉面試(4):Redis 高可用之哨兵模式」redis
「老司機帶你玩轉面試(5):Redis 集羣模式 Redis Cluster」數據庫
這個問題產生的根源是併發寫,自己 Redis 是不會產生併發問題的,看過前面文章的同窗應該知道, Redis 的線程模型是單線程的,全部的操做指令都在文件事件處理器的隊列中,這個絕對是按照先進先出的原則進行操做的,那麼併發競爭的問題是如何產生的?緩存
例如咱們如今有三個客戶端 A , B , C ,須要按序像 Redis 中寫入或者更新數據 A -> B -> C
,就像下面這樣:網絡
可是如今忽然 A 的網絡抖動了一下,致使 A 並非第一個去 Redis 中寫數據的,整個流程的操做順序變成了 B -> C -> A
,這樣數據不就錯了麼。mybatis
這就比如你在網上買東西,加購物車,下訂單,支付訂單的順序變掉了,你先下單,再支付,再加購物車,這個流程根本走不下去的好吧。這種事情發生在線上的系統上的時候,是很是恐怖的。併發
這種狀況解決起來其實也很簡單,加一個分佈式鎖就能夠了,好比這樣:
咱們能夠基於 Zookeeper
實現一個全局分佈式鎖,確保同一時間,只能有一個系統實例在操做某個 key ,別人都不容許讀和寫。
你要寫入緩存的數據,都是從 DB 裏查出來的,都得寫入 DB 中,寫入 DB 中的時候必須保存一個時間戳,從 DB 查出來的時候,時間戳也查出來。
每次要寫以前,先判斷一下當前這個 Value 的時間戳是否比緩存裏的 Value 的時間戳要新。若是是的話,那麼能夠寫,不然,就不能用舊的數據覆蓋新的數據。
只要使用緩存,就可能會涉及到緩存與數據庫的雙寫,只要是雙寫,就必定會有數據一致性的問題。
首先先介紹下經典的 Redis + BD 的讀寫模式:
這裏還會有另一個問題:爲何是刪除緩存,而不是更新緩存?
緣由很簡單,不少時候,在複雜點的緩存場景,緩存不僅僅是數據庫中直接取出來的值。
好比可能更新了某個表的一個字段,而後其對應的緩存,是須要查詢另外兩個表的數據並進行運算,才能計算出緩存最新的值的。
另外更新緩存的代價有時候是很高的。是否是說,每次修改數據庫的時候,都必定要將其對應的緩存更新一份?也許有的場景是這樣,可是對於比較複雜的緩存數據計算的場景,就不是這樣了。若是你頻繁修改一個緩存涉及的多個表,緩存也頻繁更新。可是問題在於,這個緩存到底會不會被頻繁訪問到?
舉個栗子,一個緩存涉及的表的字段,在 1 分鐘內就修改了 20 次,或者是 100 次,那麼緩存更新 20 次、100 次;可是這個緩存在 1 分鐘內只被讀取了 1 次,有大量的冷數據。實際上,若是你只是刪除緩存的話,那麼在 1 分鐘內,這個緩存不過就從新計算一次而已,開銷大幅度下降。用到緩存纔去算緩存。
其實刪除緩存,而不是更新緩存,就是一個 lazy 計算的思想,不要每次都從新作複雜的計算,無論它會不會用到,而是讓它到須要被使用的時候再從新計算。像 mybatis,hibernate,都有懶加載思想。查詢一個部門,部門帶了一個員工的 list,沒有必要說每次查詢部門,都把裏面的 1000 個員工的數據也同時查出來啊。80% 的狀況,查這個部門,就只是要訪問這個部門的信息就能夠了。先查部門,同時要訪問裏面的員工,那麼這個時候只有在你要訪問裏面的員工的時候,纔會去數據庫裏面查詢 1000 個員工。
這種模式應該就是咱們大多數人如今使用的模式,在不是嚴格要求 「緩存 + 數據庫」 必須保持一致的時候,這樣作是能夠的。
那爲何說上面這種方案在嚴格要求 「緩存 + 數據庫」 的場景下不行呢?
由於這個問題只有在對一個數據在併發的進行讀寫的時候,纔可能會出現這種問題。
就是若是說併發量沒那麼高的話,好比 1s 才一個對緩存的讀寫請求,那麼大機率是不會出現緩存 「緩存 + 數據庫」 不一致的狀況。
可是問題是,若是天天的是上億的流量,每秒併發讀是幾萬,每秒只要有數據更新的請求,就可能會出現上述的 「緩存 + 數據庫」 不一致的狀況。
那麼這種問題是否是就沒有解決方案,固然有,可是若是不是嚴格要求 「緩存 + 數據庫」 必須保持一致性的場景,最好不要使用這個方案。
咱們可讓讀請求和寫請求串行化,把全部的讀寫請求都串行到一個隊列裏面去。
串行化能夠保證必定不會出現不一致的狀況,可是它也會致使系統的吞吐量大幅度下降,用比正常狀況下多幾倍的機器去支撐線上的一個請求。
把全部的操做都放到隊列裏面,順序確定不會亂,可是併發高了,這隊列很容易阻塞,反而會成爲整個系統的瓶頸。