新聞中心
分布式鎖是一種用于保證分布式系統(tǒng)中多個(gè)進(jìn)程或線程同步訪問(wèn)共享資源的技術(shù)。同時(shí)它又是面試中的常見(jiàn)問(wèn)題,所以我們本文就重點(diǎn)來(lái)看分布式鎖的具體實(shí)現(xiàn)(含實(shí)現(xiàn)代碼)。

在分布式系統(tǒng)中,由于各個(gè)節(jié)點(diǎn)之間的網(wǎng)絡(luò)通信延遲、故障等原因,可能會(huì)導(dǎo)致數(shù)據(jù)不一致的問(wèn)題。分布式鎖通過(guò)協(xié)調(diào)多個(gè)節(jié)點(diǎn)的行為,保證在任何時(shí)刻只有一個(gè)節(jié)點(diǎn)可以訪問(wèn)共享資源,以避免數(shù)據(jù)的不一致性和沖突。
1、分布式鎖要求
分布式鎖通常需要滿足以下幾個(gè)要求:
- 互斥性:在任意時(shí)刻只能有一個(gè)客戶端持有鎖。
- 不會(huì)發(fā)生死鎖:即使持有鎖的客戶端發(fā)生故障,也能保證鎖最終會(huì)被釋放。
- 具有容錯(cuò)性:分布式鎖需要能夠容忍節(jié)點(diǎn)故障等異常情況,保證系統(tǒng)的穩(wěn)定性。
2、實(shí)現(xiàn)方案
在 Java 中,實(shí)現(xiàn)分布式鎖的方案有多種,包括:
- 基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)的分布式鎖:可以通過(guò)數(shù)據(jù)庫(kù)的樂(lè)觀鎖或悲觀鎖實(shí)現(xiàn)分布式鎖,但是由于數(shù)據(jù)庫(kù)的 IO 操作比較慢,不適合高并發(fā)場(chǎng)景。
- 基于 ZooKeeper 實(shí)現(xiàn)的分布式鎖:ZooKeeper 是一個(gè)高可用性的分布式協(xié)調(diào)服務(wù),可以通過(guò)它來(lái)實(shí)現(xiàn)分布式鎖。但是使用 ZooKeeper 需要部署額外的服務(wù),增加了系統(tǒng)復(fù)雜度。
- 基于 Redis 實(shí)現(xiàn)的分布式鎖:Redis 是一個(gè)高性能的內(nèi)存數(shù)據(jù)庫(kù),支持分布式部署,可以通過(guò)Redis的原子操作實(shí)現(xiàn)分布式鎖,而且具有高性能和高可用性。
3、數(shù)據(jù)庫(kù)分布式鎖
數(shù)據(jù)庫(kù)的樂(lè)觀鎖或悲觀鎖都可以實(shí)現(xiàn)分布式鎖,下面分別來(lái)看。
(1)悲觀鎖
在數(shù)據(jù)庫(kù)中使用 for update 關(guān)鍵字可以實(shí)現(xiàn)悲觀鎖,我們?cè)?Mapper 中添加 for update 即可對(duì)數(shù)據(jù)加鎖,實(shí)現(xiàn)代碼如下:
在 Service 中調(diào)用 Mapper 方法,即可獲取到加鎖的數(shù)據(jù):
@Transactional
public void updateWithPessimisticLock(int id, String name) {
User user = userMapper.selectByIdForUpdate(id);
if (user != null) {
user.setName(name);
userMapper.update(user);
} else {
throw new RuntimeException("數(shù)據(jù)不存在");
}
}(2)樂(lè)觀鎖
在 MyBatis 中,可以通過(guò)給表添加一個(gè)版本號(hào)字段來(lái)實(shí)現(xiàn)樂(lè)觀鎖。在 Mapper 中,使用標(biāo)簽定義更新語(yǔ)句,同時(shí)使用 set 標(biāo)簽設(shè)置版本號(hào)的增量。
UPDATE user SET
name = #{name},
version = version + 1
WHERE id = #{id} AND version = #{version}
在 Service 中調(diào)用 Mapper 方法,需要傳入更新數(shù)據(jù)的版本號(hào)。如果更新失敗,說(shuō)明數(shù)據(jù)已經(jīng)被其他事務(wù)修改,具體實(shí)現(xiàn)代碼如下:
@Transactional
public void updateWithOptimisticLock(int id, String name, int version) {
User user = userMapper.selectById(id);
if (user != null) {
user.setName(name);
user.setVersion(version);
int rows = userMapper.updateWithOptimisticLock(user);
if (rows == 0) {
throw new RuntimeException("數(shù)據(jù)已被其他事務(wù)修改");
}
} else {
throw new RuntimeException("數(shù)據(jù)不存在");
}
}4、Zookeeper 分布式鎖
在 Spring Boot 中,可以使用 Curator 框架來(lái)實(shí)現(xiàn) ZooKeeper 分布式鎖,具體實(shí)現(xiàn)分為以下 3 步:
- 引入 Curator 和 ZooKeeper 客戶端依賴;
- 配置 ZooKeeper 連接信息;
- 編寫分布式鎖實(shí)現(xiàn)類。
(1)引入 Curator 和 ZooKeeper
org.apache.curator
curator-framework
latest
org.apache.curator
curator-recipes
latest
org.apache.zookeeper
zookeeper
latest
(2)配置 ZooKeeper 連接
在 application.yml 中添加 ZooKeeper 連接配置:
spring:
zookeeper:
connect-string: localhost:2181
namespace: demo(3)編寫分布式鎖實(shí)現(xiàn)類
@Component
public class DistributedLock {
@Autowired
private CuratorFramework curatorFramework;
/**
* 獲取分布式鎖
*
* @param lockPath 鎖路徑
* @param waitTime 等待時(shí)間
* @param leaseTime 鎖持有時(shí)間
* @param timeUnit 時(shí)間單位
* @return 鎖對(duì)象
* @throws Exception 獲取鎖異常
*/
public InterProcessMutex acquire(String lockPath, long waitTime, long leaseTime, TimeUnit timeUnit) throws Exception {
InterProcessMutex lock = new InterProcessMutex(curatorFramework, lockPath);
if (!lock.acquire(waitTime, timeUnit)) {
throw new RuntimeException("獲取分布式鎖失敗");
}
if (leaseTime > 0) {
lock.acquire(leaseTime, timeUnit);
}
return lock;
}
/**
* 釋放分布式鎖
*
* @param lock 鎖對(duì)象
* @throws Exception 釋放鎖異常
*/
public void release(InterProcessMutex lock) throws Exception {
if (lock != null) {
lock.release();
}
}
}5、Redis 分布式鎖
我們可以使用 Redis 客戶端 Redisson 實(shí)現(xiàn)分布式鎖,它的實(shí)現(xiàn)步驟如下:
- 添加 Redisson 依賴
- 配置 Redisson 連接信息
- 編寫分布式鎖代碼類
(1)添加 Redisson 依賴
在 pom.xml 中添加如下配置:
org.redisson
redisson-spring-boot-starter
3.20.0
(2)配置 Redisson 連接
在 Spring Boot 項(xiàng)目的配置文件 application.yml 中添加 Redisson 配置:
spring:
data:
redis:
host: localhost
port: 6379
database: 0
redisson:
codec: org.redisson.codec.JsonJacksonCodec
single-server-config:
address: "redis://${spring.data.redis.host}:${spring.redis.port}"
database: "${spring.data.redis.database}"
password: "${spring.data.redis.password}"(3)編寫分布式鎖代碼類
import jakarta.annotation.Resource;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedissonLockService {
@Resource
private Redisson redisson;
/**
* 加鎖
*
* @param key 分布式鎖的 key
* @param timeout 超時(shí)時(shí)間
* @param unit 時(shí)間單位
* @return
*/
public boolean tryLock(String key, long timeout, TimeUnit unit) {
RLock lock = redisson.getLock(key);
try {
return lock.tryLock(timeout, unit);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
/**
* 釋放分布式鎖
*
* @param key 分布式鎖的 key
*/
public void unlock(String key) {
RLock lock = redisson.getLock(key);
lock.unlock();
}
}6、Redis VS Zookeeper
Redis 和 ZooKeeper 都可以用來(lái)實(shí)現(xiàn)分布式鎖,它們?cè)趯?shí)現(xiàn)分布式鎖的機(jī)制和原理上有所不同,具體區(qū)別如下:
- 數(shù)據(jù)存儲(chǔ)方式:Redis 將鎖信息存儲(chǔ)在內(nèi)存中,而 ZooKeeper 將鎖信息存儲(chǔ)在 ZooKeeper 的節(jié)點(diǎn)上,因此 ZooKeeper 需要更多的磁盤空間。
- 鎖的釋放:Redis 的鎖是通過(guò)設(shè)置鎖的過(guò)期時(shí)間來(lái)自動(dòng)釋放的,而 ZooKeeper 的鎖需要手動(dòng)釋放,如果鎖的持有者出現(xiàn)宕機(jī)或網(wǎng)絡(luò)中斷等情況,需要等待鎖的超時(shí)時(shí)間才能自動(dòng)釋放。
- 鎖的競(jìng)爭(zhēng)機(jī)制:Redis 使用的是單機(jī)鎖,即所有請(qǐng)求都直接連接到同一臺(tái) Redis 服務(wù)器,容易發(fā)生單點(diǎn)故障;而 ZooKeeper 使用的是分布式鎖,即所有請(qǐng)求都連接到 ZooKeeper 集群,具有較好的可用性和可擴(kuò)展性。
- 一致性:Redis 的鎖是非嚴(yán)格意義下的分布式鎖,因?yàn)樵诙嗯_(tái)機(jī)器上運(yùn)行多個(gè)進(jìn)程時(shí),由于 Redis 的主從同步可能會(huì)存在數(shù)據(jù)不一致的問(wèn)題;而 ZooKeeper 是強(qiáng)一致性的分布式系統(tǒng),保證了數(shù)據(jù)的一致性。
- 性能:Redis 的性能比 ZooKeeper 更高,因?yàn)?Redis 將鎖信息存儲(chǔ)在內(nèi)存中,而 ZooKeeper 需要進(jìn)行磁盤讀寫操作。
總之,Redis 適合實(shí)現(xiàn)簡(jiǎn)單的分布式鎖場(chǎng)景,而 ZooKeeper 適合實(shí)現(xiàn)復(fù)雜的分布式協(xié)調(diào)場(chǎng)景,也就是 ZooKeeper 適合強(qiáng)一致性的分布式系統(tǒng)。
“強(qiáng)一致性是指系統(tǒng)中的所有節(jié)點(diǎn)在任何時(shí)刻看到的數(shù)據(jù)都是一致的。ZooKeeper 中的數(shù)據(jù)是有序的樹(shù)形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都有唯一的路徑標(biāo)識(shí)符,所有節(jié)點(diǎn)都共享同一份數(shù)據(jù),當(dāng)任何一個(gè)節(jié)點(diǎn)對(duì)數(shù)據(jù)進(jìn)行修改時(shí),所有節(jié)點(diǎn)都會(huì)收到通知,更新數(shù)據(jù),并確保數(shù)據(jù)的一致性。在 ZooKeeper 中,強(qiáng)一致性體現(xiàn)在數(shù)據(jù)的讀寫操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)協(xié)議來(lái)保證數(shù)據(jù)的一致性,該協(xié)議確保了數(shù)據(jù)更新的順序,所有的數(shù)據(jù)更新都需要經(jīng)過(guò)集群中的大多數(shù)節(jié)點(diǎn)確認(rèn),保證了數(shù)據(jù)的一致性和可靠性。”
小結(jié)
在 Java 中,使用數(shù)據(jù)庫(kù)、ZooKeeper 和 Redis 都可以實(shí)現(xiàn)分布式鎖。但數(shù)據(jù)庫(kù) IO 操作比較慢,不適合高并發(fā)場(chǎng)景;Redis 執(zhí)行效率最高,但在主從切換時(shí),可能會(huì)出現(xiàn)鎖丟失的情況;ZooKeeper 是一個(gè)高可用性的分布式協(xié)調(diào)服務(wù),可以保證數(shù)據(jù)的強(qiáng)一致性,但是使用 ZooKeeper 需要部署額外的服務(wù),增加了系統(tǒng)復(fù)雜度。所以沒(méi)有最好的解決方案,只有最合適自己的解決方案。
新聞名稱:分布式鎖的三種實(shí)現(xiàn)!
當(dāng)前URL:http://www.dlmjj.cn/article/dhedjej.html


咨詢
建站咨詢
