天天看點

Redis緩存與資料庫一緻性解決方案(下)6 直接更新 Cache總結延時雙删政策設定緩存過期時間寫完資料庫後,再次删除緩存成功保證

5.2.2 先更新DB,再删除Cache

Redis緩存與資料庫一緻性解決方案(下)6 直接更新 Cache總結延時雙删政策設定緩存過期時間寫完資料庫後,再次删除緩存成功保證

這種情況下,如果其他線程并發讀緩存的請求不多,那麼,就不會有很多請求讀取到舊值。

而且,線程A一般也會很快删除緩存值,這樣一來,其他線程再次讀取時,就會發生緩存缺失,進而從資料庫中讀取最新值。是以,這種情況對業務的影響較小。

至此,Cache和DB資料不一緻的原因也都有了對應解決方案。

删除Cache或更新DB失敗而導緻資料不一緻

重試,確定删除或更新成功

在删除Cache、更新DB這兩步操作中,有其他線程的并發讀操作,導緻其他線程讀取到舊值

延遲雙删

絕大多數場景都會将Redis作為隻讀緩存:

既可以先删除緩存值再更新資料庫

也可以先更新資料庫再删除緩存

推薦優先使用先更新資料庫再删除緩存:

先删除緩存值再更新資料庫,有可能導緻請求因緩存缺失而通路資料庫,給資料庫帶來壓力

如果業務應用中讀取資料庫和寫緩存的時間不好估算,那麼,延遲雙删中的等待時間就不好設定

不過,當使用先更新資料庫再删除緩存時,也有個地方需要注意,如果業務層要求必須讀取一緻的資料,那麼,我們就需要在更新資料庫時,先在Redis緩存用戶端暫存并發讀請求,等資料庫更新完、緩存值删除後,再讀取資料,進而保證資料一緻性。

6 直接更新 Cache

在隻讀緩存中進行資料的删改操作時,需要在緩存中删除相應的緩存值。若此過程不是删除緩存,而是直接更新緩存,效果如何?

這種情況相當于把Redis當做讀寫緩存使用,删改操作同時操作DB、Cache。

6.1 無并發

先更新資料庫,再更新緩存

若更新DB成功,但Cache更新失敗,此時DB最新值,但緩存舊值,後續讀請求會直接命中緩存,得到舊值。

先更新緩存,再更新資料庫

如果更新緩存成功,但資料庫更新失敗:

  • 緩存中是最新值
  • 資料庫中是舊值

後續讀請求會直接命中緩存,但得到的是最新值,短期對業務影響不大。但一旦緩存過期或滿容後被淘汰,讀請求就會從資料庫中重新加載舊值到緩存中,之後的讀請求會從緩存中得到舊值,對業務産生影響。

針對這種其中一個操作可能失敗的情況,類似隻讀緩存方案,也可使用重試。把第二步操作放入到MQ中,消費者從MQ取出消息,再更新緩存或資料庫,成功後把消息從消息隊列删除,否則進行重試,以此達到資料庫和緩存的最終一緻。

6.2 并發讀寫

也會産生不一緻,分為以下4種雙寫場景。

雙寫模式下,更新DB有傳回值,更新Redis的操作可放到更新DB傳回後進行,通過資料庫的行鎖機制,可以避免更新DB是線程A,B,但更新Redis是線程B,A的情況。

寫+讀并發。

線程A先更新資料庫,之後線程B讀取資料,此時線程B會命中緩存,讀取到舊值,之後線程A更新緩存成功,後續的讀請求會命中緩存得到最新值。

這時,線程A未更新完緩存之前,在這期間的讀請求會短暫讀到舊值,對業務短暫影響。

線程A先更新緩存成功,之後線程B讀取資料,此時線程B命中緩存,讀取到最新值後傳回,之後線程A更新資料庫成功。這種場景下,雖然線程A還未更新完資料庫,資料庫會與緩存存在短暫不一緻,但在這之前進來的讀請求都能直接命中緩存,擷取到最新值,是以對業務沒影響。

寫+寫并發。

線程A和線程B同時更新同一條資料,更新資料庫的順序是先A後B,但更新緩存時順序是先B後A,這會導緻資料庫和緩存的不一緻。

