新聞中心
深入理解redis分布式鎖
哈嘍,大家好,我是指北君。

成都創(chuàng)新互聯(lián)公司長(zhǎng)期為超過(guò)千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為濰城企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、做網(wǎng)站,濰城網(wǎng)站改版等技術(shù)服務(wù)。擁有十載豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。
本篇文件我們來(lái)介紹如何Redis實(shí)現(xiàn)分布式鎖的演進(jìn)過(guò)程,以及為什么不能直接用Setnx實(shí)現(xiàn)分布式鎖。
1、分布式鎖簡(jiǎn)介
分布式鎖是控制分布式系統(tǒng)不同進(jìn)程共同訪問(wèn)共享資源的一種鎖的實(shí)現(xiàn)。如果不同的系統(tǒng)或同一個(gè)系統(tǒng)的不同主機(jī)之間共享了某個(gè)臨界資源,往往需要互斥來(lái)防止彼此干擾,以保證一致性。
業(yè)界流行的分布式鎖實(shí)現(xiàn),一般有這3種方式:
- 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)的分布式鎖
- 基于Redis實(shí)現(xiàn)的分布式鎖
- 基于Zookeeper實(shí)現(xiàn)的分布式鎖
這里主要介紹如何通過(guò) Redis 來(lái)實(shí)現(xiàn)分布式鎖。在介紹 Redis 分布式鎖之前,我們首先介紹一下實(shí)現(xiàn)Redis 分布式鎖的關(guān)鍵命令。
2、setnx
setnx key value
Setnx(SET if Not eXists) 命令在指定的 key 不存在時(shí),為 key 設(shè)置指定的值。
設(shè)置成功,返回 1 。設(shè)置失敗,返回 0 。
PS:Redis 官方是不推薦基于 setnx 命令來(lái)實(shí)現(xiàn)分布式鎖的,因?yàn)闀?huì)存在很多問(wèn)題,
①、單點(diǎn)問(wèn)題。比如:
- 客戶端A 從master拿到鎖lock01
- master正要把lock01同步(Redis的主從同步通常是異步的)給slave時(shí),突然宕機(jī)了,導(dǎo)致lock01沒(méi)同步給slave
- 主從切換,slave節(jié)點(diǎn)被晉級(jí)為master節(jié)點(diǎn)
- 客戶端B到master拿lock01照樣能拿到。這樣必將導(dǎo)致同一把鎖被多人使用。
②、鎖的高級(jí)用法,比如讀寫鎖、可重入鎖等等,setnx 都比較難實(shí)現(xiàn)。
這里先介紹基于 sentnx 實(shí)現(xiàn)的分布式鎖,后面會(huì)介紹官方推薦的基于 redisson 來(lái)實(shí)現(xiàn)分布式鎖。
3、Redis-分布式鎖-階段1
接到上文,查詢?nèi)?jí)分類數(shù)據(jù),如果我們部署了多個(gè)商品服務(wù),然后多個(gè)線程同時(shí)去獲取三級(jí)分類數(shù)據(jù),如果不加分布式鎖,就會(huì)導(dǎo)致,每一個(gè)部署的商品服務(wù)第一次查詢都會(huì)走 DB。
public Map> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、獲取分布式鎖
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock){
// true 表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù)
Map> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("獲取分布式鎖失敗...等待重試...");
//加鎖失敗...重試機(jī)制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
4、Redis-分布式鎖-階段2
設(shè)置鎖自動(dòng)過(guò)期
public Map> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、獲取分布式鎖
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111");
if(lock){
// true 表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù)
// 設(shè)置過(guò)期時(shí)間
stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("獲取分布式鎖失敗...等待重試...");
//加鎖失敗...重試機(jī)制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
5、Redis-分布式鎖-階段3
setnx 命令和過(guò)期時(shí)間保證原子性。
public Map> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、獲取分布式鎖
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "111",30,TimeUnit.SECONDS);
if(lock){
// true 表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù)
// 設(shè)置過(guò)期時(shí)間
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map> dataFromDb = getDataFromDb();
stringRedisTemplate.delete("lock");
return dataFromDb;
}else{
System.out.println("獲取分布式鎖失敗...等待重試...");
//加鎖失敗...重試機(jī)制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
6、Redis-分布式鎖-階段4
保證刪除的是自己的鎖。
public Map> getCatelogJsonWithRedisLock() throws InterruptedException {
// 一、獲取分布式鎖
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
if(lock){
// true 表示加鎖成功,執(zhí)行相關(guān)業(yè)務(wù)
// 設(shè)置過(guò)期時(shí)間
//stringRedisTemplate.expire("lock",30,TimeUnit.SECONDS);
Map> dataFromDb = getDataFromDb();
String lockValue = stringRedisTemplate.opsForValue().get("lock");
if(uuid.equals(lockValue)){
stringRedisTemplate.delete("lock");
}
return dataFromDb;
}else{
System.out.println("獲取分布式鎖失敗...等待重試...");
//加鎖失敗...重試機(jī)制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
7、Redis-分布式鎖-階段5
通過(guò)Lua腳本保證刪除鎖和判斷鎖兩個(gè)操作原子性
public Map> getCatelogJsonWithRedisLock(){
// 一、獲取分布式鎖
String uuid = UUID.randomUUID().toString();
Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,30,TimeUnit.SECONDS);
if (lock) {
System.out.println("獲取分布式鎖成功...");
Map> dataFromDb = null;
try {
//加鎖成功...執(zhí)行業(yè)務(wù)
dataFromDb = getDataFromDb();
} finally {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//刪除鎖
stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class), Arrays.asList("lock"), uuid);
}
//先去redis查詢下保證當(dāng)前的鎖是自己的
//獲取值對(duì)比,對(duì)比成功刪除=原子性 lua腳本解鎖
// String lockValue = stringRedisTemplate.opsForValue().get("lock");
// if (uuid.equals(lockValue)) {
// //刪除我自己的鎖
// stringRedisTemplate.delete("lock");
// }
return dataFromDb;
}else{
System.out.println("獲取分布式鎖失敗...等待重試...");
//加鎖失敗...重試機(jī)制
//休眠一百毫秒
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//自旋的方式
return getCatelogJsonWithRedisLock();
}
}
這也是分布式鎖的最終模式,需要保證兩個(gè)點(diǎn):加鎖【設(shè)置鎖+過(guò)期時(shí)間】和刪除鎖【判斷+刪除】原子性。
本文題目:深入了解redis分布式鎖
網(wǎng)址分享:http://m.5511xx.com/article/dhdoeij.html


咨詢
建站咨詢
