幾年前,我在看博客的時候,看到有一篇博客的標題就是關於數據庫,緩存一致性的,不覺得然,直接跳過去了,心想,這麼簡單的問題還討論個鬼啊。這種想法持續了好久,直到某天,我看到愈來愈多的人都在討論數據庫,緩存一致性的問題,纔好好的看了下博客,才發現原來數據庫,緩存一致性真不是一個簡單的問題。今天我也來談談數據庫,緩存一致性問題。程序員
考慮到有一些小夥伴可能技術不是那麼好,可能沒有接觸過緩存,因此這裏仍是花上一分鐘的時間,來介紹下什麼是緩存,爲何要有緩存,以及數據庫和緩存是如何搭配使用的。數據庫
讀取數據庫是比較耗時的操做,若是每次都須要去數據庫讀取數據,會對數據庫形成必定的壓力,程序性能也會比較低下,因此須要引入緩存。緩存
緩存是提高程序性能的最重要、最有效、也是最簡單的手段之一。服務器
引入緩存後,讀操做會先去緩存中看下,若是沒有命中緩存,纔去讀取數據庫,而後把讀取出來的數據再放到緩存中去,這樣下一次讀操做就能夠命中緩存了,若是命中緩存,就能夠直接把數據返回出去了。網絡
寫操做,除了修改數據庫,還須要刪除緩存,由於不刪除緩存,讀的操做讀到的永遠都是緩存中的舊數據。併發
這個方案顯然是有問題的。性能
兩個併發的讀寫操做:線程
這樣,數據庫中的數據和緩存中的數據就不一致了,爲了更好的讓你們理解這個過程,獻上一張醜到沒法自拔的圖:
blog
這個方案顯然不行,可是這個方案真的一無可取嗎?隊列
非也,讓咱們設想下這樣的場景:一個寫的請求進來,刪除緩存,這個時候,Redis服務器忽然出問題了,或者網絡忽然出問題了,致使刪除緩存失敗,拋出了一個異常,致使程序沒有繼續執行修改數據庫的操做。從數據庫、緩存一致性的角度來講,這裏很好的保證了數據庫、緩存的一致性,二者保存的數據是同樣的,儘管保存的都是老數據。
相信絕大多數小夥伴都是運用的這個方案, 先前我以爲數據庫,緩存一致性沒有什麼好討論的,太簡單了,就是由於我以爲這個方案是如此完美,可是後面我才慢慢發現這個方案也有必定的問題。
看到第一種方案存在的問題,你們也必定想到了這個方案也有一樣的問題。
在沒有緩存的狀況下,兩個併發的讀寫操做:
這樣就形成了數據庫、緩存不一致,不過,這個機率出現的很是低,由於這須要在沒有緩存的狀況下,有讀寫的併發操做,在通常狀況下,寫數據庫的操做要比讀數據庫操做慢得多,在這種狀況下,還要保證讀操做寫緩存晚於寫操做刪除緩存纔會出現這個問題,因此這個問題應該能夠忽略不計。
說了這麼多,並無看到先修改數據庫,後刪除緩存的致命問題啊,別急,讓咱們繼續設想這樣的場景:一個寫的操做進來,修改了數據庫,可是刪除緩存的時候 ,因爲Redis服務器出現問題了,或者網絡出現問題了,致使刪除緩存失敗,這樣數據庫保存的是新數據,可是緩存裏面的數據仍是老數據,妥妥的數據庫、緩存不一致啊。
能夠看到修改數據庫,後刪除緩存有兩個問題,雖然兩個問題都是低機率的,可是永遠追求完美的程序員可不能容許有這樣的事情發生,因此第三種方案出現了:延遲雙刪。
延遲雙刪就是先刪除緩存,後修改數據庫,最後延遲必定時間,再次刪除緩存。
這麼作就能夠在必定程度上緩解上述兩個問題,第一次刪除緩存至關於檢測下緩存服務是否可用,網絡是否有問題,第二次延遲必定時間,再次刪除緩存,是由於要保證讀的請求在寫的請求以前完成。
可是這麼作,仍是有必定問題,好比第一次刪除緩存是成功的,第二次刪除緩存才失敗,又該怎麼辦?
上面三種方式,都有必定的問題:
爲了解決上面三個問題,第四種方式出現了:內存隊列刪除緩存:寫操做只是修改數據庫,而後把數據的Id放在內存隊列裏面,後臺會有一個線程消費內存隊列裏面的數據,刪除緩存,若是緩存刪除失敗,能夠重試屢次。
這樣,就把修改數據庫和刪除緩存兩個操做解耦了,若是刪除緩存失敗,也能夠屢次嘗試。因爲後臺有一個線程去消費內存隊列去刪除緩存,不是直接刪除緩存,因此修改數據庫和刪除緩存之間產生了必定的延遲,這延遲應該能夠保證讀操做已經執行完畢了。
可是這麼作也有很差的地方:
可是這也是沒有辦法的事情,哪有十全十美的解決方案。
通常來講,系統分爲前臺系統和後臺系統,前臺系統主要是讀操做,後臺系統纔有寫操做。
好比商品中心,前臺是面向用戶的,當用戶打開商品詳情頁,會去緩存中拿數據,後臺是面向業務人員的,業務人員能夠在後臺系統對商品信息進行修改。
若是是具備必定規模的公司,前臺系統和後臺系統確定不在同一個服務器上,並且是由不一樣的部門去負責的,因此內存隊列是確定用不了的,若是後臺系統修改數據庫後,直接刪除緩存,必定會發生以下的故事。
後臺系統 小明:大家前臺系統的產品詳情緩存的key是什麼格式的?發我下。
前臺系統 小花:Product:XXXXX。
後臺系統 小明:好的。
過了幾天,小花找到小明。
前臺系統 小花:不對啊。大家怎麼沒有把活動中的產品詳情緩存給刪掉啊?
後臺系統 小明:納尼,我怎麼知道大家是兩個緩存啊,把活動中的產品詳情緩存的key的格式發我下。
前臺系統 小花:Activity:Product:XXXX。
後臺系統 小明:好的。
過了幾天,訂單系統的開發又找到小明。
訂單系統 小強:大家修改了產品詳情後,還要把訂單中的產品詳情緩存給刪除。
後臺系統 小明:。。。
過了幾天,廣告系統的開發又找到小明。
廣告系統 小王:大家修改了產品詳情後,還要把廣告中的產品詳情緩存給刪除。
後臺系統 小明 卒,享年25。
若是引用了第三方隊列,如RabbitMQ,Kafka,小明就不會「卒」了,後臺系統的小明修改了數據庫後,不須要關心緩存的事情,只要把數據的Id丟到消息隊列,前臺系統、廣告系統、訂單系統的開發消費消息隊列中的數據刪除緩存。
上面說的幾種方案,都是比較常見的,也比較簡單,固然不一樣的方案也能夠搭配使用,可是沒有「銀彈」,沒有完美的解決方案,就看大家的研發團隊,大家的場景適合哪一種解決方案了。
今天的話題到這裏就結束了。