專欄前言
?? 本專欄開啟,目的在于幫助大家更好的掌握學(xué)習(xí)Redis
,同時(shí)也是為了記錄我自己學(xué)習(xí)Redis
的過程,將會(huì)從基礎(chǔ)的數(shù)據(jù)類型開始記錄,直到一些更多的應(yīng)用,如緩存擊穿還有分布式鎖等。希望大家有問題也可以一起溝通,歡迎一起學(xué)習(xí),對(duì)于專欄內(nèi)容有錯(cuò)還望您可以及時(shí)指點(diǎn),非常感謝大家 ??。
1.什么是分布式鎖?
??鎖這個(gè)東西,大家都知道,在我們 jvm
內(nèi)部多個(gè)線程競(jìng)爭(zhēng)同一個(gè)資源時(shí),我們利用jvm
提供的synchronized
或者一些其他的鎖可以幫助我們讓線程對(duì)資源的串行使用。但這種方法并不適合現(xiàn)在企業(yè)廣泛使用的分布式架構(gòu)。因?yàn)樵谶@種集群模式下,jvm
內(nèi)部的鎖無(wú)法被其他jvm
內(nèi)部感知到,那這樣肯定無(wú)法滿足我們的要求,因?yàn)殒i肯定是要被大家都能感知到的,所以分布式鎖應(yīng)用而生。以前的鎖競(jìng)爭(zhēng)對(duì)象是線程之間,而分布式系統(tǒng)中競(jìng)爭(zhēng)共享資源的單位從線程升級(jí)為了進(jìn)程。
2. 分布式鎖的條件
??那么作為一個(gè)分布式鎖,它應(yīng)該具備哪些條件呢?
- 可見性:多個(gè)進(jìn)程之間均可以看見該鎖,且可以嘗試獲取該鎖
- 互斥性:鎖最基本的特性,同一時(shí)間只能保證鎖被一個(gè)進(jìn)程持有
- 高可用性:也可以理解為容錯(cuò)性,當(dāng)提供鎖的服務(wù)結(jié)點(diǎn)產(chǎn)生故障時(shí),程序不會(huì)因?yàn)槭氐綇?qiáng)烈影響
- 高性能:鎖的釋放和添加本身十分消耗性能,我們應(yīng)選擇性能較好的鎖
3.常見的分布式鎖
??我們一般常見的分布式鎖,有以下三種:
-
MySQL:
MySQL
自帶鎖機(jī)制,但由于其性能一般,所以作為分布式鎖比較少見 -
Redis:
Redis
是分布式鎖一種非常常見的實(shí)現(xiàn)方式,我們可以利用setnx
這個(gè)方法,如果插入成功表示鎖獲取成功,否則獲取失敗。利用這個(gè)機(jī)制完成互斥,從而實(shí)現(xiàn)分布式鎖,而且Redis
存儲(chǔ)在內(nèi)存中本身就符合高性能特點(diǎn)。 -
Zookeeper:
Zookeeper
也是企業(yè)開發(fā)中較好的一種實(shí)現(xiàn)分布式鎖的方案,以后有機(jī)會(huì)講解。
4.Redis 實(shí)現(xiàn)分布式鎖
基于Redis
實(shí)現(xiàn)分布式鎖,我們使用兩個(gè)方法:
-
1.獲取鎖
該指令會(huì)插入一個(gè)結(jié)構(gòu)為lock:thread01
的鎖,且超時(shí)時(shí)間為100
秒,返回值為OK
說明獲取鎖成功,失敗則返回false
,該方法不會(huì)進(jìn)行阻塞。 -
2.釋放鎖
通過手動(dòng)刪除該鎖來(lái)進(jìn)行釋放,或者可以等待TTL
讓該鎖自動(dòng)過期
核心思路:
??利用Redis
的SETNX
方法,當(dāng)多個(gè)進(jìn)程同時(shí)競(jìng)爭(zhēng)該鎖時(shí),都會(huì)調(diào)用該方法獲取鎖,只有一個(gè)進(jìn)程成功能成功獲取成功,此時(shí)Redis
將會(huì)生成該鎖,其他進(jìn)程獲取失敗。那么獲取鎖成功的進(jìn)程將會(huì)去執(zhí)行業(yè)務(wù),最后刪除該鎖,這樣就將鎖釋放了。如果想讓獲取鎖失敗的進(jìn)程重新獲取,可以手動(dòng)休眠一段時(shí)間后重新獲取。
下面是一個(gè)簡(jiǎn)單的使用StringRedisTemplate
實(shí)現(xiàn)分布式鎖的代碼:
public class DistributedLock {
private StringRedisTemplate redisTemplate;
private String lockKey;
private String lockValue;
private long lockTimeout;
//構(gòu)造方法
public DistributedLock(StringRedisTemplate redisTemplate, String lockKey, String lockValue, long lockTimeout) {
this.redisTemplate = redisTemplate;
//key
this.lockKey = lockKey;
//value
this.lockValue = lockValue;
//過期時(shí)間
this.lockTimeout = lockTimeout;
}
public boolean tryLock() {
// 嘗試獲取鎖
Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, lockTimeout, TimeUnit.MILLISECONDS);
return result != null && result;
}
public void unlock() {
// 釋放鎖
redisTemplate.delete(lockKey);
}
}
5. 分布式鎖誤刪問題
??上面的邏輯看上去完美無(wú)缺,但還是存在很嚴(yán)重的問題,考慮下面一個(gè)場(chǎng)景:
邏輯說明:
??線程A
持有鎖執(zhí)行業(yè)務(wù)的時(shí)候發(fā)生了堵塞,導(dǎo)致他的鎖TTL
到期自動(dòng)釋放了,此時(shí)線程B
成功獲取到鎖了,因?yàn)榫€程A
已經(jīng)釋放該鎖了。這時(shí)候線程A
阻塞完畢后繼續(xù)執(zhí)行完業(yè)務(wù),然后刪除該鎖,線程B
執(zhí)行完業(yè)務(wù)時(shí)突然發(fā)現(xiàn)——woc 我鎖呢? 它的鎖被線程A
誤刪了,這就是分布式鎖誤刪問題。
解決方案:
??上訴問題的產(chǎn)生主要原因,還是因?yàn)槊總€(gè)線程并不知道該鎖是不是自己的,那我們可以在刪除鎖的時(shí)候去加以判斷,如果該鎖不屬于自己,則不刪除該鎖。如果該鎖是自己的且還未到期,再進(jìn)行刪除鎖。我們這里標(biāo)識(shí)一把鎖的時(shí)候同時(shí)存入線程的ID
,一般在同一個(gè)jvm
中線程的標(biāo)識(shí)一般不相同,但我們這是在集群模式下,所以也有可能出現(xiàn)ThreadID
重復(fù)的情況,所以我們可以考慮在前面拼接上一個(gè)UUID
。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-425650.html