與場景3類似,線程A和線程B同時更新同一條資料,更新緩存的順序是先A後B,但是更新資料庫的順序是先B後A,這也會導緻資料庫和緩存的不一緻。

場景1和2對業務影響較小,場景3和4會造成資料庫和緩存不一緻,影響較大。即讀寫緩存下,寫+讀并發對業務的影響較小,而寫+寫并發時,會造成資料庫和緩存的不一緻。

針對場景3、4解決方案:對于寫請求,配合分布式鎖。寫請求進來時,針對同一資源的修改操作,先加分布式鎖,這樣同一時間隻允許一個線程去更新DB和Cache,沒有拿到鎖的線程把操作放入到MQ,延時處理。

這樣保證多個線程操作同一資源的順序性,以此保證一緻性。

綜上,使用讀寫緩存同時操作資料庫和緩存時,因為其中一個操作失敗導緻不一緻的問題,同樣可以通過MQ重試解決。

而在并發的場景下,讀+寫并發對業務沒有影響或者影響較小,而寫+寫并發時需要配合分布式鎖的使用,才能保證緩存和資料庫的一緻性。

另外,讀寫緩存模式由于會同時更新資料庫和緩存:

優點

緩存一直會有資料。若更新後立即通路,可直接命中緩存,能降低讀請求對DB的壓力(沒有隻讀緩存的删除緩存導緻緩存缺失和再加載的過程)

缺點

若更新後的資料,之後很少再被通路到,會導緻緩存中保留的不是最熱資料,緩存使用率不高(隻讀緩存中保留的都是熱資料)

是以讀寫緩存比較适合用于讀寫相當的業務場景。

總結

Redis緩存與資料庫一緻性解決方案(下)6 直接更新 Cache總結延時雙删政策設定緩存過期時間寫完資料庫後,再次删除緩存成功保證

延時雙删政策

寫DB前後都執行

redis.del(key)

,并設定合理逾時時間。

執行流程

  1. 先删除緩存
  2. 再寫資料庫
  3. 休眠xx毫秒(根據具體業務時間)
  4. 再次删除緩存

xx毫秒怎麼确定?

需要評估項目讀資料業務邏輯耗時,以確定讀請求結束,寫請求可删除讀請求造成的緩存髒資料。

該政策還要考慮 redis 和資料庫主從同步的耗時。最後的寫資料的休眠時間:則在讀資料業務邏輯的耗時的基礎上,加上幾百ms即可。比如:休眠1秒。

設定緩存過期時間

理論上,設定緩存過期時間,是保證最終一緻性的解決方案。

所有的寫操作以DB為準,隻要到達緩存過期時間,則後面的讀請求自然會從DB讀取新值,然後回填緩存。

結合雙删政策+緩存逾時設定,這樣最差的情況就是在逾時時間内資料存在不一緻,而且又增加寫請求耗時。

寫完資料庫後,再次删除緩存成功保證

上述的方案有一個缺點,那就是操作完資料庫後,由于種種原因删除緩存失敗,這時,可能就會出現資料不一緻的情況。

需提供保障重試方案。

方案一

具體流程

  1. 更新資料庫資料
  2. 緩存因為種種問題删除失敗
  3. 将需要删除的key發送至消息隊列
  4. 自己消費消息,獲得需要删除的key
  5. 繼續重試删除操作,直到成功

然而,該方案有一個缺點,對業務線代碼造成大量的侵入。于是有了方案二。

在方案二中,啟動一個訂閱程式去訂閱資料庫的binlog,獲得需要操作的資料。在應用程式中,另起一段程式,獲得這個訂閱程式傳來的資訊,進行删除緩存操作。

方案二

  1. 資料庫會将操作資訊寫入binlog日志當中
  2. 訂閱程式提取出所需要的資料以及key
  3. 另起一段非業務代碼,獲得該資訊
  4. 嘗試删除緩存操作,發現删除失敗
  5. 将這些資訊發送至消息隊列
  6. 重新從消息隊列中獲得該資料,重試操作。

以上方案都是在業務中經常會碰到的場景,可以依據業務場景的複雜和對資料一緻性的要求來選擇具體的方案。