成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

經(jīng)常用Redis,這些坑你知道嗎?

數(shù)據(jù)庫(kù) 其他數(shù)據(jù)庫(kù) Redis
近些年,Redis憑借在性能、穩(wěn)定性和高可擴(kuò)展性上的卓越表現(xiàn),基本上已經(jīng)成了互聯(lián)網(wǎng)行業(yè)緩存中間件的標(biāo)配,甚至很多傳統(tǒng)行業(yè)也在使用Redis。那么我們?cè)谑褂肦edis等緩存中間件時(shí),要注意哪些問(wèn)題呢?本文咱們就來(lái)聊聊,我們使用緩存中間件過(guò)程中曾經(jīng)遇到的坑!

近些年,Redis憑借在性能、穩(wěn)定性和高可擴(kuò)展性上的卓越表現(xiàn),基本上已經(jīng)成了互聯(lián)網(wǎng)行業(yè)緩存中間件的標(biāo)配,甚至很多傳統(tǒng)行業(yè)也在使用Redis。那么我們?cè)谑褂肦edis等緩存中間件時(shí),要注意哪些問(wèn)題呢?本文咱們就來(lái)聊聊,我們使用緩存中間件過(guò)程中曾經(jīng)遇到的坑!

緩存穿透

先看一個(gè)常見(jiàn)的緩存使用方式。請(qǐng)求來(lái)了,先查緩存,緩存有值就直接返回;緩存沒(méi)值,查數(shù)據(jù)庫(kù),然后把數(shù)據(jù)庫(kù)的值存到緩存,再返回。

假如緩存沒(méi)查到某個(gè)值,查數(shù)據(jù)庫(kù)也沒(méi)這個(gè)值,也就是說(shuō)要查的值根本不存在,這樣就會(huì)導(dǎo)致每次對(duì)這個(gè)值的查詢請(qǐng)求都會(huì)穿透到數(shù)據(jù)庫(kù)。這就是所謂的“緩存穿透”。

如何避免緩存穿透?

如果從數(shù)據(jù)庫(kù)中沒(méi)查到值,可以在緩存中記錄一個(gè)空值,來(lái)避免“緩存穿透”。并且要給這個(gè)空值設(shè)置一個(gè)較短的過(guò)期時(shí)間。

比如說(shuō),我們經(jīng)常會(huì)把用戶信息緩存到Redis。如果調(diào)用方傳了一個(gè)不存在的UserID,在緩存中就查不到這個(gè)用戶信息,然后去DB也查不到。這樣就會(huì)導(dǎo)致,每次根據(jù)這個(gè)UserID查用戶信息,都會(huì)穿透到數(shù)據(jù)庫(kù),給數(shù)據(jù)庫(kù)造成了壓力。為了避免緩存穿透,當(dāng)數(shù)據(jù)庫(kù)查不到時(shí),我們可以在緩存中記錄一條空數(shù)據(jù),比如userID做為key,空json做為值,如果程序獲得這個(gè)空json,就按用戶不存在處理。再給這個(gè)key設(shè)置一個(gè)很短的過(guò)期時(shí)間,比如30秒。

緩存雪崩

我們經(jīng)常會(huì)遇到需要初始化緩存的情況。比如說(shuō)用戶系統(tǒng)重構(gòu),表結(jié)構(gòu)發(fā)生了變化,緩存信息也要變,上線前需要初始化緩存,將用戶信息批量存入緩存。假如我們給這些用戶信息設(shè)置相同的過(guò)期時(shí)間,到過(guò)期時(shí)間點(diǎn)所有用戶信息的緩存記錄就會(huì)同時(shí)集中失效,導(dǎo)致大量請(qǐng)求瞬間打到數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)很可能會(huì)被搞掛。這種緩存集中失效,導(dǎo)致大量請(qǐng)求同時(shí)穿透到數(shù)據(jù)庫(kù)的情況,就是所謂的“雪崩效應(yīng)”。

所以,當(dāng)我們向緩存初始化數(shù)據(jù)時(shí),要保證每個(gè)緩存記錄過(guò)期時(shí)間的離散性。可以采用一個(gè)較大的固定值加上一個(gè)較小的隨機(jī)值。比如過(guò)期時(shí)間可以是:10小時(shí) + 0到3600秒的隨機(jī)值。

緩存并發(fā)

