自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

分布式鎖的三種實(shí)現(xiàn)!

開發(fā) 后端 Redis
在 Java 中,使用數(shù)據(jù)庫、ZooKeeper 和 Redis 都可以實(shí)現(xiàn)分布式鎖。但數(shù)據(jù)庫 IO 操作比較慢,不適合高并發(fā)場景;Redis 執(zhí)行效率最高,但在主從切換時,可能會出現(xiàn)鎖丟失的情況;ZooKeeper 是一個高可用性的分布式協(xié)調(diào)服務(wù),可以保證數(shù)據(jù)的強(qiáng)一致性,但是使用 ZooKeeper 需要部署額外的服務(wù),增加了系統(tǒng)復(fù)雜度。所以沒有最好的解決方案,只有最合適自己的解決方案。

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

在分布式系統(tǒng)中,由于各個節(jié)點(diǎn)之間的網(wǎng)絡(luò)通信延遲、故障等原因,可能會導(dǎo)致數(shù)據(jù)不一致的問題。分布式鎖通過協(xié)調(diào)多個節(jié)點(diǎn)的行為,保證在任何時刻只有一個節(jié)點(diǎn)可以訪問共享資源,以避免數(shù)據(jù)的不一致性和沖突。

1、分布式鎖要求

分布式鎖通常需要滿足以下幾個要求:

  1. 互斥性:在任意時刻只能有一個客戶端持有鎖。
  2. 不會發(fā)生死鎖:即使持有鎖的客戶端發(fā)生故障,也能保證鎖最終會被釋放。
  3. 具有容錯性:分布式鎖需要能夠容忍節(jié)點(diǎn)故障等異常情況,保證系統(tǒng)的穩(wěn)定性。

2、實(shí)現(xiàn)方案

在 Java 中,實(shí)現(xiàn)分布式鎖的方案有多種,包括:

  • 基于數(shù)據(jù)庫實(shí)現(xiàn)的分布式鎖:可以通過數(shù)據(jù)庫的樂觀鎖或悲觀鎖實(shí)現(xiàn)分布式鎖,但是由于數(shù)據(jù)庫的 IO 操作比較慢,不適合高并發(fā)場景。
  • 基于 ZooKeeper 實(shí)現(xiàn)的分布式鎖:ZooKeeper 是一個高可用性的分布式協(xié)調(diào)服務(wù),可以通過它來實(shí)現(xiàn)分布式鎖。但是使用 ZooKeeper 需要部署額外的服務(wù),增加了系統(tǒng)復(fù)雜度。
  • 基于 Redis 實(shí)現(xiàn)的分布式鎖:Redis 是一個高性能的內(nèi)存數(shù)據(jù)庫,支持分布式部署,可以通過Redis的原子操作實(shí)現(xiàn)分布式鎖,而且具有高性能和高可用性。

3、數(shù)據(jù)庫分布式鎖

數(shù)據(jù)庫的樂觀鎖或悲觀鎖都可以實(shí)現(xiàn)分布式鎖,下面分別來看。

(1)悲觀鎖

在數(shù)據(jù)庫中使用 for update 關(guān)鍵字可以實(shí)現(xiàn)悲觀鎖,我們在 Mapper 中添加 for update 即可對數(shù)據(jù)加鎖,實(shí)現(xiàn)代碼如下:

<!-- UserMapper.xml -->
<select id="selectByIdForUpdate" resultType="User">
    SELECT * FROM user WHERE id = #{id} FOR UPDATE
</select>

在 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)樂觀鎖

在 MyBatis 中,可以通過給表添加一個版本號字段來實(shí)現(xiàn)樂觀鎖。在 Mapper 中,使用標(biāo)簽定義更新語句,同時使用 set 標(biāo)簽設(shè)置版本號的增量。

<!-- UserMapper.xml -->
<update id="updateWithOptimisticLock">
    UPDATE user SET
    name = #{name},
    version = version + 1
    WHERE id = #{id} AND version = #{version}
</update>

