【分布式锁介绍与使用-(redisson,zk)】—(RLock,InterProcessMutex)

在这里插入图片描述

1、分布式锁介绍

分布式锁是一种用于在分布式系统中实现协调和同步的机制。在分布式系统中,多个节点(服务器或进程)可能同时访问共享资源,而为了避免资源竞争和数据不一致性问题,需要使用分布式锁来确保在任意时刻只有一个节点可以访问该资源。

常见的分布式锁实现方式有以下几种:

1、基于数据库的锁:可以使用数据库的事务机制来实现锁。例如,可以在数据库中创建一个带唯一约束的表,该表记录被锁定的资源,其他节点在尝试获得锁时会遇到唯一性约束的冲突。

2、基于缓存的锁:使用分布式缓存系统(如Redis)来实现锁。通过在缓存中存储一个特定的键值对,代表资源是否被锁定,其他节点可以根据该键值对的状态来判断是否可以获取锁。

3、基于ZooKeeper的锁:ZooKeeper是一个分布式协调服务,可以用于实现分布式锁。通过在ZooKeeper上创建临时有序节点,每个节点根据创建时间的先后顺序来竞争锁。

2、分布式锁的使用场景

举几个常见例子来说明一下:
1、避免重复处理:
假设有一个分布式任务调度系统,多个节点同时执行定时任务,为了避免同一个任务被多个节点重复执行,可以使用分布式锁来保证在任意时刻只有一个节点可以执行该任务。

2、缓存同步:
在一个分布式缓存系统中,多个节点共享缓存数据。为了避免缓存数据的不一致性,当某个节点需要更新缓存时,可以使用分布式锁来确保只有一个节点能够更新缓存。

3、控制并发操作:
在一个分布式电商系统中,有一个商品秒杀的场景,为了避免超卖,可以使用分布式锁来控制对商品库存的并发扣减,保证在任意时刻只有一个节点可以扣减库存。

4、资源访问控制:
在一个分布式文件系统中,多个节点可能同时尝试访问或修改同一个文件。为了避免文件的并发读写问题,可以使用分布式锁来确保在任意时刻只有一个节点可以访问或修改该文件。

5、分布式任务协调:
在一个分布式计算系统中,多个节点同时参与一个大型计算任务,为了协调任务的执行顺序,可以使用分布式锁来确定某个节点获得执行权。

3、使用redisson实现分布式锁应用

3.1 引入依赖

1、使用Redisson实现分布式锁非常简单,Redisson是一个支持Redis的Java客户端,它提供了便捷的API来实现分布式锁:添加Redisson的依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.15.5</version> <!-- 请使用最新版本 -->
</dependency>

3.2 实现逻辑

假如需要做一个数据同步
1、确定数据同步的共享资源:
首先,确定需要进行数据同步的共享资源,例如数据库表、文件等。在数据同步过程中,可能会涉及多个节点同时对共享资源进行读写操作,为了保证数据的正确性和一致性,需要使用分布式锁来协调数据的访问。

2、获取分布式锁:
当一个节点需要对共享资源进行数据同步操作时,首先尝试获取分布式锁。如果成功获取锁,则表示该节点可以进行数据同步操作,如果获取失败,则表示其他节点正在进行数据同步,当前节点需要等待其他节点释放锁后才能获取到锁。

3、执行数据同步操作:
当一个节点成功获取锁后,它可以执行数据同步操作,例如读取共享资源、进行数据处理、写入更新后的数据等。在执行数据同步操作时,要确保数据的正确性和完整性,以避免数据不一致问题。

4、释放分布式锁:
当节点完成数据同步操作后,需要释放分布式锁,让其他节点有机会获取锁进行数据同步。通过调用Redisson提供的unlock()方法来释放锁。
代码如下

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class DataSynchronization {
    
    

    private static final String LOCK_KEY = "data_sync_lock";
    private static final int LOCK_EXPIRE_TIME_SECONDS = 60;

    private RedissonClient redissonClient;

    public DataSynchronization() {
    
    
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        redissonClient = Redisson.create(config);
    }

    public void synchronizeData() {
    
    
        RLock lock = redissonClient.getLock(LOCK_KEY);
        try {
    
    
            // 尝试获取锁
            if (lock.tryLock(LOCK_EXPIRE_TIME_SECONDS, java.util.concurrent.TimeUnit.SECONDS)) {
    
    
                // 获取锁成功,执行数据同步操作
                // TODO: 数据同步操作代码
            } else {
    
    
                // 获取锁失败,等待其他节点完成数据同步
                // TODO: 等待逻辑,可以加入重试或超时机制
            }
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            // 释放锁
            lock.unlock();
        }
    }

    public void close() {
    
    
        redissonClient.shutdown();
    }
}

4、使用zk实现分布式锁

如果要使用ZooKeeper(zk)来实现数据同步的分布式锁,可以按照以下步骤进行:

1、创建一个ZooKeeper客户端:
首先,您需要创建一个ZooKeeper客户端,连接到ZooKeeper服务器集群。可以使用ZooKeeper提供的Java客户端库(例如Apache Curator)来实现。

2、创建锁节点:
当一个节点需要进行数据同步操作时,它会在ZooKeeper上创建一个临时顺序节点(EPHEMERAL_SEQUENTIAL)。每个节点创建的节点都有一个唯一的序列号,ZooKeeper会按照序列号的顺序对节点进行排序。

3、获取锁:
当一个节点创建临时顺序节点后,它会检查是否是序列号最小的节点,如果是,则表示该节点获取了锁;否则,它会监听前一个节点,等待前一个节点释放锁。当前一个节点的锁被释放后,ZooKeeper会通知等待的节点,然后它们继续竞争锁。