當(dāng)系統(tǒng)并發(fā)很高,緩存數(shù)據(jù)尤其是熱點(diǎn)數(shù)據(jù)過(guò)期后,可能會(huì)出現(xiàn)多個(gè)請(qǐng)求同時(shí)訪問(wèn)數(shù)據(jù)庫(kù)并設(shè)置緩存的情況,不但給數(shù)據(jù)庫(kù)帶來(lái)壓力,而且會(huì)有緩存頻繁更新的問(wèn)題。

我們可以通過(guò)加鎖來(lái)避免緩存并發(fā)問(wèn)題。如果從緩存查不到數(shù)據(jù),對(duì)查詢數(shù)據(jù)加分布式鎖,然后查數(shù)據(jù)庫(kù)并把數(shù)據(jù)庫(kù)查詢結(jié)果放入緩存。其他線程等待鎖釋放后,直接從緩存取值。

比如,電商系統(tǒng)會(huì)緩存商品SKU價(jià)格,一些熱點(diǎn)商品的并發(fā)訪問(wèn)會(huì)非常高。當(dāng)緩存過(guò)期失效后,訪問(wèn)請(qǐng)求從緩存查不到記錄,此時(shí)可以用商品SKU ID為Key加分布式鎖,然后從數(shù)據(jù)庫(kù)查詢價(jià)格并把價(jià)格放入緩存,最后解鎖。解鎖后其他請(qǐng)求就可以從緩存直接取值了。從而避免了數(shù)據(jù)庫(kù)的壓力。

分布式鎖

以我們之前做過(guò)的5人拼團(tuán)為例。如果有用戶參加團(tuán)購(gòu),我們需要先校驗(yàn)參團(tuán)人數(shù)是否達(dá)到了上限5人。如果沒(méi)達(dá)到5人,用戶才可以參團(tuán)。偽代碼如下:

  1. //根據(jù)拼團(tuán)ID獲取目前參團(tuán)成員數(shù)量 
  2. int numOfMembers = pinTuanService.getNumOfMembersById(pinTuanID); 
  3. if(numOfMembers < 5) { 
  4.   pinTuanService.pintuan();//執(zhí)行,加入拼團(tuán),生單等邏輯 
  5. }  