在 Service 中調(diào)用 Mapper 方法,需要傳入更新數(shù)據(jù)的版本號。如果更新失敗,說明數(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 框架來實(shí)現(xiàn) ZooKeeper 分布式鎖,具體實(shí)現(xiàn)分為以下 3 步:

  1. 引入 Curator 和 ZooKeeper 客戶端依賴;
  2. 配置 ZooKeeper 連接信息;
  3. 編寫分布式鎖實(shí)現(xiàn)類。

(1)引入 Curator 和 ZooKeeper

<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>latest</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>latest</version>
</dependency>
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>latest</version>
</dependency>

(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   等待時間
     * @param leaseTime  鎖持有時間
     * @param timeUnit   時間單位
     * @return 鎖對象
     * @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 鎖對象
     * @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 中添加如下配置:

<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>

(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 超時時間
     * @param unit    時間單位
     * @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 都可以用來實(shí)現(xiàn)分布式鎖,它們在實(shí)現(xiàn)分布式鎖的機(jī)制和原理上有所不同,具體區(qū)別如下:

  • 數(shù)據(jù)存儲方式:Redis 將鎖信息存儲在內(nèi)存中,而 ZooKeeper 將鎖信息存儲在 ZooKeeper 的節(jié)點(diǎn)上,因此 ZooKeeper 需要更多的磁盤空間。
  • 鎖的釋放:Redis 的鎖是通過設(shè)置鎖的過期時間來自動釋放的,而 ZooKeeper 的鎖需要手動釋放,如果鎖的持有者出現(xiàn)宕機(jī)或網(wǎng)絡(luò)中斷等情況,需要等待鎖的超時時間才能自動釋放。
  • 鎖的競爭機(jī)制:Redis 使用的是單機(jī)鎖,即所有請求都直接連接到同一臺 Redis 服務(wù)器,容易發(fā)生單點(diǎn)故障;而 ZooKeeper 使用的是分布式鎖,即所有請求都連接到 ZooKeeper 集群,具有較好的可用性和可擴(kuò)展性。
  • 一致性:Redis 的鎖是非嚴(yán)格意義下的分布式鎖,因?yàn)樵诙嗯_機(jī)器上運(yùn)行多個進(jìn)程時,由于 Redis 的主從同步可能會存在數(shù)據(jù)不一致的問題;而 ZooKeeper 是強(qiáng)一致性的分布式系統(tǒng),保證了數(shù)據(jù)的一致性。
  • 性能:Redis 的性能比 ZooKeeper 更高,因?yàn)?Redis 將鎖信息存儲在內(nèi)存中,而 ZooKeeper 需要進(jìn)行磁盤讀寫操作。

總之,Redis 適合實(shí)現(xiàn)簡單的分布式鎖場景,而 ZooKeeper 適合實(shí)現(xiàn)復(fù)雜的分布式協(xié)調(diào)場景,也就是 ZooKeeper 適合強(qiáng)一致性的分布式系統(tǒng)。

強(qiáng)一致性是指系統(tǒng)中的所有節(jié)點(diǎn)在任何時刻看到的數(shù)據(jù)都是一致的。ZooKeeper 中的數(shù)據(jù)是有序的樹形結(jié)構(gòu),每個節(jié)點(diǎn)都有唯一的路徑標(biāo)識符,所有節(jié)點(diǎn)都共享同一份數(shù)據(jù),當(dāng)任何一個節(jié)點(diǎn)對數(shù)據(jù)進(jìn)行修改時,所有節(jié)點(diǎn)都會收到通知,更新數(shù)據(jù),并確保數(shù)據(jù)的一致性。在 ZooKeeper 中,強(qiáng)一致性體現(xiàn)在數(shù)據(jù)的讀寫操作上。ZooKeeper 使用 ZAB(ZooKeeper Atomic Broadcast)協(xié)議來保證數(shù)據(jù)的一致性,該協(xié)議確保了數(shù)據(jù)更新的順序,所有的數(shù)據(jù)更新都需要經(jīng)過集群中的大多數(shù)節(jié)點(diǎn)確認(rèn),保證了數(shù)據(jù)的一致性和可靠性。

小結(jié)

在 Java 中,使用數(shù)據(jù)庫、ZooKeeper 和 Redis 都可以實(shí)現(xiàn)分布式鎖。但數(shù)據(jù)庫 IO 操作比較慢,不適合高并發(fā)場景;Redis 執(zhí)行效率最高,但在主從切換時,可能會出現(xiàn)鎖丟失的情況;ZooKeeper 是一個高可用性的分布式協(xié)調(diào)服務(wù),可以保證數(shù)據(jù)的強(qiáng)一致性,但是使用 ZooKeeper 需要部署額外的服務(wù),增加了系統(tǒng)復(fù)雜度。所以沒有最好的解決方案,只有最合適自己的解決方案。

責(zé)任編輯:姜華 來源: Java中文社群
相關(guān)推薦

2024-10-29 21:17:25

2024-09-02 22:49:33

2022-05-26 10:27:41

分布式互聯(lián)網(wǎng)

2019-11-04 08:38:45

分布式事務(wù)主流TCC

2024-11-28 15:11:28

2014-04-09 14:59:55

Apache Spar

2019-06-19 15:40:06

分布式鎖RedisJava

2020-03-31 16:13:26

分布式事務(wù)方案TCC

2021-02-28 07:49:28

Zookeeper分布式

2023-11-27 13:50:00

ELK日志架構(gòu)

2019-02-26 09:51:52

分布式鎖RedisZookeeper

2017-01-16 14:13:37

分布式數(shù)據(jù)庫

2018-04-03 16:24:34

分布式方式

2017-04-13 10:51:09

Consul分布式

2022-04-08 08:27:08

分布式鎖系統(tǒng)

2023-08-21 19:10:34

Redis分布式

2022-01-06 10:58:07

Redis數(shù)據(jù)分布式鎖

2021-10-25 10:21:59

ZK分布式鎖ZooKeeper

2023-04-03 10:00:00

Redis分布式

2024-10-09 17:12:34

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號