【面试】阿里面试官:你们项目分布式锁怎么实现的?

分布式锁的实现方案

在分布式系统中,分布式锁用于协调多个节点对共享资源的访问。
常见的实现方式包括基于数据库、Redis、ZooKeeper等。
以下是几种具体的实现方案。

基于Redis的分布式锁

Redis因其高性能和原子性操作,成为分布式锁的热门选择。使用SETNX(SET if Not eXists)命令可以实现锁的获取。

public class RedisDistributedLock {
    private Jedis jedis;
    private String lockKey;
    private int expireTime; // 锁的过期时间,防止死锁

    public RedisDistributedLock(Jedis jedis, String lockKey, int expireTime) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.expireTime = expireTime;
    }

    public boolean tryLock() {
        long result = jedis.setnx(lockKey, "locked");
        if (result == 1) {
            jedis.expire(lockKey, expireTime);
            return true;
        }
        return false;
    }

    public void unlock() {
        jedis.del(lockKey);
    }
}
问题与解决方案

问题1:锁过期但业务未完成
如果锁过期时间设置过短,业务逻辑未完成时锁已释放,可能导致其他线程获取锁并操作共享资源。

解决方案:续租机制
通过后台线程定期检查锁是否仍被持有,并延长锁的过期时间。

public boolean tryLockWithRenewal() {
    String threadId = Thread.currentThread().getId() + "";
    String result = jedis.set(lockKey, threadId, "NX", "PX", expireTime);
    if ("OK".equals(result)) {
        // 启动续租线程
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(expireTime / 3);
                    if (threadId.equals(jedis.get(lockKey))) {
                        jedis.expire(lockKey, expireTime);
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }).start();
        return true;
    }
    return false;
}

问题2:锁误删
线程A释放锁时可能误删线程B的锁(如A因GC暂停导致锁过期后被B获取)。

解决方案:锁持有者验证
在释放锁时验证锁是否仍由当前线程持有。

public void unlockWithValidation() {
    String threadId = Thread.currentThread().getId() + "";
    if (threadId.equals(jedis.get(lockKey))) {
        jedis.del(lockKey);
    }
}

基于ZooKeeper的分布式锁

ZooKeeper通过临时顺序节点和Watcher机制实现分布式锁。

public class ZkDistributedLock {
    private ZooKeeper zk;
    private String lockPath;
    private String currentPath;

    public ZkDistributedLock(ZooKeeper zk, String lockPath) {
        this.zk = zk;
        this.lockPath = lockPath;
    }

    public boolean tryLock() throws KeeperException, InterruptedException {
        currentPath = zk.create(lockPath + "/lock_", new byte[0], 
            ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        List<String> children = zk.getChildren(lockPath, false);
        Collections.sort(children);
        if (currentPath.equals(lockPath + "/" + children.get(0))) {
            return true;
        }
        return false;
    }

    public void unlock() throws KeeperException, InterruptedException {
        zk.delete(currentPath, -1);
    }
}
问题与解决方案

问题1:惊群效应
所有等待锁的节点都会监听前一个节点的删除事件,导致大量通知。

解决方案:顺序监听
每个节点只监听其前一个节点的删除事件。

public boolean tryLockWithWatch() throws KeeperException, InterruptedException {
    currentPath = zk.create(lockPath + "/lock_", new byte[0], 
        ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
    List<String> children = zk.getChildren(lockPath, false);
    Collections.sort(children);
    int index = children.indexOf(currentPath.substring(lockPath.length() + 1));
    if (index == 0) {
        return true;
    } else {
        String previousPath = lockPath + "/" + children.get(index - 1);
        final CountDownLatch latch = new CountDownLatch(1);
        Stat stat = zk.exists(previousPath, event -> {
            if (event.getType() == EventType.NodeDeleted) {
                latch.countDown();
            }
        });
        if (stat != null) {
            latch.await();
        }
        return true;
    }
}

基于数据库的分布式锁

利用数据库的唯一索引或排他锁实现分布式锁。

CREATE TABLE distributed_lock (
    lock_name VARCHAR(64) PRIMARY KEY,
    owner VARCHAR(64),
    expire_time TIMESTAMP
);
乐观锁实现
public boolean tryLockWithOptimistic(String lockName, String owner, int expireSeconds) {
    Connection conn = getConnection();
    try {
        PreparedStatement stmt = conn.prepareStatement(
            "INSERT INTO distributed_lock (lock_name, owner, expire_time) VALUES (?, ?, ?)");
        stmt.setString(1, lockName);
        stmt.setString(2, owner);
        stmt.setTimestamp(3, new Timestamp(System.currentTimeMillis() + expireSeconds * 1000));
        return stmt.executeUpdate() > 0;
    } catch (SQLException e) {
        if (e.getErrorCode() == 1062) { // Duplicate entry
            return false;
        }
        throw new RuntimeException(e);
    }
}
悲观锁实现
public boolean tryLockWithPessimistic(String lockName, String owner) {
    Connection conn = getConnection();
    try {
        conn.setAutoCommit(false);
        PreparedStatement stmt = conn.prepareStatement(
            "SELECT * FROM distributed_lock WHERE lock_name = ? FOR UPDATE");
        stmt.setString(1, lockName);
        ResultSet rs = stmt.executeQuery();
        if (rs.next()) {
            return false;
        } else {
            stmt = conn.prepareStatement(
                "INSERT INTO distributed_lock (lock_name, owner) VALUES (?, ?)");
            stmt.setString(1, lockName);
            stmt.setString(2, owner);
            stmt.executeUpdate();
            conn.commit();
            return true;
        }
    } catch (SQLException e) {
        conn.rollback();
        throw new RuntimeException(e);
    }
}
问题与解决方案

问题1:数据库性能瓶颈
高并发场景下,数据库可能成为性能瓶颈。

解决方案:结合缓存
使用Redis或ZooKeeper作为主要锁实现,数据库作为备份或用于一致性要求极高的场景。


总结

分布式锁的实现需考虑锁的获取、释放、续租及异常处理。Redis适合高性能场景,ZooKeeper适合强一致性场景,数据库适合资源受限或已有数据库依赖的场景。根据具体需求选择合适的方案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小冷coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值