一庵寞、前言
分布式鎖是一種用于協(xié)調(diào)分布式系統(tǒng)中多個節(jié)點之間對共享資源進行訪問控制的機制。它可以確保在分布式環(huán)境下墙杯,同一時間只有一個節(jié)點能夠獲取到鎖,并且其他節(jié)點需要等待釋放鎖后才能獲取光酣。
以下是使用分布式鎖的幾個常見場景和原因:
避免資源沖突:當多個節(jié)點需要同時對共享資源進行讀寫操作時,使用分布式鎖可以確保同一時間只有一個節(jié)點能夠執(zhí)行寫操作脉课,避免數(shù)據(jù)沖突和一致性問題救军。
防止重復(fù)處理:在某些業(yè)務(wù)場景中,可能會出現(xiàn)重復(fù)處理的問題倘零,例如訂單支付唱遭、秒殺等。使用分布式鎖可以確保同一時間只有一個節(jié)點能夠處理該任務(wù)呈驶,避免重復(fù)處理和產(chǎn)生臟數(shù)據(jù)拷泽。
控制資源并發(fā):某些資源的并發(fā)操作會導(dǎo)致性能問題,如數(shù)據(jù)庫的并發(fā)寫操作袖瞻。使用分布式鎖可以限制對資源的并發(fā)訪問司致,提高系統(tǒng)的穩(wěn)定性和性能。
避免死鎖:在分布式環(huán)境下聋迎,由于網(wǎng)絡(luò)延遲等原因脂矫,可能會發(fā)生死鎖的情況。使用分布式鎖可以避免死鎖問題的發(fā)生霉晕,確保資源的正確釋放庭再。
二捞奕、使用redisTemplate.opsForValue().setIfAbsent
1獲取鎖:
客戶端通過在Redis中設(shè)置一個特定的鍵,作為鎖的標識佩微,并設(shè)置過期時間以避免死鎖情況缝彬。這個操作可以通過Redis的SETNX命令來實現(xiàn)。如果SETNX命令返回1哺眯,表示鎖獲取成功,客戶端可以繼續(xù)執(zhí)行相應(yīng)的業(yè)務(wù)邏輯扒俯;如果返回0奶卓,表示鎖已經(jīng)被其他客戶端持有,客戶端需要等待或進行重試操作撼玄。
可以通過Redis的SET命令設(shè)置鎖的過期時間夺姑,以防止鎖一直被持有而導(dǎo)致死鎖。設(shè)置過期時間可以保證即使持有鎖的客戶端發(fā)生異常退出掌猛,鎖也會在過期時間后自動釋放盏浙。
2釋放鎖:
客戶端完成業(yè)務(wù)操作后,通過DEL命令刪除鎖的鍵荔茬,即可釋放鎖废膘。只有持有鎖的客戶端才能刪除鎖,以避免誤刪其他客戶端的鎖慕蔚。
下面是一個示例:
@Component
public class DistributedLock {
private static final String LOCK_KEY = "my_lock";
private static final long EXPIRE_TIME = 30000; // 鎖的過期時間丐黄,單位為毫秒
private static final long WAIT_TIME = 1000; // 獲取鎖時的等待時間,單位為毫秒
@Autowired
private StringRedisTemplate redisTemplate;
public boolean acquireLock() throws InterruptedException {
long start = System.currentTimeMillis();
while (true) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "locked", EXPIRE_TIME, TimeUnit.MILLISECONDS);
if (success != null && success) {
return true;
}
long current = System.currentTimeMillis();
if (current - start > WAIT_TIME) {
return false;
}
Thread.sleep(100); // 等待一段時間后進行重試
}
}
public void releaseLock() {
redisTemplate.delete(LOCK_KEY);
}
}
在上述代碼中孔飒,acquireLock方法嘗試獲取分布式鎖灌闺,如果成功獲取,則返回true坏瞄;如果超過等待時間仍未獲取到鎖桂对,則返回false。releaseLock方法用于釋放鎖鸠匀。
使用setIfAbsent的缺點
使用redisTemplate.opsForValue().setIfAbsent()方法實現(xiàn)分布式鎖存在以下缺點:
可靠性問題:使用redisTemplate.opsForValue().setIfAbsent()方法實現(xiàn)分布式鎖時蕉斜,需要手動編寫代碼來處理鎖的獲取和釋放,容易出現(xiàn)人為的錯誤狮崩,如忘記釋放鎖蛛勉、鎖的過期時間設(shè)置不正確等。而Redisson框架提供了更加可靠的分布式鎖實現(xiàn)睦柴,內(nèi)部封裝了各種功能的鎖诽凌,并提供了易于使用的API,能夠確保鎖的可靠性和正確性坦敌。
功能限制:redisTemplate.opsForValue().setIfAbsent()方法只能實現(xiàn)簡單的鎖功能侣诵,無法支持更復(fù)雜的功能痢法,如可重入鎖、公平鎖杜顺、紅鎖和讀寫鎖等财搁。而Redisson框架提供了豐富的分布式鎖實現(xiàn)方式,可以根據(jù)實際需求選擇適用的鎖類型躬络。
性能問題:redisTemplate.opsForValue().setIfAbsent()方法實現(xiàn)分布式鎖時尖奔,每次都需要與Redis服務(wù)器進行通信,可能會造成較高的網(wǎng)絡(luò)開銷和延遲穷当。而Redisson框架通過內(nèi)部的優(yōu)化和封裝提茁,能夠提供更高效的分布式鎖實現(xiàn),減少與Redis服務(wù)器的通信次數(shù)和網(wǎng)絡(luò)開銷馁菜。
可拓展性問題:使用redisTemplate.opsForValue().setIfAbsent()方法實現(xiàn)分布式鎖時茴扁,隨著業(yè)務(wù)的發(fā)展和變化,可能需要添加更多的功能和特性汪疮,而手動編寫的代碼可能無法滿足新的需求峭火。而Redisson框架提供了豐富的鎖實現(xiàn),同時也支持自定義鎖的擴展智嚷,能夠更好地適應(yīng)業(yè)務(wù)的變化和拓展卖丸。
三、使用redisson實現(xiàn)分布式鎖
通過Redisson框架可以方便地實現(xiàn)分布式鎖纤勒。Redisson是一個基于Redis的分布式Java對象和服務(wù)框架坯苹,提供了豐富的分布式鎖的實現(xiàn)方式。
要使用Redisson實現(xiàn)分布式鎖摇天,需要完成以下步驟:
1 引入Redisson依賴:在項目的pom.xml文件中添加Redisson的依賴粹湃。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.0</version>
</dependency>
2 創(chuàng)建RedissonClient對象:在Spring Boot中,可以通過Redisson的Spring支持來創(chuàng)建RedissonClient對象泉坐。
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return Redisson.create(config);
}
}
在上述代碼中为鳄,創(chuàng)建了一個RedissonClient對象,并配置了連接Redis的地址腕让。
3 實現(xiàn)分布式鎖:
使用RedissonClient對象獲取RLock對象孤钦,RLock是Redisson提供的分布式鎖接口。
通過RLock對象的lock方法來獲取鎖纯丸,并在獲取鎖成功后執(zhí)行業(yè)務(wù)邏輯偏形。
通過RLock對象的unlock方法來釋放鎖。
下面是一個示例:
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithLock() {
RLock lock = redissonClient.getLock("my_lock");
try {
lock.lock();
// 執(zhí)行業(yè)務(wù)邏輯...博客原文:https://blog.csdn.net/qq_27471405/article/details/134109185
} finally {
lock.unlock();
}
}
}
在上述代碼中觉鼻,executeWithLock方法通過redissonClient獲取了一個名為"my_lock"的鎖俊扭,并通過lock方法獲取鎖。在獲取鎖成功后坠陈,可以執(zhí)行業(yè)務(wù)邏輯萨惑。最后捐康,通過unlock方法釋放鎖。
Redisson還提供了其他一些功能強大的分布式鎖實現(xiàn)方式庸蔼,如可重入鎖解总、公平鎖、紅鎖姐仅、讀寫鎖等花枫。這些鎖的實現(xiàn)方式更加靈活和強大,可以根據(jù)實際需求進行選擇和使用萍嬉。
使用Redisson實現(xiàn)分布式鎖時乌昔,需要確保Redis服務(wù)器的可用性和穩(wěn)定性,以避免單點故障導(dǎo)致的鎖失效或鎖的不穩(wěn)定情況壤追。此外,還需要根據(jù)具體的應(yīng)用場景和需求供屉,合理設(shè)置鎖的過期時間行冰,避免鎖的長時間占用。
- 可重入鎖(Reentrant Lock):
可重入鎖是指同一個線程可以多次獲得同一個鎖伶丐,而不會發(fā)生死鎖悼做。Redisson的可重入鎖實現(xiàn)是基于Redis的分布式鎖的一種特例。
@Service
public class ReentrantLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithReentrantLock() {
RLock lock = redissonClient.getLock("my_lock");
try {
lock.lock();
// 執(zhí)行業(yè)務(wù)邏輯...小小魚兒小小林的博客測試
executeWithReentrantLock();
} finally {
lock.unlock();
}
}
}
在上述代碼中哗魂,使用redissonClient獲取了一個名為"my_lock"的可重入鎖肛走,并通過lock方法獲取鎖。在獲取鎖成功后录别,可以執(zhí)行業(yè)務(wù)邏輯朽色,包括遞歸調(diào)用executeWithReentrantLock方法。最后组题,通過unlock方法釋放鎖葫男。
- 公平鎖(Fair Lock):
公平鎖是指按照線程請求鎖的順序來分配鎖。Redisson的公平鎖實現(xiàn)可以保證多個線程按照先后順序獲取鎖崔列。
@Service
public class FairLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithFairLock() {
RLock lock = redissonClient.getFairLock("my_lock");
try {
lock.lock();
// 執(zhí)行業(yè)務(wù)邏輯...小小魚兒小小林的博客測試
} finally {
lock.unlock();
}
}
}
在上述代碼中梢褐,使用redissonClient獲取了一個名為"my_lock"的公平鎖,并通過lock方法獲取鎖赵讯。在獲取鎖成功后盈咳,可以執(zhí)行業(yè)務(wù)邏輯。最后边翼,通過unlock方法釋放鎖鱼响。
- 紅鎖(Red Lock):
紅鎖是指在多個Redis節(jié)點上獲取鎖,以提高分布式系統(tǒng)的可靠性和容錯性讯私。Redisson的紅鎖實現(xiàn)是基于Redis的分布式鎖的一種優(yōu)化方式热押。
@Service
public class RedLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithRedLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
redLock.lock();
// 執(zhí)行業(yè)務(wù)邏輯...
} finally {
redLock.unlock();
}
}
}
在上述代碼中西傀,使用redissonClient分別獲取了名為"lock1"、"lock2"和"lock3"的鎖桶癣,并通過RedissonRedLock將這些鎖組合成紅鎖拥褂。在獲取紅鎖成功后,可以執(zhí)行業(yè)務(wù)邏輯牙寞。最后饺鹃,通過unlock方法釋放紅鎖。
- 讀寫鎖(ReadWrite Lock):
讀寫鎖是指在多線程環(huán)境下间雀,對于讀操作可以并行進行悔详,對于寫操作必須互斥進行。Redisson的讀寫鎖實現(xiàn)提供了讀鎖和寫鎖兩種操作惹挟。
@Service
public class ReadWriteLockService {
@Autowired
private RedissonClient redissonClient;
public void readWithReadWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
RLock readLock = rwLock.readLock();
try {
readLock.lock();
// 執(zhí)行讀操作...
} finally {
readLock.unlock();
}
}
public void writeWithReadWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
RLock writeLock = rwLock.writeLock();
try {
writeLock.lock();
// 執(zhí)行寫操作...
} finally {
writeLock.unlock();
}
}
}
在上述代碼中茄螃,使用redissonClient獲取了一個名為"my_lock"的讀寫鎖,并通過readLock方法獲取讀鎖连锯,通過writeLock方法獲取寫鎖归苍。在獲取鎖成功后,可以執(zhí)行相應(yīng)的讀操作或?qū)懖僮髟瞬馈W詈笃雌ㄟ^unlock方法釋放鎖。
四摇展、遇到redis單點故障怎么辦
當使用Redisson實現(xiàn)分布式鎖時吻氧,如果遇到Redis服務(wù)器的單點故障,可以采取以下解決方案:
1 Redis Sentinel(哨兵模式):Redis Sentinel是Redis官方提供的高可用性解決方案咏连,它通過監(jiān)控Redis主節(jié)點和從節(jié)點的狀態(tài)盯孙,實現(xiàn)自動故障轉(zhuǎn)移和故障恢復(fù)。在使用Redisson時捻勉,可以配置Redis Sentinel來實現(xiàn)高可用性的Redis集群镀梭,在主節(jié)點故障時,Redis Sentinel會自動將從節(jié)點切換為主節(jié)點踱启,從而保證分布式鎖的可用性报账。
2 Redis Cluster(集群模式):Redis Cluster是Redis官方提供的分布式解決方案,通過將數(shù)據(jù)分散到多個節(jié)點上進行存儲和訪問埠偿,實現(xiàn)高可用性和橫向擴展透罢。使用Redisson時,可以配置Redis Cluster來搭建分布式鎖的集群冠蒋,當某個節(jié)點出現(xiàn)故障時羽圃,其他節(jié)點仍然可以正常工作,確保分布式鎖的可用性抖剿。
3 使用RedLock算法:RedLock算法是由Redis官方提出的一種多實例鎖機制朽寞,通過在多個獨立的Redis實例之間獲取鎖识窿,確保鎖的可靠性。在使用Redisson時脑融,可以使用RedLock算法來實現(xiàn)分布式鎖喻频,通過協(xié)調(diào)多個Redis實例之間的鎖獲取和釋放,即使部分實例發(fā)生故障肘迎,仍然可以保證鎖的可用性甥温。
4 引入其他高可用的中間件:除了Redis本身的高可用性解決方案,也可以考慮引入其他高可用的中間件妓布,如ZooKeeper姻蚓、etcd等。在使用Redisson時匣沼,可以將這些中間件作為分布式鎖的協(xié)調(diào)中心狰挡,用于進行鎖的獲取和釋放操作,以保證分布式鎖的可用性释涛。
以上解決方案都需要在配置和部署時做相應(yīng)的工作圆兵,如正確配置Redis Sentinel或Redis Cluster、合理設(shè)計Redis實例的數(shù)量和分布枢贿、選擇合適的RedLock算法實現(xiàn)等。同時刀脏,還需要在代碼實現(xiàn)中考慮異常處理和重試機制局荚,以應(yīng)對可能出現(xiàn)的故障和異常情況。