4、执行数据同步操作:
当一个节点成功获取锁后,它可以执行数据同步操作,例如读取共享资源、进行数据处理、写入更新后的数据等。在执行数据同步操作时,要确保数据的正确性和完整性,以避免数据不一致问题。

释放锁:
当节点完成数据同步操作后,需要删除自己创建的临时节点,从而释放锁。这样,其他等待锁的节点就有机会获取锁进行数据同步。
代码如下:

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

public class DataSynchronization {
    
    

    private static final String ZK_CONNECT_STRING = "localhost:2181";
    private static final String LOCK_PATH = "/data_sync_lock";

    private CuratorFramework client;
    private InterProcessMutex lock;

    public DataSynchronization() {
    
    
        client = CuratorFrameworkFactory.newClient(ZK_CONNECT_STRING, new ExponentialBackoffRetry(1000, 3));
        client.start();

        lock = new InterProcessMutex(client, LOCK_PATH);
    }

    public void synchronizeData() {
    
    
        try {
    
    
            // 尝试获取锁
            lock.acquire();
            // 获取锁成功,执行数据同步操作
            // TODO: 数据同步操作代码
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                // 释放锁
                lock.release();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public void close() {
    
    
        client.close();
    }
}

在程序中,分布式锁可能结合jvm中的锁一起做一些更细粒度的同步控制。

可以结合ZooKeeper分布式锁和AtomicBoolean来实现更为复杂的数据同步逻辑。AtomicBoolean是Java中的一个原子布尔类型,它可以保证对布尔值的读写操作是原子性的,适用于更细粒度的同步控制。
以下是一种可能的实现方式:
1、使用ZooKeeper分布式锁来协调进程之间的互斥访问,确保在同一时间只有一个进程能够执行数据同步操作。前面提到的ZooKeeper分布式锁的方式可以实现这个目标。
2、在每个进程中使用AtomicBoolean来控制具体的数据同步逻辑,例如某些细粒度的数据处理操作。AtomicBoolean可以用来标识在某个时间点上是否允许执行数据同步逻辑。

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;

import java.util.concurrent.atomic.AtomicBoolean;

public class DataSynchronization {
    
    

    private static final String ZK_CONNECT_STRING = "localhost:2181";
    private static final String LOCK_PATH = "/data_sync_lock";

    private CuratorFramework client;
    private InterProcessMutex lock;
    private AtomicBoolean canSynchronize = new AtomicBoolean(true);

    public DataSynchronization() {
    
    
        client = CuratorFrameworkFactory.newClient(ZK_CONNECT_STRING, new ExponentialBackoffRetry(1000, 3));
        client.start();

        lock = new InterProcessMutex(client, LOCK_PATH);
    }

    public void synchronizeData() {
    
    
        try {
    
    
            // 尝试获取锁
            lock.acquire();
            // 获取锁成功,检查是否可以执行数据同步逻辑
            if (canSynchronize.get()) {
    
    
                // 设置为不允许执行数据同步逻辑,防止其他进程同时执行
                canSynchronize.set(false);

                // TODO: 执行数据同步操作
                // 例如读取共享资源、进行数据处理、写入更新后的数据等

                // 重新设置为允许执行数据同步逻辑,以便其他进程能够执行
                canSynchronize.set(true);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                // 释放锁
                lock.release();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

    public void close() {
    
    
        client.close();
    }
}

通过使用canSynchronize的控制方式,可以简化竞争处理的逻辑。多个进程竞争获取锁的过程由ZooKeeper分布式锁来处理,而数据同步逻辑的执行由canSynchronize来控制,使代码逻辑更加清晰和简洁

5、关于redisson补充

Redisson提供了丰富的功能和工具,除了实现分布式锁外,它还可以做很多其他有用的事情,如下所示:

分布式集合:
Redisson提供了各种分布式集合的实现,包括分布式Set、分布式List、分布式Queue、分布式Deque等。这些集合都可以在分布式环境下使用,并提供了丰富的方法来操作集合中的元素。
分布式Map:
Redisson提供了分布式Map的实现,允许在多个节点之间共享和操作Map数据。除了普通的Map功能外,还提供了类似于ConcurrentMap的并发特性。
分布式锁:
如前所述,Redisson可以方便地实现分布式锁,用于协调和同步多个节点对共享资源的访问。
分布式信号量:
Redisson提供了分布式信号量的实现,可以用于控制并发操作,限制同时访问某个资源的节点数量。
分布式计数器:
Redisson支持分布式计数器,用于在多个节点之间共享和操作计数值。
分布式发布/订阅:
Redisson提供了分布式发布/订阅的功能,可以在多个节点之间进行消息的发布和订阅,用于实现事件驱动的架构。
分布式锁定对象:
Redisson支持对任意Java对象进行锁定,而不仅限于字符串键。这使得在复杂场景下实现更为灵活的锁定策略成为可能。
限流器:
Redisson提供了分布式限流器的实现,用于限制对某个资源的访问速率,防止系统过载。
分布式任务调度:
Redisson支持分布式任务调度,可以在多个节点之间调度和执行任务。
优先队列:
Redisson提供了支持优先级的分布式队列和阻塞队列,用于实现任务优先级调度和处理。

除了上述功能,Redisson还支持分布式位图、HyperLogLog、GEO地理位置等高级数据结构的实现。它的设计目标是简化开发人员在分布式环境中的操作,提供了很多方便易用的API和工具,使得在分布式系统中使用Redis更加方便和高效。
后续补充一下redisson的其他使用。

猜你喜欢

转载自blog.csdn.net/qq_40454136/article/details/131899865