談談數據庫,緩存一致性

幾年前,我在看博客的時候,看到有一篇博客的標題就是關於數據庫,緩存一致性的,不覺得然,直接跳過去了,心想,這麼簡單的問題還討論個鬼啊。這種想法持續了好久,直到某天,我看到愈來愈多的人都在討論數據庫,緩存一致性的問題,纔好好的看了下博客,才發現原來數據庫,緩存一致性真不是一個簡單的問題。今天我也來談談數據庫,緩存一致性問題。程序員

科普

考慮到有一些小夥伴可能技術不是那麼好,可能沒有接觸過緩存,因此這裏仍是花上一分鐘的時間,來介紹下什麼是緩存,爲何要有緩存,以及數據庫和緩存是如何搭配使用的。數據庫

讀取數據庫是比較耗時的操做,若是每次都須要去數據庫讀取數據,會對數據庫形成必定的壓力,程序性能也會比較低下,因此須要引入緩存。緩存

緩存是提高程序性能的最重要、最有效、也是最簡單的手段之一。服務器

引入緩存後,讀操做會先去緩存中看下,若是沒有命中緩存,纔去讀取數據庫,而後把讀取出來的數據再放到緩存中去,這樣下一次讀操做就能夠命中緩存了,若是命中緩存,就能夠直接把數據返回出去了。網絡

image.png

寫操做,除了修改數據庫,還須要刪除緩存,由於不刪除緩存,讀的操做讀到的永遠都是緩存中的舊數據。併發

先刪除緩存,後修改數據庫

這個方案顯然是有問題的。性能

兩個併發的讀寫操做:線程

  1. 一個寫的操做先進來,把緩存刪除了;
  2. 在寫操做尚未更新數據庫的時候,一個讀的請求又進來了,發現沒有命中緩存,就去數據庫把老數據取出來了;
  3. 寫操做更新了數據庫;
  4. 讀操做把老數據放在了緩存中。

這樣,數據庫中的數據和緩存中的數據就不一致了,爲了更好的讓你們理解這個過程,獻上一張醜到沒法自拔的圖:
image.pngblog

這個方案顯然不行,可是這個方案真的一無可取嗎?隊列

非也,讓咱們設想下這樣的場景:一個寫的請求進來,刪除緩存,這個時候,Redis服務器忽然出問題了,或者網絡忽然出問題了,致使刪除緩存失敗,拋出了一個異常,致使程序沒有繼續執行修改數據庫的操做。從數據庫、緩存一致性的角度來講,這裏很好的保證了數據庫、緩存的一致性,二者保存的數據是同樣的,儘管保存的都是老數據。

先修改數據庫,後刪除緩存

相信絕大多數小夥伴都是運用的這個方案, 先前我以爲數據庫,緩存一致性沒有什麼好討論的,太簡單了,就是由於我以爲這個方案是如此完美,可是後面我才慢慢發現這個方案也有必定的問題。

看到第一種方案存在的問題,你們也必定想到了這個方案也有一樣的問題。

在沒有緩存的狀況下,兩個併發的讀寫操做:

  1. 讀操做先進來,發現沒有緩存,去數據庫中讀數據,這個時候由於某種緣由卡了,沒有及時把數據放入緩存;
  2. 寫的操做進來了,修改了數據庫,刪除了緩存;
  3. 讀操做恢復,把老數據寫進了緩存。

image.png

這樣就形成了數據庫、緩存不一致,不過,這個機率出現的很是低,由於這須要在沒有緩存的狀況下,有讀寫的併發操做,在通常狀況下,寫數據庫的操做要比讀數據庫操做慢得多,在這種狀況下,還要保證讀操做寫緩存晚於寫操做刪除緩存纔會出現這個問題,因此這個問題應該能夠忽略不計。

說了這麼多,並無看到先修改數據庫,後刪除緩存的致命問題啊,別急,讓咱們繼續設想這樣的場景:一個寫的操做進來,修改了數據庫,可是刪除緩存的時候 ,因爲Redis服務器出現問題了,或者網絡出現問題了,致使刪除緩存失敗,這樣數據庫保存的是新數據,可是緩存裏面的數據仍是老數據,妥妥的數據庫、緩存不一致啊。

延遲雙刪

