Redis在高併發下常見的錯誤場景

2021-09-19 08:34:29 字數 2344 閱讀 5135

第一種,看看自己是否已經入坑了。

//判斷redis快取是否有資料

if(!jedis.exists("testlocklistv_1"))else

system.out.println(jedis.get("testlocklistv_1"));//快取有資料直接返回快取資料

很多同學都是判斷是否存在相同的key,如果不存在,就訪問資料庫,然後把資料放入快取中,然後返回。

實際多執行緒執行時,會列印如下

多執行緒情況下多次擊穿快取,直接訪問資料庫

多執行緒情況下多次擊穿快取,直接訪問資料庫

多執行緒情況下多次擊穿快取,直接訪問資料庫

多執行緒情況下多次擊穿快取,直接訪問資料庫

多執行緒情況下多次擊穿快取,直接訪問資料庫

多執行緒情況下多次擊穿快取,直接訪問資料庫

答案很簡單,因為多個執行緒同時執行jedis.exists("testlocklistv_1")這一段**時,快取中確實沒有資料。然後都執行查詢資料庫,放入快取中。這樣請求還是直接通過資料庫拿資料

第二種經過改造,我們知道redis是可以實現分布式鎖的jedis.setnx(key,value); 那我們改造一下,通過分布式鎖來解決穿透的問題。

改造如下:

jedis jedis = jedispool.getresource();

//判斷redis快取是否有資料

if(!jedis.exists("testlocklistv_2"))

}else

system.out.println(jedis.get("testlocklistv_2"));//快取有資料直接返回快取資料

高併發先,列印結果

多執行緒情況下多次擊穿快取,直接訪問資料庫

資料庫中返回的查詢記錄

多執行緒情況下多次擊穿快取,直接訪問資料庫

資料庫中返回的查詢記錄

結果發現,即使加了鎖,我們還是穿透了快取,直接訪問資料庫了,雖然請求較少。仔細分析原因是因為,多個執行緒同時進入後判斷是否有相同key,由於第一次訪問,redis中是沒有相應的key的,但是多個執行緒又同時去拿鎖,此時雖然只有乙個執行緒能獲取鎖成功,進入查詢資料庫,然後把資料放入快取。但是會出現剛放完快取,釋放所。同一時間內有幾個執行緒剛通過if(!jedis.exists("testlocklistv_2"))判斷是否有次鍵值對的判斷,然後也重新獲取鎖,也訪問資料庫,儲存資料到快取。這樣也出現了擊穿快取,直接訪問資料庫中。

第三種,上面分析完畢前面幾種情況以後。

歸納一下修改後的邏輯:

1.查詢快取,如果快取存在,返回結果

2.快取不存在,查詢資料庫

3.爭奪分布式鎖

4.成功獲得鎖,再次判斷快取的存在

5.如果快取仍舊不存在,把查詢資料庫的結果迴圈放入快取

6.釋放分布式鎖

這種二次判斷存在性的機制有乙個專門的名字,叫做雙重檢測

**如下:

jedis jedis = jedispool.getresource();

//判斷redis快取是否有資料

if(!jedis.exists("testlocklistv_3"))else

system.out.println(jedis.get("testlocklistv_3"));//快取有資料直接返回快取資料

}}else

多執行緒下執行結果:

多執行緒情況下多次擊穿快取,直接訪問資料庫

資料庫中返回的查詢記錄

非常好已經只訪問一次資料庫,並且沒有穿透快取。

原理,因為所有的執行緒下都只能有乙個執行緒獲取鎖,如果獲取鎖以後,更新完快取以後。另外乙個快取進入,也要判斷是否已經存在key,沒有key才會查詢資料庫更新。只要執行過獲取鎖的操作完畢以後,肯定會存入快取的。所以這種方案是不會有擊穿快取的情況的。

幾點補充:

1.文中所使用的分布式鎖,其實並不是「正宗」的分布式鎖,當執行緒爭奪鎖失敗的時候,會直接返回查詢db的結果,而不會依靠自旋機制來等鎖。

2.為什麼優惠券列表的資訊要使用list型別來存入快取,而不是把整個列表存為乙個很長的json字串?這是由於業務需要,使用list在某些情況下更方便對單個優惠券資訊進行修改(lset指令)。

3.為什麼優惠券列表的資訊不使用redis的set或者hash資料型別來儲存,實現自動去重呢?對於set型別,去重前需要對比整個字串是否完全相同,而每一張優惠券是乙個較長的json字串,對比的效率會比較低。使用hash倒是可以實現高效的去重,但並未在根本上解決重複更新的問題。

Redis在高併發下存在的問題

描述 在某些特定環境下,無論是先更新redis還是更新資料庫,兩者的資料都有可能不一致。解決方案1 雙寫模式 解決方案2 失效模式 最終解決方案 無論是雙寫模式還是失效模式,都會導致快取的不一致問題。即多個例項同時更新會出事,怎麼辦?如果是使用者緯度資料 訂單資料 使用者資料 這種併發機率非常小,不...

springboot下redis高併發下的快取穿透

public responsebody string getclassesbyid pathvariable id integer id return redistemplate.opsforvalue get classes 從redis中拿 這樣看單機條件下沒有問題但是高併發下還是會存在多個使用...

高併發下restTemplate的錯誤分析

org.apache.http.conn.connectionpooltimeoutexception timeout waiting for connection此問題很明顯是連線等待超時,而且是從連線池中獲取的連線。那麼就有乙個很詫異的問題,這裡哪來的連線池呢?然後我去跟蹤resttemplat...