Redis 數據安全

1. 前言

       這裏所說的數據安全並不是像Java語言中,多個線程修改一個變量如i++,造成結果錯誤。我們知道Redis是單線程的,即使有在多的客戶端進行i++操作,結果也總是符合預期。而這裏的數據安全更多的是指緩存數據不符合我們的預期。

2. 緩存與數據庫雙寫時的數據一致性

       對於緩存和數據庫的操作是兩個操作,並不是原子性的,所以如果沒有合適的方法去保證,一定會帶來數據不一致性的情況。

2.1 cache aside pattern

       最經典的緩存+數據庫讀寫的模式,cache aside pattern

  • 讀的時候,先讀緩存,緩存沒有的話,那麼就讀數據庫,然後取出數據後放入緩存,同時返回響應。
  • 更新的時候,先刪除緩存,然後再更新數據庫。

       爲什麼選擇刪除緩存而不是先更新緩存,或者說是更新數據庫成功後,在將刪除的緩存進行重新保存?

       首先說一下第一種方案的缺陷:

       如果我們先更新緩存,但是如果更新數據庫失敗,那麼此時便造成數據庫與緩存狀態不一致。

       第二種方案的缺陷:

       該方案相當於我們每進行一次數據的更新,就保存一次緩存,那麼這些緩存是否一定是熱點數據呢?如果不是的話,其不僅浪費內存,而且緩存並不僅僅是數據庫中的數據,更有可能需要經過大量的計算才能保存到緩存中。

       如我之前寫的一個視頻排行榜程序,需要進行全表掃描,根據視頻的播放量和評論數計算熱度。如果視頻的播放量每播放一次,便進行一次熱度計算,毫無疑問,很浪費資源。

       而選擇只刪除緩存,不進行緩存數據重新更新,是一種懶加載的思想,當我們需要緩存的時候再重新保存。

2.2 cache aside pattern 一定能保證數據一致性嗎

       考慮如下場景,序號代表執行過程,注意並不意味着請求先到來,其先來的請求一定先於後來的請求執行完畢。

在這裏插入圖片描述
       可以看到,即使採用cache aside pattern,在請求A更新完數據庫之後,數據庫中的數據與緩存中的數據依然處於不一致的狀態。

       該情況通常只會出現在只有在對一個數據在併發的進行讀寫的時候,纔可能會出現這種問題。並且如果併發量很低的話,特別是讀併發很低,每天訪問量就1萬次,那麼很少的情況下,會出現剛纔描述的那種不一致的場景。但如果每天的是上億的流量,每秒併發讀是幾萬,每秒只要有數據更新的請求,就可能會出現上述的數據庫+緩存不一致的情況。

2.3 數據庫與緩存更新與讀取操作進行異步串行化

       爲解決上述的問題,我們可以採用數據庫與緩存更新與讀取操作進行異步串行化的方案。

在這裏插入圖片描述
       我們在JVM針對該商品創建一個內存隊列,只有前面的請求結束纔可以輪到後面的請求執行,如此便可以做到緩存與數據庫的一致性。

       但是如果我們對於每一個讀請求,都將其加入到內存隊列中,毫無疑問是比較浪費時間的。我們可以用過一個volatile變量維護當前內存隊列中寫請求的個數,只有寫請求個數不爲0時,纔將其加入隊列,否則直接進行讀取操作即可。

       在上面我們僅僅是創建了一個內存隊列,倘若我們有多個商品,只使用一個內存隊列顯然併發性非常有限,可以針對每一種或每一類商品創建一個內存隊列,這樣粒度降低,併發性便可以提供。

在這裏插入圖片描述

       上述方案在多服務實例部署時,有這樣一種情況,即對於同一個商品的操作被路由分發的不同的實例中,因爲內存隊列是基於JVM的,所以依然有可能造成數據的不一致。

       解決方案:通過nginx進行路由分流時,同一個商品使其分流到一臺實例中。

3. redis 的併發競爭問題該如何解決

       多客戶端同時併發寫一個key,可能本來應該先到的數據後到了,導致數據版本錯了。或者是多客戶端同時獲取一個key,修改值之後再寫回去,只要順序錯了,數據就錯了。

在這裏插入圖片描述
       Redis中的數據變化可能有如下幾種情況:

	v0 --> v1 --> v2 -- v3
	v0 --> v2 --> v1 -- v3
	v0 --> v3 --> v1 -- v2
	......

       如果我們想讓數據按時間的先後順序進行保存,即數據變化的方式爲v0 --> v1 --> v2 -- v3,可以採用時間戳+CAS的方式。

       如果當前的時間戳大於緩存中的數據庫才允許操作。

在這裏插入圖片描述
       如此便可以保證緩存中的數據符合我們最終的目標數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章