能夠看到修改數據庫,後刪除緩存有兩個問題,雖然兩個問題都是低機率的,可是永遠追求完美的程序員可不能容許有這樣的事情發生,因此第三種方案出現了:延遲雙刪。

延遲雙刪就是先刪除緩存,後修改數據庫,最後延遲必定時間,再次刪除緩存。

圖片.png

這麼作就能夠在必定程度上緩解上述兩個問題,第一次刪除緩存至關於檢測下緩存服務是否可用,網絡是否有問題,第二次延遲必定時間,再次刪除緩存,是由於要保證讀的請求在寫的請求以前完成。

可是這麼作,仍是有必定問題,好比第一次刪除緩存是成功的,第二次刪除緩存才失敗,又該怎麼辦?

內存隊列

上面三種方式,都有必定的問題:

  • 修改數據庫、刪除緩存這兩個操做耦合在了一塊兒,沒有很好的作到單一職責;
  • 若是寫操做比較頻繁,可能會對Redis形成必定的壓力;
  • 若是刪除緩存失敗,該怎麼辦?

爲了解決上面三個問題,第四種方式出現了:內存隊列刪除緩存:寫操做只是修改數據庫,而後把數據的Id放在內存隊列裏面,後臺會有一個線程消費內存隊列裏面的數據,刪除緩存,若是緩存刪除失敗,能夠重試屢次。

這樣,就把修改數據庫和刪除緩存兩個操做解耦了,若是刪除緩存失敗,也能夠屢次嘗試。因爲後臺有一個線程去消費內存隊列去刪除緩存,不是直接刪除緩存,因此修改數據庫和刪除緩存之間產生了必定的延遲,這延遲應該能夠保證讀操做已經執行完畢了。

可是這麼作也有很差的地方:

  • 程序複雜度成倍上升,須要維護線程、隊列以及消費者;
  • 若是寫操做很是頻繁,隊列的數據比較多,可能消費會比較慢,修改數據庫後,間隔了必定的時間,緩存才被刪除。

可是這也是沒有辦法的事情,哪有十全十美的解決方案。

第三方隊列

通常來講,系統分爲前臺系統和後臺系統,前臺系統主要是讀操做,後臺系統纔有寫操做。

好比商品中心,前臺是面向用戶的,當用戶打開商品詳情頁,會去緩存中拿數據,後臺是面向業務人員的,業務人員能夠在後臺系統對商品信息進行修改。

若是是具備必定規模的公司,前臺系統和後臺系統確定不在同一個服務器上,並且是由不一樣的部門去負責的,因此內存隊列是確定用不了的,若是後臺系統修改數據庫後,直接刪除緩存,必定會發生以下的故事。

後臺系統 小明:大家前臺系統的產品詳情緩存的key是什麼格式的?發我下。
前臺系統 小花:Product:XXXXX。
後臺系統 小明:好的。

過了幾天,小花找到小明。

前臺系統 小花:不對啊。大家怎麼沒有把活動中的產品詳情緩存給刪掉啊?
後臺系統 小明:納尼,我怎麼知道大家是兩個緩存啊,把活動中的產品詳情緩存的key的格式發我下。
前臺系統 小花:Activity:Product:XXXX。
後臺系統 小明:好的。

過了幾天,訂單系統的開發又找到小明。
訂單系統 小強:大家修改了產品詳情後,還要把訂單中的產品詳情緩存給刪除。
後臺系統 小明:。。。

過了幾天,廣告系統的開發又找到小明。
廣告系統 小王:大家修改了產品詳情後,還要把廣告中的產品詳情緩存給刪除。

後臺系統 小明 卒,享年25。

若是引用了第三方隊列,如RabbitMQ,Kafka,小明就不會「卒」了,後臺系統的小明修改了數據庫後,不須要關心緩存的事情,只要把數據的Id丟到消息隊列,前臺系統、廣告系統、訂單系統的開發消費消息隊列中的數據刪除緩存。

上面說的幾種方案,都是比較常見的,也比較簡單,固然不一樣的方案也能夠搭配使用,可是沒有「銀彈」,沒有完美的解決方案,就看大家的研發團隊,大家的場景適合哪一種解決方案了。

今天的話題到這裏就結束了。

相關文章
相關標籤/搜索