經常用Redis,這些坑你知道嗎?

2021-10-04 00:11:30 字數 3189 閱讀 5385

近些年,redis憑藉在效能、穩定性和高可擴充套件性上的卓越表現,基本上已經成了網際網路行業快取中介軟體的標配,甚至很多傳統行業也在使用redis。那麼我們在使用redis等快取中介軟體時,要注意哪些問題呢?本文咱們就來聊聊,我們使用快取中介軟體過程中曾經遇到的坑!

快取穿透

先看乙個常見的快取使用方式。請求來了,先查快取,快取有值就直接返回;快取沒值,查資料庫,然後把資料庫的值存到快取,再返回。

假如快取沒查到某個值,查資料庫也沒這個值,也就是說要查的值根本不存在,這樣就會導致每次對這個值的查詢請求都會穿透到資料庫。這就是所謂的「快取穿透」。

如何避免快取穿透?

如果從資料庫中沒查到值,可以在快取中記錄乙個空值,來避免「快取穿透」。並且要給這個空值設定乙個較短的過期時間。

比如說,我們經常會把使用者資訊快取到redis。如果呼叫方傳了乙個不存在的userid,在快取中就查不到這個使用者資訊,然後去db也查不到。這樣就會導致,每次根據這個userid查使用者資訊,都會穿透到資料庫,給資料庫造成了壓力。為了避免快取穿透,當資料庫查不到時,我們可以在快取中記錄一條空資料,比如userid做為key,空json做為值,如果程式獲得這個空json,就按使用者不存在處理。再給這個key設定乙個很短的過期時間,比如30秒。

快取雪崩

我們經常會遇到需要初始化快取的情況。比如說使用者系統重構,表結構發生了變化,快取資訊也要變,上線前需要初始化快取,將使用者資訊批量存入快取。假如我們給這些使用者資訊設定相同的過期時間,到過期時間點所有使用者資訊的快取記錄就會同時集中失效,導致大量請求瞬間打到資料庫,資料庫很可能會被搞掛。這種快取集中失效,導致大量請求同時穿透到資料庫的情況,就是所謂的「雪崩效應」。

快取併發

當系統併發很高,快取資料尤其是熱點資料過期後,可能會出現多個請求同時訪問資料庫並設定快取的情況,不但給資料庫帶來壓力,而且會有快取頻繁更新的問題。

我們可以通過加鎖來避免快取併發問題。如果從快取查不到資料,對查詢資料加分布式鎖,然後查資料庫並把資料庫查詢結果放入快取。其他執行緒等待鎖釋放後,直接從快取取值。

比如,電商系統會快取商品sku**,一些熱點商品的併發訪問會非常高。當快取過期失效後,訪問請求從快取查不到記錄,此時可以用商品sku id為key加分布式鎖,然後從資料庫查詢**並把**放入快取,最後解鎖。解鎖後其他請求就可以從快取直接取值了。從而避免了資料庫的壓力。

分布式鎖

以我們之前做過的5人拼團為例。如果有使用者參加**,我們需要先校驗參團人數是否達到了上限5人。如果沒達到5人,使用者才可以參團。偽**如下:

//根據拼團id獲取目前參團成員數量

int numofmembers = pintuanservice.getnumofmembersbyid(pintuanid);

if(numofmembers < 5)

高併發場景下,上面的**會有很嚴重的問題。如果某個團當前的參團人數是4,這時有兩個使用者同時參團,使用者a和使用者b的請求同時進入上面的**塊,a和b的請求同時執行到第2行**,獲取的numofmembers都是4,表示式 numofmembers < 5 成立,所以兩個使用者都能執行到第4行**,就是說a使用者和b使用者都能成功參加拼團。於是,參團人數就超過了5人的上限。所以我們就需要加鎖來避免這個問題。synchronized行嗎?不行。因為我們的服務是多節點部署的,所以要加分布式鎖。**如下:

boolean aquired = distributedlock.aquirelock(pintuanid, 3000);

