談談基於Redis分布式鎖(上) 手寫方案

2021-10-03 12:12:27 字數 3133 閱讀 6511

單體架構的應用可以直接使用本地鎖(synchronized)就可以解決多執行緒資源競爭的問題。如果公司業務發展較快,可以通過部署多個服務節點來提高系統的並行處理能力。由於本地鎖的作用範圍只限於當前應用的執行緒。高併發場景下,集群中某個應用的本地鎖並不會對其它應用的資源訪問產生互斥,就會產生資料不一致的問題,所以分布鎖就派上了用場

常見的分布式鎖應用場景:秒殺活動、優惠券搶購、介面冪等性校驗等

1、資料庫樂觀鎖

2、基於redis的分布式鎖

3、基於zookeeper的分布式鎖

本篇部落格主要介紹基於redis實現的分布式鎖

1、互斥性

任何時候鎖資源只能被乙個執行緒持有

2、死鎖

獲取鎖的客戶端由於某些原因未能釋放鎖,也要保證其他客戶端可以獲取到鎖

3、可重入

同一執行緒已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖

4、安全性

鎖只能被持有該鎖的客戶端刪除,不能由其它客戶端刪除

在高併發場景下,應用程式在執行過程中往往會受到網路、cpu、記憶體等因素的影響,所以實現乙個執行緒安全的分布式元件,往往需要考慮很多case,下面我們通過手寫的方式來探索乙個完美的分布鎖方案的複雜性

使用setnx()和uuid

/**

* 獲取鎖

*/public

static

boolean

getlock

(jedis jedis, string key,

int time)

return

false;}

/** * 釋放鎖

*/public

static

void

releaselock

(jedis jedis)

上面的**存在以下2個問題:

1、由於這是兩條redis命令,不具有原子性,如果程式在執行完setnx()之後突然崩潰,導致鎖沒有設定過期時間,那麼鎖資源將永遠不會被釋放

2、jedis.del(「object」)會導致操作超時的執行緒繼續執行時解鎖其它正在執行的執行緒鎖資源,可以通過給value賦值為requestid,我們就知道這把鎖是哪個請求加的,在解鎖的時候就可以區分了

requestid可以使用uuid.randomuuid().tostring()方法生成,修改**如下:

/**

* @param reqid 可用使用uuid生成

*/public

static

boolean

getlock

(jedis jedis, string reqid, string key,

int time)

return

false;}

/** * 釋放鎖

*/public

static

void

releaselock

(jedis jedis, string key, string reqid)

}public

static

void

main

(string[

] args)

}finally

}

解鎖好像還是有問題!

如果呼叫jedis.del()方法的時候,這把鎖已經不屬於當前執行緒的時候會解除其它執行緒加的鎖。

那麼是否真的有這種場景?

答案是肯定的,比如執行緒a在執行jedis.del()之前,鎖突然過期了,此時執行緒b嘗試加鎖成功,然後執行緒a再執行del()方法,則將執行緒b的鎖給刪除了! 原因是刪除操作分兩條命令去執行的,考慮使用lua指令碼原子操作來解決。關於lua指令碼的相關知識可以檢視其它博文

修改如下:

public

static

boolean

releaselock

(jedis jedis, string reqid, string key)

return

false

;}

使用setnx()和過期時間

執行過程如下:

通過setnx()方法嘗試加鎖,如果當前鎖不存在,返回加鎖成功。如果鎖已經存在(其它執行緒獲取成功)則獲取鎖的過期時間和當前時間比較。如果鎖已過期,則設定新的過期時間,返回加鎖成功。

public

static

boolean

getlock

(jedis jedis, string key,

int time)

// 鎖存在,獲取鎖的過期時間

string currentvalue = jedis.

get(key);if

(currentvalue != null && long.

parselong

(currentvalue)

< system.

currenttimemillis()

)}return

false

;}

以上方案有如下問題:

1、system.currenttimemillis()是不同的客戶端伺服器的系統時間,分布式環境下每個客戶端的時間必須同步。

2、鎖過期時,如果多個執行緒同時執行jedis.getset()方法,雖然只有乙個執行緒可以加鎖成功,但這個執行緒的鎖過期時間可能被其他執行緒所覆蓋

3、因為客戶端系統時間不同步,根據系統時間刪除鎖時會誤刪其它系統的鎖

手寫方案看似好像很完美,遺憾的是,還是有問題!!!

1、不滿足可重入性

2、a執行緒獲鎖成功,只是由於一些客觀因素(如:資料庫i/o偶爾過慢)操作延時,導致key失效,但是業務**還會是繼續往下執行。因為key失效的原因,其它執行緒可以繼續獲取到鎖,從某種意義上講還是沒有達到從根本上解決資源互斥的效果!

有什麼解決方案嗎?有,超時時間續期!可以通過定時任務延長失效時間(參考redisson的watch dog),但是實現起來有點麻煩。另外,可重入性問題也可以得到解決,比如:先將獲鎖成功的執行緒資訊暫存起來,再次請求鎖資源時判斷一下即可。

(完)下篇文章我們將介紹redisson的分布式鎖實現原理。

基於Redis實現分布式鎖

分布式鎖的基本功能 1.同一時刻只能存在乙個鎖 2.需要解決意外死鎖問題,也就是鎖能超時自動釋放 3.支援主動釋放鎖 分布式鎖解決什麼問題 多程序併發執行任務時,需要保證任務的有序性或者唯一性 準備 redis版本 2.6 redis是主從 sentinel模式 為了高可用 原理 redis2.6之...

基於redis分布式主鍵生成

idgenerator idgenerator idgenerator.builder addhost 127.0.0.1 6379,fce3758b2e0af6cbf8fea4d42b379cd0dc374418 addhost 127.0.0.1 7379,1abc55928f37176cb93...

基於redis的分布式鎖

public class redislock 加鎖 取到鎖加鎖,並返回值用於解鎖 取不到鎖則立即返回 1 param millitimeout 最長鎖定時間,超時後自動刪除鎖,避免死鎖 return public synchronized long lock long millitimeout lo...