原文連接java
能堅持別人不能堅持的,才能擁有別人不曾擁有的。
關注編程大道
公衆號,讓咱們一同堅持心中所想,一塊兒成長!!web
使用Redis作K-V存儲,必定要注意過時時間的把控,任何K-V的存儲都要設置過時時間,無論多長時間。通常在封裝Redis操做工具類時提供默認使用系統公共超時時間的操做API,避免新手在使用時不設置過時時間,致使內存的浪費。另外,經過鏈接池 Jedis jedis = JedisPool.getResource(); 這樣獲取Redis鏈接最好使用try/finally塊,而且在finally塊中調用 jedis.close(); 將鏈接歸還給鏈接池,不然將會一直持有鏈接,頗有可能致使在未來的某一時刻報拿不到鏈接的錯。這也是以前某一個同事犯過的錯致使生產bug!面試
你覺得Redis作緩存就萬無一失嗎?就單純的遵循那種經典操做嗎?(即:請求來了,先看緩存有沒有,有直接返回,沒有就查數據庫,數據庫有的話先存緩存,而後返回,數據庫沒有就返回空)這樣就是Redis緩存的正確姿式嗎?若是你這樣作,極可能疏忽一點,那就是緩存穿透。如以前在項目中作的一個需求-頁面廣告可配置化自動上下線(我在以前專門寫過一篇文章介紹這個需求的一步步演進過程,對Redis新手頗有幫助,感興趣的能夠去看看),簡單的提一下吧,就是好比在支付完成的頁面你們都應該見過吧,好比支付完成後的結果頁,可能會彈出來一個紅包什麼的,頁面下方的廣告位等,就是相似的這樣一個需求。由於這個頁面訪問量很大,進這個頁面就查這個廣告位的數據,當運營最近不想配置廣告了,這邊查到的是否是就是是空啊?數據庫也是空的,緩存也沒有數據,那不少請求都來,這樣就無緣無故的形成了數據庫的壓力呀,多麼的浪費!若是是別的其餘業務,黑客鑽了空子,專門請求你係統根本不存在的數據,請求多了,都打到數據庫,是頗有可能把你數據庫打死的。若是你在作需求的時候沒想到這一點,那後續出了問題,你就等着背鍋了。redis
怎麼避免呢?算法
好辦,能夠將數據庫也不存在的數據存個null值或一個空json(總之你本身約定好就行),也給放到Redis裏,設置個較短的過時時間,下次再來取的時候看到是空就直接返回。另外,可使用布隆過濾器作一層系統級的防禦,專門去攔截系統中根本不存在的key。數據庫
剛說完緩存穿透,再聊聊緩存雪崩。好比你將用戶數據放到緩存裏,當某一時刻這些數據所有都過時了,大量請求都過來,發現緩存沒法命中,不就都去數據庫了嗎,數據庫一會兒來這麼多請求不就搞掛了嗎?解決辦法就是儘可能是key的過時時間分散開,不要集中。在一個固定的過時時間上+一個隨機值,好比你設置的過時時間是5小時,你能夠加一個0-600秒的隨機值。編程
緩存失效時多個請求同時請求同一個key,都發現緩存中空了,都去查數據庫,這不是浪費嗎,正常一個去查就好了,查完放緩存別的請求直接從緩存拿就好了。這就是緩存併發問題。當請求很是的多的時候,會對數據庫形成很大的衝擊,也是有可能把數據庫搞掛的吧?怎麼解決,能夠對更新緩存的操做加鎖,使用synchronized嗎?不行,由於生產上是分佈式部署的,須要使用redis分佈式鎖。json
例如,當緩存數據失效的時候,某一線程使用資源ID做爲key嘗試加分佈式鎖,加鎖成功的線程執行更新緩存的操做將查到的數據放入緩存緩存中,其餘線程就能夠直接使用緩存數據了。緩存
正如上面所說,在集羣部署的狀況下synchronized就失效了,因此分佈式鎖就派上用場了。常見的分佈式鎖的實現方式有三種:基於數據庫,基於Redis,基於Zookeeper。架構
Redis分佈式鎖須要特別注意的點就是鎖的過時時間,如,使用redis的setnx命令,設置成功即表示拿到鎖,而後設置過時時間,命令執行失敗的線程表示獲取鎖失敗。必定要注意鎖的過時時間的設置,有加鎖的操做,也要有解鎖的操做。如以前咱們項目的一個臨時性的一個組團競走的活動,10人成團競走PK的活動,在組團階段,用戶能夠邀請朋友加入本身的團。咱們的團數據是存放在Redis中的,包括每一個團的人數。當用戶發起入團操做時,後臺邏輯會從redis取該團的現有成員數,若是小於10才能繼續走下面的邏輯。當併發場景下,如團長分享給不少人入團邀請,這些人的入團請求併發執行的狀況下頗有可能能形成組團人數超過10人的狀況。由於在併發場景下,執行獲取當前團成員數的這行代碼會被多個請求獲取到,好比臨界的時候,團成員已經有了9個,同時來了倆入團請求,若是不加控制,同時執行讀取現有團成員個數時都讀到的是9,而後都執行入團操做,就會形成團成員超過10人的bug。
因此在入團請求的邏輯上,要加分佈式鎖,獲取到鎖才能執行後續邏輯。由於獲取鎖的操做是使用setnx命令,並無等待鎖的機制,咱們須要在獲取鎖的邏輯加一個自旋,每隔必定時間嘗試一次獲取,超過必定時間後返回加鎖失敗。
public boolean tryLock(String lockKey,long expireTime){
long waitTime = 0;
//setIfAbsent使用的是redis的setnx方法
boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey,"jingzouLock",
expireTime,TimeUnit.MILLISECONDS);
if(success==true){
return success;
}else{
while(success==false && waitTime <50000L){
success = redisTemplate.opsForValue().setIfAbsent(lockKey,"jingzouLock",
expireTime, TimeUnit.MILLISECONDS);
try{
Thread.sleep(100);
}catch(Exception e){}
waitTime+=100L;
}
}
return success;
}
複製代碼
另外,還須要遵循「解鈴還須繫鈴人」的原則,誰加的鎖誰解,否則本身加的鎖,被別人解了也是會形成問題的。例如,用戶A,請求入團,拿到分佈式鎖,若是A由於某些緣由在鎖超時時間內沒有執行完代碼,鎖就過時自動釋放了,若是此時B請求加入同一個團,拿到了分佈式鎖,若是此時A請求執行完了,釋放鎖了,可是釋放的是B的鎖,這樣也有可能形成團人數超過10的bug。因此,設置分佈式鎖時的value能夠設置成不一樣的值,如A請求是用戶ID爲12的用戶,設置分佈式鎖的時候就value就能夠用這個惟一的元素,當解鎖的時候再驗證value是12時才能執行解鎖操做。
如上加鎖代碼,咱們增長一個參數String value傳入動態值,在上述場景中能夠用用戶ID,代替咱們寫死的"jingzouLock"。而後在釋放鎖的方法裏,咱們先判斷value值,相同再執行刪除。
public void releaseLock(String lockKey,String value){
String valueInRedis = redisTemplate.get(lockKey);
if(value.equals(valueInRedis)){
redisTemplate.delete(lockKey);
}
}
複製代碼
還有一種場景須要考慮。當Redis master發生故障,主備切換時每每會形成數據丟失,包括分佈式鎖的Key-Value。這樣就會致使鎖間接的被釋放了,假如操做還沒執行完,鎖被其餘請求拿到了,分佈式鎖就起不到做用了。
考慮到這方面的問題,Redis官方提供了Redlock算法,以及相應的開源實現Redisson。用到分佈式鎖的場景,你們能夠直接使用 Redisson,很是方便,後期可能會寫一寫Redisson的技術乾貨。
另外,若是系統對可靠性要求很高,如需用到分佈式鎖,建議使用分佈式鎖的另外實現方式,如:Zookeeper,etcd等。
好了,今天就分享到這。若是感受本文對您有幫助,有勞點下在看,把知識分享給更多的人哦
以爲好看,請點贊哦~
關注公衆號 編程大道 ,第一時間獲文章推送。
以爲好看,請 點贊、關注、轉發 哦~