高并發(fā)場(chǎng)景下,上面的代碼會(huì)有很嚴(yán)重的問(wèn)題。如果某個(gè)團(tuán)當(dāng)前的參團(tuán)人數(shù)是4,這時(shí)有兩個(gè)用戶同時(shí)參團(tuán),用戶A和用戶B的請(qǐng)求同時(shí)進(jìn)入上面的代碼塊,A和B的請(qǐng)求同時(shí)執(zhí)行到第2行代碼,獲取的numOfMembers都是4,表達(dá)式 numOfMembers < 5 成立,所以兩個(gè)用戶都能執(zhí)行到第4行代碼,就是說(shuō)A用戶和B用戶都能成功參加拼團(tuán)。于是,參團(tuán)人數(shù)就超過(guò)了5人的上限。所以我們就需要加鎖來(lái)避免這個(gè)問(wèn)題。synchronized行嗎?不行。因?yàn)槲覀兊姆?wù)是多節(jié)點(diǎn)部署的,所以要加分布式鎖。代碼如下:

  1. boolean aquired = distributedLock.aquireLock(pinTuanID, 3000); 
  2. if(aquired == true) { 
  3.   try{ 
  4.     //根據(jù)拼團(tuán)ID獲取目前參團(tuán)成員數(shù)量 
  5.     int numOfMembers = pinTuanService.getNumOfMembersById(pinTuanID); 
  6.     if(numOfMembers < 5) { 
  7.       pinTuanService.pintuan();//執(zhí)行,加入拼團(tuán),生單等邏輯 
  8.     }  
  9.   } finally { 
  10.     distributedLock.releaseLock(pinTuanID); 
  11.   } 

這樣就好多啦!接下來(lái)我們看看基于Redis分布式鎖的實(shí)現(xiàn),以及特別要注意的問(wèn)題。一般我們會(huì)基于setnx實(shí)現(xiàn)Redis分布式鎖。setnx命令可以檢查key是否存在,如果key不存在,就在Redis中創(chuàng)建一個(gè)鍵值對(duì)(操作成功),如果key已經(jīng)存在就放棄執(zhí)行(操作失敗)。

先看一段基于Springboot實(shí)現(xiàn)的加鎖和釋放鎖的代碼:

  1. @Component 
  2. public class DistributedLock { 
  3.  
  4.  @Autowired 
  5.  private StringRedisTemplate redisTemplate; 
  6.   
  7.  /** 
  8.  * 加鎖 
  9.  * lockKey,redis的key 
  10.  * expireTime,過(guò)期時(shí)間,單位是毫秒 
  11.  * 注:setIfAbsent方法就使用了redis的setnx 
  12.  */ 
  13.   public boolean aquireLock(String lockKey, long expireTime) { 
  14.    long waitTime = 0; 
  15.    boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, "distributedLock"
  16.                      expireTime, TimeUnit.MILLISECONDS); 
  17.    if(success == true){ 
  18.       return success;    
  19.    } else { 
  20.      //如果加鎖失敗,循環(huán)重試加鎖 
  21.      while(success != true && waitTime < 5000L ) { 
  22.        success = redisTemplate.opsForValue().setIfAbsent(lockKey, "distributedLock"
  23.                        expireTime, TimeUnit.MILLISECONDS); 
  24.        sleep 100毫秒;                 
  25.        waitTime += 100L; 
  26.      } 
  27.    } 
  28.     
  29.    return success; 
  30.  } 
  31.   
  32.  /** 
  33.  * 釋放鎖 
  34.  * lockKey,redis的key 
  35.  */ 
  36.  public void releaseLock(String lockKey) { 
  37.    redisTemplate.delete(lockKey); 
  38.  }  
  39.   

上面的代碼。乍一看,好像沒(méi)什么問(wèn)題!加鎖失敗有循環(huán)重試加鎖,過(guò)期時(shí)間設(shè)置了,而且也保證了創(chuàng)建Key-Value鍵值對(duì)和設(shè)置過(guò)期時(shí)間的原子性,這樣當(dāng)程序沒(méi)有正常釋放鎖時(shí),也能保證過(guò)期后鎖自動(dòng)釋放(注意:redis較老的版本不支持 setnx 和設(shè)置過(guò)期時(shí)間的原子操作,不過(guò)可以利用Lua腳本來(lái)保證原子性)。

我們?cè)僮屑?xì)思考一下,一般場(chǎng)景我們會(huì)對(duì)Key設(shè)置一個(gè)很短的過(guò)期時(shí)間,當(dāng)一次操作因?yàn)榫W(wǎng)絡(luò)等原因耗費(fèi)了較長(zhǎng)時(shí)間,操作還沒(méi)完成key就過(guò)期失效了。這樣會(huì)產(chǎn)生什么問(wèn)題呢?我們還是以拼團(tuán)為例加以說(shuō)明,先看看下面這張圖:

 

 

 

 

如上圖,用戶A和用戶B同時(shí)參加同一團(tuán),團(tuán)ID為 001,我們以團(tuán)ID作為分布式鎖的Key,"distributedLock" 作為固定的Value,過(guò)期時(shí)間是5秒。A先獲取分布式鎖,但是由于網(wǎng)絡(luò)等原因A的拼團(tuán)操作在5秒內(nèi)沒(méi)完成,這時(shí)Key過(guò)期并從Redis清除掉,A的分布式鎖失效。此時(shí)用戶B拿到分布式鎖,Key也同樣是團(tuán)ID 001。在用戶B的拼團(tuán)邏輯執(zhí)行完之前,用戶A的邏輯先執(zhí)行完了,緊接著A就把鎖給釋放了。不過(guò)A的鎖早已經(jīng)過(guò)期失效了,B持有鎖的Key和A又完全一樣,所以此時(shí)A釋放的其實(shí)是B的鎖。這樣一來(lái)整個(gè)拼團(tuán)還是有可能會(huì)超員。怎么解決呢?

我們可以把分布式鎖的Value設(shè)成可以區(qū)分的值,比如拼團(tuán)的場(chǎng)景Value可以設(shè)置為userID,在釋放鎖的時(shí)候根據(jù)key和value來(lái)判斷當(dāng)前的鎖是不是自己的,只有Redis中userID和自己的userID相同才釋放鎖。

