面试题答案
一键面试设计方案
- 基于Zookeeper的分布式锁:
- Zookeeper的节点具有顺序性和临时节点特性。创建一个父节点,每个客户端尝试在父节点下创建顺序临时节点。最小序号的节点持有者获得锁。当锁持有者释放锁(临时节点删除),下一个序号最小的节点自动获得锁。
- 这种方案利用Zookeeper的一致性协议(ZAB协议)保证数据一致性,通过节点监听机制实现锁的高效传递,适用于对数据一致性要求高的场景。
- 基于Redis的分布式锁:
- 使用Redis的SETNX(SET if Not eXists)命令来尝试获取锁。当一个客户端成功执行SETNX命令,即表示获得锁。为防止死锁,可给锁设置一个过期时间。释放锁时通过Lua脚本保证删除锁操作的原子性。
- 为提高可用性,可采用Redlock算法,向多个独立的Redis实例获取锁,当大多数实例获取成功则认为获取锁成功。
关键技术实现
- 基于Zookeeper:
- 获取锁:
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ZookeeperDistributedLock {
private static final String LOCK_NODE = "/lock";
private ZooKeeper zk;
private String lockPath;
private CountDownLatch latch;
public ZookeeperDistributedLock(String connectString) throws Exception {
zk = new ZooKeeper(connectString, 5000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if (event.getType() == Event.EventType.NodeDeleted && event.getPath().equals(getPreviousNode())) {
latch.countDown();
}
}
});
}
public void acquire() throws Exception {
lockPath = zk.create(LOCK_NODE + "/seq-", "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
List<String> children = zk.getChildren(LOCK_NODE, false);
Collections.sort(children);
int index = children.indexOf(lockPath.substring(LOCK_NODE.length() + 1));
if (index == 0) {
return;
} else {
latch = new CountDownLatch(1);
zk.exists(LOCK_NODE + "/" + children.get(index - 1), true);
latch.await();
}
}
public void release() throws Exception {
zk.delete(lockPath, -1);
zk.close();
}
private String getPreviousNode() {
try {
List<String> children = zk.getChildren(LOCK_NODE, false);
Collections.sort(children);
int index = children.indexOf(lockPath.substring(LOCK_NODE.length() + 1));
return LOCK_NODE + "/" + children.get(index - 1);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
- **释放锁**:直接删除创建的临时节点,Zookeeper会自动通知下一个等待的客户端。
2. 基于Redis: - 获取锁:
import redis.clients.jedis.Jedis;
public class RedisDistributedLock {
private static final String LOCK_KEY = "lock_key";
private static final String LOCK_VALUE = "lock_value";
private static final int EXPIRE_TIME = 10; // 锁过期时间,单位秒
public boolean acquire(Jedis jedis) {
String result = jedis.set(LOCK_KEY, LOCK_VALUE, "NX", "EX", EXPIRE_TIME);
return "OK".equals(result);
}
public void release(Jedis jedis) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
jedis.eval(script, 1, LOCK_KEY, LOCK_VALUE);
}
}
- **释放锁**:通过Lua脚本保证删除锁操作的原子性,防止误删其他客户端的锁。
可能面临的挑战和应对策略
- 网络分区:
- 挑战:在网络分区情况下,可能导致部分节点无法与其他节点通信,出现脑裂问题,多个节点认为自己获得了锁。
- 应对策略:
- 基于Zookeeper:Zookeeper通过ZAB协议在多数节点可用时能正常工作,网络分区恢复后自动进行数据同步和状态调整。
- 基于Redis(Redlock算法):在多个Redis实例间通过多数派机制,网络分区时只有一个分区能获得多数实例的锁,减少脑裂影响。
- 性能问题:
- 挑战:高并发场景下,频繁获取和释放锁可能导致性能瓶颈。
- 应对策略:
- 基于Zookeeper:优化节点设计,减少不必要的监听和节点创建删除操作。可以批量获取和释放锁,减少Zookeeper的压力。
- 基于Redis:采用集群部署,提高Redis的处理能力。优化Lua脚本,减少脚本执行时间。
- 死锁问题:
- 挑战:如果获取锁的客户端崩溃或异常退出,未释放锁,可能导致死锁。
- 应对策略:
- 基于Zookeeper:利用临时节点特性,客户端崩溃时,其创建的临时节点自动删除,锁自动释放。
- 基于Redis:设置合理的锁过期时间,即使客户端异常未释放锁,过期后其他客户端也能获取锁。