private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";
@Override
public boolean tryLock(long timeoutSec) {
// 獲取線程標(biāo)識(shí)
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 獲取鎖
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
@Override
public void unlock() {
// 獲取當(dāng)前線程的標(biāo)識(shí)
String threadId = ID_PREFIX + Thread.currentThread().getId();
// 獲取鎖中的標(biāo)識(shí)
String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);
// 判斷標(biāo)識(shí)是否一致
if (threadId.equals(id)) {
// 釋放鎖
stringRedisTemplate.delete(KEY_PREFIX + name);
}
}
6. 分布式鎖原子性問題
??在進(jìn)行區(qū)分鎖的處理后,那么是不是一定不會(huì)產(chǎn)生問題呢?
??考慮一種更加極端的情況,當(dāng)線程 A
判斷完標(biāo)識(shí)發(fā)現(xiàn)一致后,準(zhǔn)備釋放鎖的時(shí)候又突然出現(xiàn)了阻塞情況(比如JVM
垃圾揮手),鎖又到期了,線程B
進(jìn)來(lái)拿了一把鎖,因?yàn)榫€程A
已經(jīng)判斷完標(biāo)識(shí),所以它一刪鎖又把B
的鎖給刪掉了,這就又產(chǎn)生了誤刪的問題。
??解決的方案需要我們使用Lua
腳本,來(lái)保證拿鎖、判斷標(biāo)識(shí)、刪鎖三個(gè)操作是一個(gè)原子性操作,而Lua
腳本可以同時(shí)執(zhí)行多條Redis
指令并且保證原子性,Lua
腳本是一門腳本語(yǔ)言,有興趣可以自行了解一下。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-425650.html
到了這里,關(guān)于【Redis從入門到進(jìn)階】第 7 講:基于 Redis 實(shí)現(xiàn)分布式鎖的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!