如何保證緩存與數據庫雙寫時的數據一致性?

如何保證緩存與數據庫雙寫時的數據一致性?

在作系統優化時,想到了將數據進行分級存儲的思路。由於在系統中會存在一些數據,有些數據的實時性要求不高,好比一些配置信息。基本上配置了好久纔會變一次。而有一些數據實時性要求很是高,好比訂單和流水的數據。因此這裏根據數據要求實時性不一樣將數據分爲三級。redis

  • 第1級:訂單數據和支付流水數據;這兩塊數據對實時性和精確性要求很高,因此不添加任何緩存,讀寫操做將直接操做數據庫。sql

  • 第2級:用戶相關數據;這些數據和用戶相關,具備讀多寫少的特徵,因此咱們使用redis進行緩存。數據庫

  • 第3級:支付配置信息;這些數據和用戶無關,具備數據量小,頻繁讀,幾乎不修改的特徵,因此咱們使用本地內存進行緩存。緩存

可是隻要使用到緩存,不管是本地內存作緩存仍是使用 redis 作緩存,那麼就會存在數據同步的問題,由於配置信息緩存在內存中,而內存時沒法感知到數據在數據庫的修改。這樣就會形成數據庫中的數據與緩存中數據不一致的問題。接下來就討論一下關於保證緩存和數據庫雙寫時的數據一致性。markdown

解決方案

那麼咱們這裏列出來全部策略,而且討論他們優劣性。架構

  1. 先更新數據庫,後更新緩存
  2. 先更新數據庫,後刪除緩存
  3. 先更新緩存,後更新數據庫
  4. 先刪除緩存,後更新數據庫

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

這種場景通常是沒有人使用的,主要緣由是在更新緩存那一步,爲何呢?由於有的業務需求緩存中存在的值並非直接從數據庫中查出來的,有的是須要通過一系列計算來的緩存值,那麼這時候後你要更新緩存的話其實代價是很高的。若是此時有大量的對數據庫進行寫數據的請求,可是讀請求並很少,那麼此時若是每次寫請求都更新一下緩存,那麼性能損耗是很是大的。性能

舉個例子好比在數據庫中有一個值爲 1 的值,此時咱們有 10 個請求對其每次加一的操做,可是這期間並無讀操做進來,若是用了先更新數據庫的辦法,那麼此時就會有十個請求對緩存進行更新,會有大量的冷數據產生,若是咱們不更新緩存而是刪除緩存,那麼在有讀請求來的時候那麼就會只更新緩存一次。優化

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

這一種狀況應該不須要咱們考慮了吧,和第一種狀況是同樣的。spa

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

該方案也會出問題,具體出現的緣由以下。日誌

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

此時來了兩個請求,請求 A(更新操做) 和請求 B(查詢操做)

  1. 請求 A 會先刪除 Redis 中的數據,而後去數據庫進行更新操做
  2. 此時請求 B 看到 Redis 中的數據時空的,會去數據庫中查詢該值,補錄到 Redis 中
  3. 可是此時請求 A 並無更新成功,或者事務還未提交

那麼這時候就會產生數據庫和 Redis 數據不一致的問題。如何解決呢?其實最簡單的解決辦法就是延時雙刪的策略。

延時雙刪
延時雙刪

可是上述的保證事務提交完之後再進行刪除緩存還有一個問題,就是若是你使用的是 Mysql 的讀寫分離的架構的話,那麼其實主從同步之間也會有時間差。

主從同步時間差
主從同步時間差

此時來了兩個請求,請求 A(更新操做) 和請求 B(查詢操做)

  1. 請求 A 更新操做,刪除了 Redis
  2. 請求主庫進行更新操做,主庫與從庫進行同步數據的操做
  3. 請 B 查詢操做,發現 Redis 中沒有數據
  4. 去從庫中拿去數據
  5. 此時同步數據還未完成,拿到的數據是舊數據

此時的解決辦法就是若是是對 Redis 進行填充數據的查詢數據庫操做,那麼就強制將其指向主庫進行查詢。

從主庫中拿數據
從主庫中拿數據

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

問題:這一種狀況也會出現問題,好比更新數據庫成功了,可是在刪除緩存的階段出錯了沒有刪除成功,那麼此時再讀取緩存的時候每次都是錯誤的數據了。

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

此時解決方案就是利用消息隊列進行刪除的補償。具體的業務邏輯用語言描述以下:

  1. 請求 A 先對數據庫進行更新操做
  2. 在對 Redis 進行刪除操做的時候發現報錯,刪除失敗
  3. 此時將Redis 的 key 做爲消息體發送到消息隊列中
  4. 系統接收到消息隊列發送的消息後再次對 Redis 進行刪除操做

可是這個方案會有一個缺點就是會對業務代碼形成大量的侵入,深深的耦合在一塊兒,因此這時會有一個優化的方案,咱們知道對 Mysql 數據庫更新操做後再 binlog 日誌中咱們都可以找到相應的操做,那麼咱們能夠訂閱 Mysql 數據庫的 binlog 日誌對緩存進行操做。

利用訂閱 binlog 刪除緩存
利用訂閱 binlog 刪除緩存

總結

每種方案各有利弊,好比在第二種先刪除緩存,後更新數據庫這個方案咱們最後討論了要更新 Redis 的時候強制走主庫查詢就能解決問題,那麼這樣的操做會對業務代碼進行大量的侵入,可是不須要增長的系統,不須要增長總體的服務的複雜度。最後一種方案咱們最後討論了利用訂閱 binlog 日誌進行搭建獨立系統操做 Redis,這樣的缺點其實就是增長了系統複雜度。其實每一次的選擇都須要咱們對於咱們的業務進行評估來選擇,沒有一種技術是對於全部業務都通用的。沒有最好的,只有最適合咱們的。

相關文章
相關標籤/搜索