说说你了解的分布式锁实现

文摘   2024-09-21 07:42   北京  

分布式锁是分布式系统中用来解决多个进程或节点之间对共享资源的安全访问问题的一种机制。

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

1. 基于数据库的分布式锁

实现原理:

基于数据库的分布式锁主要利用数据库的唯一性索引或主键等特性来实现。

当需要获取锁时,向数据库中插入一条记录,如果插入成功,则表示获取锁成功;

如果插入失败(如因唯一索引冲突),则表示锁已被其他节点持有。

释放锁时,从数据库中删除相应的记录。

优缺点:

  • • 优点:利用数据库的事务特性保证了一致性和可靠性。

  • • 缺点:性能相对较差,因为每次获取和释放锁都需要对数据库进行读写操作,且涉及到网络通信延迟;同时,数据库锁没有失效时间,未获得锁的进程只能一直等待已获得锁的进程主动释放锁,可能导致死锁问题。

2. 基于缓存的分布式锁(以Redis为例)

实现原理:

Redis等缓存系统通常支持原子操作,如SETNX(SET if Not eXists)。

当需要获取锁时,使用SETNX命令尝试设置一个键值对,如果键不存在,则设置成功并返回1,表示获取锁成功;

如果键已存在,则返回0,表示锁已被其他节点持有。

同时,可以设置键的过期时间,以避免死锁问题。

释放锁时,使用DEL命令删除键。

优缺点:

  • • 优点:性能较好,因为缓存系统通常位于内存中,读写速度更快;支持原子操作,实现简单;可以设置过期时间,避免死锁。

  • • 缺点:可靠性相对较弱,如果缓存系统发生故障,可能导致锁无法释放或出现竞争条件。

3. 基于ZooKeeper的分布式锁

实现原理:

ZooKeeper是一个分布式协调服务,它提供了临时节点和顺序节点等特性来实现分布式锁。

当需要获取锁时,客户端在ZooKeeper中创建一个临时顺序节点,然后获取所有子节点并进行排序。

如果当前节点是所有子节点中最小的(即序号最小),则表示获取锁成功;否则,监听前一个节点,等待其被删除后重新尝试获取锁。

释放锁时,客户端删除自己创建的临时节点。

优缺点:

  • • 优点:利用ZooKeeper的顺序节点和监听机制保证了分布式环境下的有序性和并发控制;ZooKeeper本身提供了高可用和一致性保证。

  • • 缺点:实现相对复杂;对ZooKeeper的依赖较强;在集群规模较大的情况下,可能存在羊群效应等性能问题。

应用场景

对于上述提到的三种分布式锁实现方式(基于数据库、基于Redis、基于ZooKeeper),以下是相应的Java示例代码。

请注意,这些示例是为了展示基本的实现原理,并不包含完整的错误处理和优化。

1. 基于数据库的分布式锁(示例代码简化版)

由于基于数据库的锁实现通常依赖于数据库的唯一约束,因此这里给出一个简化的SQL语句示例,而不是完整的Java代码。

-- 创建锁表(假设表名为distributed_lock,包含锁名称和锁持有者的信息)
CREATE TABLE distributed_lock (
    lock_name VARCHAR(255PRIMARY KEY,
    lock_owner VARCHAR(255),
    lock_time TIMESTAMP
);

-- 获取锁(假设锁名称为'my_lock',锁持有者为'node1',锁时间为当前时间)
-- 如果插入成功,表示获取锁成功;如果插入失败(如因唯一约束冲突),表示锁已被其他节点持有
INSERT INTO distributed_lock (lock_name, lock_owner, lock_time)
VALUES ('my_lock''node1', NOW())
ON DUPLICATE KEY UPDATE lock_owner = VALUES(lock_owner), lock_time = VALUES(lock_time);

-- 释放锁(假设锁名称为'my_lock',且锁持有者为'node1')
-- 在实际应用中,可能需要先检查锁是否仍然由当前节点持有,以避免误删除其他节点的锁
DELETE FROM distributed_lock WHERE lock_name = 'my_lock' AND lock_owner = 'node1';

2. 基于Redis的分布式锁(使用Jedis客户端)

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "my_lock";
    private static final int EXPIRE_TIME = 10// 锁过期时间,单位秒
    private static final String LOCK_VALUE = "locked";

    public static boolean tryLock(Jedis jedis, String lockOwnerId) {
        String result = jedis.set(LOCK_KEY, lockOwnerId, "NX""EX", EXPIRE_TIME);
        return "OK".equals(result);
    }

    public static void releaseLock(Jedis jedis, String lockOwnerId) {
        // 使用Lua脚本确保原子性操作:只有当锁持有者与传入的值相同时,才删除锁
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(luaScript, 1, LOCK_KEY, lockOwnerId);
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost"6379); // 连接Redis服务器
        String lockOwnerId = "node1"// 锁持有者ID(可以是节点名称、线程ID等)

        // 尝试获取锁
        if (tryLock(jedis, lockOwnerId)) {
            try {
                // 执行临界区代码
                System.out.println("Lock acquired, executing critical section...");
            } finally {
                // 释放锁
                releaseLock(jedis, lockOwnerId);
                System.out.println("Lock released.");
            }
        } else {
            System.out.println("Failed to acquire lock.");
        }

        jedis.close(); // 关闭Redis连接
    }
}

3. 基于ZooKeeper的分布式锁(使用Curator框架)

import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import java.util.concurrent.TimeUnit;

public class ZooKeeperDistributedLock {
    private static final String ZOOKEEPER_ADDRESS = "localhost:2181";
    private static final String LOCK_PATH = "/locks/my_lock";

    public static void main(String[] args) {
        CuratorFramework client = CuratorFrameworkFactory.newClient(
                ZOOKEEPER_ADDRESS,
                new ExponentialBackoffRetry(10003)
        );
        client.start();

        InterProcessMutex lock = new InterProcessMutex(client, LOCK_PATH);

        try {
            // 尝试获取锁,等待最多5秒
            if (lock.acquire(5, TimeUnit.SECONDS)) {
                try {
                    // 执行临界区代码
                    System.out.println("Lock acquired, executing critical section...");
                } finally {
                    // 释放锁
                    lock.release();
                    System.out.println("Lock released.");
                }
            } else {
                System.out.println("Failed to acquire lock within the specified timeout.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            client.close();
        }
    }
}

在以上示例中:

  • • 基于数据库的锁实现仅给出了SQL语句示例,实际使用时需要集成到Java代码中,并通过JDBC或类似框架执行。

  • • 基于Redis的锁实现使用了Jedis客户端库,并展示了如何尝试获取锁和释放锁。

  • • 基于ZooKeeper的锁实现使用了Curator框架,该框架提供了更高层次的抽象和简化的API来与ZooKeeper交互。示例中展示了如何使用InterProcessMutex来获取和释放分布式锁。


数据库杂记
数据库技术专家,PostgreSQL ACE,SAP HANA,Sybase ASE/ASA,Oracle,MySQL,SQLite各类数据库, SAP BTP云计算技术, 以及陈式太极拳教学倾情分享。出版过三本技术图书,武术6段。
 最新文章