if(aquired == true)

} finally

}這樣就好多啦!接下來我們看看基於redis分布式鎖的實現,以及特別要注意的問題。一般我們會基於setnx實現redis分布式鎖。setnx命令可以檢查key是否存在,如果key不存在,就在redis中建立乙個鍵值對(操作成功),如果key已經存在就放棄執行(操作失敗)。

先看一段基於springboot實現的加鎖和釋放鎖的**:

@component

public class distributedlock {

@autowired

private stringredistemplate redistemplate;

上面的**。乍一看,好像沒什麼問題!加鎖失敗有迴圈重試加鎖,過期時間設定了,而且也保證了建立key-value鍵值對和設定過期時間的原子性,這樣當程式沒有正常釋放鎖時,也能保證過期後鎖自動釋放(注意:redis較老的版本不支援 setnx 和設定過期時間的原子操作,不過可以利用lua指令碼來保證原子性)。

我們再仔細思考一下,一般場景我們會對key設定乙個很短的過期時間,當一次操作因為網路等原因耗費了較長時間,操作還沒完成key就過期失效了。這樣會產生什麼問題呢?我們還是以拼團為例加以說明,先看看下面這張圖:

如上圖,使用者a和使用者b同時參加同一團,團id為 001,我們以團id作為分布式鎖的key,「distributedlock」 作為固定的value,過期時間是5秒。a先獲取分布式鎖,但是由於網路等原因a的拼團操作在5秒內沒完成,這時key過期並從redis清除掉,a的分布式鎖失效。此時使用者b拿到分布式鎖,key也同樣是團id 001。在使用者b的拼團邏輯執行完之前,使用者a的邏輯先執行完了,緊接著a就把鎖給釋放了。不過a的鎖早已經過期失效了,b持有鎖的key和a又完全一樣,所以此時a釋放的其實是b的鎖。這樣一來整個拼團還是有可能會超員。怎麼解決呢?

我們可以把分布式鎖的value設成可以區分的值,比如拼團的場景value可以設定為userid,在釋放鎖的時候根據key和value來判斷當前的鎖是不是自己的,只有redis中userid和自己的userid相同才釋放鎖。

改進後的**如下:

@component

public class distributedlock {

@autowired

private stringredistemplate redistemplate;

還有一種場景需要考慮。當redis master發生故障,主備切換時往往會造成資料丟失,包括分布式鎖的key-value 也可能丟失。這樣就會導致操作還沒執行完,鎖就被其他請求拿到了。redis官方提供了redlock演算法,以及相應的開源實現 redisson。用到分布式鎖的場景,大家可以直接使用 redisson,非常方便。如果系統對可靠性要求很高,如需用到分布式鎖,建議使用 zookeeper,etcd 等。

這些你都知道嗎?

如果身邊有胃不好的人,請分享給ta 1 最養胃的,麵條 2 如果熬粥,少放點蘇打進去,3 小公尺粥就饅頭,可以養胃。4 有兩種飲料應該多喝,一是牛奶,二是熱水。5 胃不好,要少食多餐。6 大棗 豆腐 白菜 牛奶 胡蘿蔔 健脾和胃。7 花生,蜂蜜都是養胃的。8 紅茶 蜂蜜.十分養胃。計 喝水 法 1 ...

這些python語句你知道嗎?

a i for i in range 1,5 print a 結果是 1,2,3,4 在用python寫 時,有時可能還沒想好函式怎麼寫,只寫了函式宣告,但為了保證語法正確,必須輸入一些東西,在這種情況下,我們會使用pass語句。def func args pass 同樣,break語句能讓我們跳出...

關於軟體文件 這些你知道嗎?

軟體文件 document 也稱檔案,通常指的是一些記錄的資料和資料 它具有固定不變的形式,可被人和計算機閱讀。它和 電腦程式共同構成了能完成特定功能的計算機軟體 有人把源程式也當作文件的一部分 我們知道,硬體產品和產品資料在整 個生產過程中都是有形可見的,軟體生產則有很大不同,文件本身就是軟體產品...