中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redis與MySQL的雙寫一致性問題怎么解決

發布時間:2023-05-10 17:37:22 來源:億速云 閱讀:135 作者:iii 欄目:開發技術

本篇內容介紹了“RedisMySQL的雙寫一致性問題怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

    Redis與MySQL雙寫一致性是指在使用緩存和數據庫同時存儲數據的場景下( 主要是存在高并發的情況)如何保證兩者的數據一致性(內容相同或者盡可能接近)

     正常業務流程

    Redis與MySQL的雙寫一致性問題怎么解決

    讀沒什么問題,關鍵就在于寫(更新)操作,這就會出現幾個問題了,這里是先更新數據庫,然后對緩存操作。但對于緩存操作,是更新緩存還是刪除緩存呢?或者為什么不是先操作(刪除、更新)緩存在更新數據庫呢?

    總結一下就是到底先操作緩存再操作數據庫,還是先操作數據庫再操作緩存?

    帶著這幾個問題接著往下講。

    首先講一下操作緩存,包括兩種:更新緩存和刪除緩存,如何選擇?

    更新緩存? 刪除緩存?

    假設都先更新數據庫(因為先操作緩存再操作數據庫問題較大,后面會講)

    •  更新緩存

    先更新數據庫,再更新緩存。

    如果兩個請求同時對同一條數據進行修改,那么可能出現先后順序顛倒,導致緩存中的數據是舊的。之后的讀請求讀到的都是舊數據,只有當緩存失效后,才能從數據庫中得到正確的值。

    Redis與MySQL的雙寫一致性問題怎么解決

    • 刪除緩存

    先更新數據庫,再刪除緩存。

    會有這樣一種情況:緩存剛好失效,請求B從數據庫中查詢數據,得到舊值。此時請求A更新數據庫,將新值寫入數據庫,并刪除緩存。而請求B又將舊值寫入緩存中,導致臟數據

    Redis與MySQL的雙寫一致性問題怎么解決

    從上面看出現臟數據的要求要比更新緩存的要求更多,必須滿足以下幾個條件:

    1. 緩存失效

    2. 讀請求 + 寫請求并發

    3. 更新數據庫 + 刪除緩存的時間要比讀數據庫 + 寫緩存時間

    前面兩個很好滿足,我們再看看第三點,這個真的會出現嗎?

    數據庫在更新時一般是加鎖的,讀操作的速度遠快于寫操作的,所以第三點發生概率極低(當然也可能發生)

    注:這里我其實不是很理解,單純看確實發生概率低,但如果出現網絡延遲等情況呢,不也會發生嗎?希望好心人解惑,我反正沒理解。

    因此,在選擇刪除緩存時,還需要結合其他技術來優化性能和一致性。例如:

    • 使用消息隊列來異步地刪除或更新緩存,避免阻塞主線程或者丟失消息。

    • 使用延時雙刪來增加刪除成功率和減少不一致時間窗口。即在更新數據庫后立即刪除一次緩存,并在一定時間間隔后再次刪除一次。

     對比

    在更新緩存中, 每次去更新緩存,但是緩存中的數據不一定會被馬上讀取,這就會導致緩存中可能存放了很多不常訪問的數據,浪費緩存資源。而且很多情況下,寫到緩存中的值,并不是與數據庫中的值一一對應的,很有可能是先查詢數據庫,再經過一系列「計算」得出一個值,才把這個值才寫到緩存中。

    由此可見,這種更新緩存的方案,不僅緩存利用率不高,還會造成機器性能的浪費。所以我們一般考慮刪除緩存

    先更新緩存再更新數據庫

    在更新數據時,先將新數據寫入緩存(Redis),再將新數據寫入數據庫(MySQL)

     但其存在一下問題:

    • 緩存更新成功,但數據庫更新失敗,導致數據不一致

     :用戶修改了自己的昵稱,系統先將新的昵稱寫入緩存,然后再更新數據庫。但是在更新數據庫的過程中,發生了網絡故障或者數據庫宕機等異常情況,導致數據庫中的昵稱沒有被修改。這樣就會出現緩存中的昵稱和數據庫中的昵稱不一致的情況。

    • 緩存更新成功,但數據庫更新延遲,導致其他請求讀取到舊的數據

     :用戶下單了一個商品,系統先將訂單狀態寫入緩存,然后再更新數據庫。但是在更新數據庫的過程中,由于并發量大或者其他原因,導致數據庫的寫入速度慢于緩存的寫入速度。這樣就會出現其他請求從緩存中讀取到訂單狀態為已支付,而從數據庫中讀取到訂單狀態為未支付的情況。

    • 緩存更新成功,但在數據庫更新之前有其他請求查詢了緩存和數據庫,并將舊的數據寫回緩存,覆蓋了新的數據

     例:用戶A修改了自己的頭像,并上傳到服務器上。系統先將新的頭像地址寫入緩存,并返回給用戶A顯示。然后再將新的頭像地址更新到數據庫中。但是在這個過程中,用戶B訪問了用戶A的個人主頁,并從緩存中讀取到了新的頭像地址。由于緩存失效策略或者其他原因(比如重啟),導致緩存被清空或者過期。這時候用戶B再次訪問用戶A 的個人主頁,并從數據庫中讀取到了舊的頭像地址,并將其寫回緩存中。這樣就會出現緩存中 的頭像地址和 數據庫 中 的頭像地址不一致 的情況。

    上面說了一堆,其實總結就是緩存更新成功了,數據庫沒更新(更新失敗),導致緩存存的是最新值,數據庫存的是舊值。如果緩存失效了,就會拿到數據庫中的舊值。

     后面我自己也搞疑惑了,既然是因為數據庫更新失敗導致的問題,那我是不是只要保證數據庫更新成功就可以解決數據不一致的問題,當數據庫更新失敗時,不停的重試更新數據庫,直到數據庫更新完成。

    后面發現自己太天真,其中存在很多問題,比如:

    • 如果數據庫更新失敗的原因是數據庫宕機或者網絡故障,那么你不停地重試更新數據庫可能會造成更大的壓力和延遲,甚至導致數據庫恢復困難。

    • 如果數據庫更新失敗的原因是數據沖突或者業務邏輯錯誤,那么你不停地重試更新數據庫可能會導致數據丟失或者數據錯亂,甚至影響其他用戶的數據。

    • 如果你不停地重試更新數據庫,那么你需要考慮如何保證重試的冪等性和順序性,以及如何處理重試過程中發生的異常情況。

     所以,這種方法并不是一個很好的解決方案。

    先更新數據庫,再更新緩存

    當有一個更新操作時,先更新數據庫數據,然后再更新對應的緩存數據

     但是,這種方案也有一些問題和風險,比如:

    • 如果更新數據庫成功了,但是更新緩存失敗了,那么就會導致緩存中就會保留舊的數據,而數據庫中已經是新的數據,即臟數據。

    • 如果在更新數據庫和更新緩存之間,有其他請求查詢了同一個數據,并且發現緩存存在,那么就會從緩存中讀取舊的數據。這樣也會造成緩存和數據庫之間的不一致性。

     因此,在使用更新緩存操作時,無論誰先誰后,但凡后者發生異常,就會對業務造成影響。(還是上面那張圖)

    Redis與MySQL的雙寫一致性問題怎么解決

    那么如何處理異常情況來保證數據一致性呢

    這些問題的源頭都是多線程并發所導致的,所以最簡單的方法就是加鎖(分布式鎖)。兩個線程要修改同一條數據,每個線程在改之前,先去申請分布式鎖,拿到鎖的線程才允許更新數據庫和緩存,拿不到鎖的線程,返回失敗,等待下次重試。這么做的目的,就是為了只允許一個線程去操作數據和緩存,避免并發問題。

    但加鎖費時費力,肯定不推薦。并且,每次去更新緩存,但是緩存中的數據不一定會被馬上讀取,這就會導致緩存中可能存放了很多不常訪問的數據,浪費緩存資源。而且很多情況下,寫到緩存中的值,并不是與數據庫中的值一一對應的,很有可能是先查詢數據庫,再經過一系列「計算」得出一個值,才把這個值才寫到緩存中。

    由此可見,這種更新數據庫 + 更新緩存的方案,不僅緩存利用率不高,還會造成機器性能的浪費。

    所以此時我們需要考慮另外一種方案:刪除緩存

    先刪除緩存再更新數據庫

    當有一個更新操作時,先刪除對應的緩存數據,然后再更新數據庫數據

     但是,這種方案也有一些問題和風險,比如:

    • 如果刪除緩存后,更新數據庫失敗了,那么就會導致緩存丟失,下次查詢時需要重新從數據庫加載數據,增加了數據庫壓力和響應時間。

    • 如果在刪除緩存和更新數據庫之間,有其他請求查詢了同一個數據,并且發現緩存不存在,那么就會從數據庫中讀取舊的數據,并寫入到緩存中。這樣就會造成緩存和數據庫之間的不一致性。

    Redis與MySQL的雙寫一致性問題怎么解決

    先更新數據庫,再刪除緩存

    當有一個更新操作時,先更新數據庫數據,再刪除緩存

    上面其實講過了,我再重復一遍吧

    會有這樣一種情況:緩存剛好失效,請求B從數據庫中查詢數據,得到舊值。此時請求A更新數據庫,將新值寫入數據庫,并刪除緩存。而請求B又將舊值寫入緩存中,導致臟數據

    Redis與MySQL的雙寫一致性問題怎么解決

    從上面看出現臟數據的要求要比更新緩存的要求更多,必須滿足以下幾個條件:

    1. 緩存失效

    2. 讀請求 + 寫請求并發

    3. 更新數據庫 + 刪除緩存的時間要比讀數據庫 + 寫緩存時間

    前面兩個很好滿足,我們再看看第三點,這個真的會出現嗎?

    數據庫在更新時一般是加鎖的,讀操作的速度遠快于寫操作的,所以第三點發生概率極低

    所以,解決雙寫問題更適合的方法是先更新數據庫,再刪除緩存,當然具體場景具體分析,不定說一定就是這個。

    講解了這些操作后會出現的問題,那么為了避免這些問題,如何做呢?

    • 先刪除緩存再更新數據庫,然后使用異步線程或消息隊列來重建緩存。

    • 先更新數據庫再刪除緩存,并設置一個合理的過期時間來保證緩存的有效性。

    • 使用分布式鎖或樂觀鎖來控制并發訪問,并保證每次只有一個請求能夠操作緩存和數據庫

     ……

    下面講幾種常見的方法以保證雙寫一致性

    解決方案

    1. 重試

    上面也提到過,當第二步操作失敗時,我就重試嘛,盡可能地補救,但重試的成本太大,上面講過就不重復了。

    2. 異步重試

    既然重試方法占用資源,那我就做異步。在刪除或更新緩存時,如果操作失敗,不立即返回錯誤,而是通過一些機制(如消息隊列、定時任務、訂閱binlog等)來觸發緩存的重試操作。這樣可以避免同步重試緩存時的性能損耗和阻塞問題,但也可能導致緩存和數據庫的數據不一致的時間較長。

    2.1 使用消息隊列實現重試
    • 消息隊列保證可靠性:寫到隊列中的消息,成功消費之前不會丟失(重啟項目也不擔心)

    • 消息隊列保證消息成功投遞:下游從隊列拉取消息,成功消費后才會刪除消息,否則還會繼續投遞消息給消費者(符合我們重試的需求)

    • Redis與MySQL的雙寫一致性問題怎么解決

    使用消息隊列異步重試緩存的情況是指,當信息發生變化時,先更新數據庫,然后刪緩存,如果刪除成功就皆大歡喜,如果刪除失敗,則將需要刪除的key發送到消息隊列。另外有一個消費者線程從消息隊列中獲取要刪除的key,并根據key刪除或更新Redis中的緩存。如果操作失敗,則重新發送到消息隊列中進行重試。

    注:也可以不先嘗試刪除,直接發送給消息隊列,讓消息隊列

    舉例來說,假設有一個用戶信息表,需要將用戶信息緩存在Redis中。如果采用使用消息隊列異步重試緩存的方案,可以有以下幾個步驟:

    • 當用戶信息發生變化時,先更新數據庫,并返回成功結果給前端。

    • 嘗試去刪除緩存,成功則結束操作,失敗則將要刪除或更新緩存的操作生成一個消息(比如包含key和操作類型),并發送到消息隊列中(比如使用Kafka或RabbitMQ)。

    • 另外有一個消費者線程從消息隊列中訂閱并獲取這些消息,并根據消息內容刪除或更新Redis中的對應信息。

    • 如果刪除或更新緩存成功,則把這個消息從消息隊列中移除(丟棄),以免重復操作。

    • 如果刪除或更新緩存失敗,則執行失敗策略,比如設置一個延遲時間或者一個重試次數限制,然后重新發送這個消息到消息隊列中進行重試。

    • 如果重試超過一定次數仍然失敗,則向業務層發送報錯信息,并記錄日志。

    2.2 Binlog實現異步重試刪除

    使用binlog實現一致性的基本思路是利用binlog日志來記錄數據庫的變更操作,然后通過主從復制或者增量備份的方式來同步或者恢復數據。

    舉例來說,如果我們有一個主數據庫和一個從數據庫,我們可以在主數據庫上開啟binlog日志,并設置從數據庫作為它的復制節點。這樣,當主數據庫上發生任何變更操作時,它會將對應的binlog日志發送給從數據庫,從數據庫則會根據binlog日志來執行相同的操作,從而保證數據一致性。

    另外,如果我們需要恢復某個時間點之前的數據,我們也可以利用binlog日志來實現。首先,我們需要找到對應時間點之前的最近一個全量備份文件,并將其恢復到目標數據庫。然后,我們需要找到對應時間點之前的所有增量備份文件(即binlog日志文件),并按照順序將其應用到目標數據庫。這樣,我們就可以恢復出目標時間點之前的數據狀態了。

    Redis與MySQL的雙寫一致性問題怎么解決

    • 使用 Binlog 實時更新/刪除 Redis 緩存。利用 Canal,即將負責更新緩存的服務偽裝成一個 MySQL 的從節點,從 MySQL 接收 Binlog,解析 Binlog 之后,得到實時的數據變更信息,然后根據變更信息去更新/刪除 Redis 緩存;

    • MQ+Canal 策略,將 Canal Server 接收到的 Binlog 數據直接投遞到 MQ 進行解耦,使用 MQ 異步消費 Binlog 日志,以此進行數據同步;

    注:binlog日志是MySQL的二進制日志,它記錄了對數據庫的變更操作,比如插入、更新、刪除等。 binlog日志有兩個主要作用,一個是主從復制,另一個是增量備份。

    主從復制是指在一個主數據庫和一個或多個從數據庫之間實現數據的同步。主數據庫會將自己的binlog日志發送給從數據庫,從數據庫則會根據binlog日志來執行相同的操作,從而保證數據一致性。這樣可以提高數據的可用性和可靠性,也可以實現負載均衡和故障轉移。

    增量備份是指在全量備份的基礎上,定期備份數據庫的變更操作。全量備份是指將整個數據庫的數據完整地備份到一個文件中。增量備份則是指將每次變更操作對應的binlog日志文件備份到另一個文件中。這樣可以減少備份所占用的空間和時間,也可以實現靈活地恢復數據到任意時間點。

    至此,我們可以得出結論,想要保證數據庫和緩存一致性,推薦采用「先更新數據庫,再刪除緩存」方案,并配合「消息隊列」或「訂閱變更日志」的方式來做。

    3. 延時雙刪

    我們重點在將先更新數據庫,在刪除緩存。那如果我要先刪除緩存,再更新數據庫呢?

    回顧之前講的先刪除緩存,再更新數據庫,它會出現舊值覆蓋緩存的問題,那好辦,我們直接把這個舊值給刪了不就完了嗎,延時雙刪就是這個原理,它的基本思路是:

    1. 先刪除緩存

    2. 再更新數據庫

    3. 休眠一段時間(根據系統情況確定)

    4. 再次刪除緩存

    這樣做的目的是為了防止在更新數據庫后,有其他線程讀取到舊的緩存數據,并將其寫回緩存,導致數據不一致。

    舉個例子:假設有一個用戶信息表,其中有一個字段是用戶積分。現在有兩個線程A和B同時對用戶積分進行操作:

    • 線程A要給用戶增加100積分

    • 線程B要給用戶減少50積分

    如果使用延時雙刪策略,那么線程A和B的執行過程可能如下:

    • 線程A先刪除緩存中的用戶信息

    • 線程A再從數據庫中讀取用戶信息,發現用戶積分為1000

    • 線程A將用戶積分加上100,變為1100,并更新到數據庫中

    • 線程A休眠5秒(假設這個時間足夠讓數據庫同步)

    • 線程A再次刪除緩存中的用戶信息

    • 線程B先刪除緩存中的用戶信息

    • 線程B再從數據庫中讀取用戶信息,發現用戶積分為1100(因為線程A已經更新了)

    • 線程B將用戶積分減去50,變為1050,并更新到數據庫中

    • 線程B休眠5秒(假設這個時間足夠讓數據庫同步)

    • 線程B再次刪除緩存中的用戶信息

    這樣最終結果就是:數據庫中的用戶積分為1050,緩存中沒有該用戶信息。當下次有請求查詢該用戶信息時,就會從數據庫中讀取并寫入到緩存中。這樣就保證了數據一致性。

    延時雙刪適用于高并發場景,特別是對數據的修改操作比較頻繁,而查詢操作比較少的情況。這樣可以減輕數據庫的壓力,提高性能,同時保證數據的最終一致性。延時雙刪也適用于數據庫有主從同步延遲的場景,因為它可以避免在更新數據庫后,從庫還沒有同步完成時,讀取到舊的緩存數據,并將其寫回緩存。

    注: 這個休眠時間 = 讀業務邏輯數據的耗時 + 幾百毫秒。 為了確保讀請求結束,寫請求可以刪除讀請求可能帶來的緩存臟數據。

    “Redis與MySQL的雙寫一致性問題怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    江孜县| 云南省| 镇江市| 余江县| 武隆县| 霍邱县| 白城市| 凤城市| 雅江县| 邹城市| 漠河县| 湄潭县| 墨脱县| 崇文区| 尚志市| 宝兴县| 迁安市| 文登市| 城口县| 米林县| 班戈县| 思茅市| 平果县| 绥德县| 略阳县| 达州市| 广元市| 潜山县| 芦溪县| 龙海市| 武清区| 祁连县| 老河口市| 长泰县| 东城区| 克拉玛依市| 仲巴县| 泰顺县| SHOW| 崇义县| 青海省|