改進(jìn)后的代碼如下:

  1. @Component 
  2. public class DistributedLock { 
  3.  
  4.  @Autowired 
  5.  private StringRedisTemplate redisTemplate; 
  6.   
  7.  /** 
  8.  * 加鎖 
  9.  * lockKey,redis的key 
  10.  * expireTime,過(guò)期時(shí)間,單位是毫秒 
  11.  * 注:setIfAbsent方法就使用了redis的setnx 
  12.  */ 
  13.   public boolean aquireLock(String lockKey, String userID, long expireTime) { 
  14.    long waitTime = 0; 
  15.    boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, userID, 
  16.                      expireTime, TimeUnit.MILLISECONDS); 
  17.    if(success == true){ 
  18.       return success;    
  19.    } else { 
  20.      //如果加鎖失敗,循環(huán)重試加鎖 
  21.      while(success != true && waitTime < 5000L ) { 
  22.        success = redisTemplate.opsForValue().setIfAbsent(lockKey, userID, 
  23.                        expireTime, TimeUnit.MILLISECONDS); 
  24.        sleep 100毫秒;                 
  25.        waitTime += 100L; 
  26.      } 
  27.    } 
  28.     
  29.    return success; 
  30.  } 
  31.   
  32.  /** 
  33.  * 釋放鎖 
  34.  * lockKey,redis的key 
  35.  */ 
  36.  public void releaseLock(String lockKey, String userID) { 
  37.    String userIDFromRedis = redisTemplate.get(lockKey); 
  38.    if( userID.equals(userIDFromRedis) ) { 
  39.      redisTemplate.delete(lockKey); 
  40.    } 
  41.  }  
  42.   

還有一種場(chǎng)景需要考慮。當(dāng)Redis master發(fā)生故障,主備切換時(shí)往往會(huì)造成數(shù)據(jù)丟失,包括分布式鎖的Key-Value 也可能丟失。這樣就會(huì)導(dǎo)致操作還沒(méi)執(zhí)行完,鎖就被其他請(qǐng)求拿到了。Redis官方提供了Redlock算法,以及相應(yīng)的開(kāi)源實(shí)現(xiàn) Redisson。用到分布式鎖的場(chǎng)景,大家可以直接使用 Redisson,非常方便。如果系統(tǒng)對(duì)可靠性要求很高,如需用到分布式鎖,建議使用 Zookeeper,etcd 等。 

 

 

責(zé)任編輯:龐桂玉 來(lái)源: 楊建榮的學(xué)習(xí)筆記
相關(guān)推薦

2020-10-28 11:20:55

vue項(xiàng)目技

2015-06-29 09:06:51

2020-11-18 07:52:08

2020-10-08 18:58:46

條件變量開(kāi)發(fā)線程

2022-09-14 08:11:06

分頁(yè)模糊查詢

2024-02-26 08:19:00

WebSpring容器

2015-07-03 11:20:41

編程學(xué)習(xí)方法

2016-01-11 09:48:07

2021-08-05 18:21:29

Autowired代碼spring

2023-01-16 08:09:51

SpringMVC句柄

2024-02-19 00:00:00

Console函數(shù)鏈接庫(kù)

2018-05-11 15:53:59

2016-03-18 19:03:35

認(rèn)知計(jì)算IBM

2021-11-10 15:37:49

Go源碼指令

2018-08-07 09:29:35

數(shù)據(jù)庫(kù)MySQL優(yōu)化方法

2022-11-10 09:00:41

2019-02-12 11:15:15

Spring設(shè)計(jì)模式Java

2018-02-06 09:40:25

PythonOOP繼承

2018-03-07 06:37:14

開(kāi)源項(xiàng)目源代碼云計(jì)算

2019-07-08 10:18:38

MPLSIP數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: av日韩高清| 作爱视频免费观看 | 成人精品鲁一区一区二区 | 日本色综合| julia中文字幕久久一区二区 | 久久蜜桃资源一区二区老牛 | 亚洲欧美久久 | 国产精品高清一区二区 | 日韩高清三区 | 视频在线h | 国产在线视频一区二区 | av在线一区二区 | 国内精品视频免费观看 | 黄色一级片aaa | 国产一区二区精 | 涩涩操| 免费精品视频 | 三级成人片 | 中文字幕一区二区三区四区五区 | 欧美一区二区三区在线免费观看 | 五月婷婷导航 | 日韩在线免费视频 | 人人澡人人爱 | www.99re| 国产日韩免费视频 | 久久久精品影院 | 本地毛片 | 精品久久久久久久久久久久久久 | 一级毛片中国 | 精品亚洲一区二区三区四区五区高 | 久久av一区| 日韩精品久久久久久 | a级大片免费观看 | 国产精品我不卡 | 99久久婷婷国产综合精品电影 | 成人在线播放网址 | 亚洲精品国产成人 | 亚洲一区二区三区四区五区中文 | 免费久久视频 | 亚洲啊v| 日韩欧美在线观看视频 |