欢迎光临
我们一直在努力

【大白话说Java面试题 第91题】【Mysql篇】第21题:分布式锁的使用场景和原理?

📌 PDF:大白话说Java面试题 — 03-Mysql篇

第21题:分布式锁的使用场景和原理

📚 回答:

  • 核心考点: 大厂面试要求深入理解分布式锁的适用场景、实现原理、常见问题与解决方案,并能根据不同场景进行技术选型。面试官常追问:“Redis锁的过期时间怎么设置?”、“Redlock算法是什么?”、“ZooKeeper锁的羊群效应怎么解决?”

1. 分布式锁的核心概念

定义:分布式锁是控制分布式系统中多个进程/线程对共享资源互斥访问的协调机制,保证同一时刻只有一个客户端持有锁。

为什么需要分布式锁?

场景单机分布式
锁机制 JVM锁(synchronized、ReentrantLock) 跨进程/跨节点的分布式锁
问题 多线程竞争共享资源 多实例竞争共享资源(数据库、缓存、文件)

三大核心特性:

特性说明重要性
互斥性 同一时刻只有一个客户端能持有锁 必须满足
可重入性 同一客户端可重复获取已持有的锁 按需
高可用 锁服务本身不能成为单点故障 必须满足
防死锁 锁持有者宕机时,锁能自动释放 必须满足
高性能 加锁解锁延迟低、吞吐量高 必须满足
2. 核心使用场景

2.1 库存扣减(防止超卖)

// 电商秒杀场景
public void reduceStock(Long productId, Integer quantity) {
String lockKey = "lock:product:stock:" + productId;
// 获取分布式锁
boolean locked = redisLock.lock(lockKey, 3000);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后重试");
}
try {
// 查询库存
int stock = productMapper.selectStock(productId);
if (stock < quantity) {
throw new BusinessException("库存不足");
}
// 扣减库存
productMapper.updateStock(productId, stock quantity);
} finally {
redisLock.unlock(lockKey);
}
}

2.2 分布式定时任务(防止重复执行)

@Scheduled(cron = "0 0 2 * * ?") // 凌晨2点执行
public void doDailyReport() {
String lockKey = "lock:job:dailyReport";
// 尝试获取锁,获取成功才执行
if (redisLock.tryLock(lockKey, 0, TimeUnit.SECONDS)) {
try {
generateReport(); // 生成日报
} finally {
redisLock.unlock(lockKey);
}
} else {
log.info("另一实例正在执行,跳过");
}
}

2.3 防止缓存击穿(缓存重建互斥)

public String getData(String key) {
String value = redis.get(key);
if (value != null) {
return value;
}
// 缓存失效,尝试获取锁
String lockKey = "lock:cache:rebuild:" + key;
if (redisLock.tryLock(lockKey, 1000)) {
try {
// 双重检查
value = redis.get(key);
if (value != null) return value;

// 从数据库加载
value = loadFromDB(key);
redis.setex(key, 3600, value);
return value;
} finally {
redisLock.unlock(lockKey);
}
} else {
// 等待片刻后重试
Thread.sleep(100);
return getData(key);
}
}

2.4 其他场景

场景示例说明
唯一性校验 订单号生成、防重复提交 防止分布式下ID重复
分布式ID生成 雪花算法workerID分配 保证workerID全局唯一
配置动态更新 Apollo/Nacos配置发布 同一时刻只一个节点发布
3. Redis分布式锁的实现原理

3.1 基础版本(SETNX + EXPIRE)

// 问题:非原子操作,可能SETNX后崩溃导致锁永不释放
Boolean success = redis.setnx(lockKey, clientId);
if (success) {
redis.expire(lockKey, 30); // 如果这步崩溃,锁永不释放
}

3.2 原子版本(SET NX EX)

// Redis 2.6.12+ 支持原子操作
String result = redis.set(lockKey, clientId, "NX", "EX", 30);
// NX:不存在才设置
// EX:过期时间30秒

3.3 完整实现要点

public class RedisDistributedLock {

// 获取锁
public boolean lock(String key, String value, int expireSec) {
String result = jedis.set(key, value, "NX", "EX", expireSec);
return "OK".equals(result);
}

// 释放锁(需要Lua脚本保证原子性,防止误删其他线程的锁)
public boolean unlock(String key, String value) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Object result = jedis.eval(luaScript, Collections.singletonList(key),
Collections.singletonList(value));
return Long.valueOf(1).equals(result);
}
}

3.4 Redis锁的常见问题与解决方案

问题原因解决方案
锁误释放 线程A的锁过期,线程B获取锁,线程A释放时删了B的锁 释放时校验value(客户端标识)
锁过期业务未完成 业务执行时间超过锁过期时间 看门狗(WatchDog)自动续期
主从切换锁丢失 Redis主从异步复制,主宕机锁未同步到从 Redlock算法(多节点)
不可重入 同一线程重复获取同一锁失败 ThreadLocal存储重入次数
阻塞获取 获取锁失败立即返回 自旋重试(需退避算法)

看门狗实现:

// 获取锁后启动定时任务,在锁过期前1/3时间续期
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
if (isLockHeld()) {
jedis.expire(lockKey, expireSec); // 续期
}
}, expireSec / 3, expireSec / 3, TimeUnit.SECONDS);

4. Redlock算法(Redis作者推荐)

4.1 核心原理

Redlock是Redis作者提出的分布式锁算法,解决Redis主从切换导致锁丢失问题。

工作流程:

  • 获取当前时间戳(毫秒)
  • 依次向N个(通常5个)独立的Redis节点尝试获取锁
  • 当成功获取锁的节点数 > N/2(多数派)且总耗时 < 锁有效期时,认为获取锁成功
  • 锁的有效期 = 初始有效期 – 获取锁耗时
  • 释放锁时,向所有节点发送释放请求
  • 4.2 Redlock优缺点

    优点缺点
    高可用:少数节点宕机不影响 性能低:需要多节点网络通信
    强一致性:多数派决策 时钟漂移问题:依赖节点时间同步
    自动失效:自带TTL 实现复杂:需要维护多个连接

    生产建议:绝大多数场景不需要Redlock,单节点Redis + 主从 + 看门狗已足够。Redlock只在金融级强一致性场景考虑。

    5. ZooKeeper分布式锁实现原理

    5.1 核心机制

    ZooKeeper的**临时顺序节点(Ephemeral Sequential Node)**特性天然适合分布式锁。

    工作流程:

  • 客户端在锁路径下创建临时顺序节点(如/lock/seq-000001)
  • 获取该路径下所有子节点,判断自己是否是序号最小的节点
  • 是 → 获得锁;否 → 监听前一个节点的删除事件
  • 前一个节点删除后,再次判断自己是否最小(重复步骤2)
  • 代码示例:

    public class ZooKeeperDistributedLock {

    public void lock(String lockPath) throws Exception {
    String currentPath = zk.create(lockPath + "/seq-",
    null,
    ZooDefs.Ids.OPEN_ACL_UNSAFE,
    CreateMode.EPHEMERAL_SEQUENTIAL);

    List<String> children = zk.getChildren(lockPath, false);
    Collections.sort(children);

    String minNode = children.get(0);
    if (currentPath.endsWith(minNode)) {
    // 获得锁
    return;
    } else {
    // 监听前一个节点
    String prevNode = getPrevNode(currentPath, children);
    CountDownLatch latch = new CountDownLatch(1);
    zk.exists(lockPath + "/" + prevNode, event -> latch.countDown());
    latch.await(); // 阻塞等待
    // 重新尝试获取锁(递归)
    lock(lockPath);
    }
    }
    }

    5.2 Redis vs ZooKeeper对比

    对比维度RedisZooKeeper
    性能 极高(内存,单机10万+ QPS) 一般(1万+ QPS)
    一致性 最终一致(主从异步) 强一致(ZAB协议)
    可靠性 主从切换可能丢锁 高(多数派写入)
    实现复杂度
    依赖 Redis集群 ZooKeeper集群
    自动续期 需自己实现看门狗 原生支持(临时节点)
    适用场景 高并发、高性能场景 强一致性场景
    6. 分布式锁选型对比
    方案性能一致性可用性复杂度典型场景
    Redis单机 极高 开发/测试
    Redis主从 极高 中(可能丢锁) 高并发业务(99%场景)
    Redlock 金融级强一致性
    ZooKeeper 极高 强一致性要求(配置中心)
    数据库唯一索引 简单场景、无额外依赖

    选型决策树:

    是否需要极高性能(10万+ QPS)?
    ├── 是 → Redis主从 + 看门狗
    └── 否 → 是否需要强一致性?
    ├── 是 → ZooKeeper / Redlock
    └── 否 → Redis主从

    7. 常见问题与解决方案

    Q1:锁过期时间怎么设置?

    A:设置为业务执行时间的2-3倍,且配合看门狗自动续期。经验值:秒杀场景100-300ms,缓存重建1-3秒。

    Q2:获取锁失败怎么处理?

    A:根据业务决定:

    • 快速失败:立即返回"系统繁忙"(秒杀场景)
    • 阻塞等待:自旋重试,使用退避算法(指数退避)
    • 排队等待:使用消息队列

    Q3:Redis分布式锁怎么实现可重入?

    A:使用ThreadLocal存储锁持有信息:

    ThreadLocal<Map<String, Integer>> lockCount = ...;
    public boolean lock(String key) {
    if (lockCount.get().containsKey(key)) {
    lockCount.get().put(key, count + 1);
    return true;
    }
    // 尝试获取Redis锁…
    }

    Q4:ZooKeeper锁的羊群效应如何解决?

    A:不监听所有子节点,只监听前一个节点,避免所有客户端同时被唤醒。


    💡 面试官想要的满分总结:

    "分布式锁是分布式系统中协调共享资源访问的核心机制。

    核心场景:

    • 库存扣减(防超卖)
    • 分布式定时任务(防重复执行)
    • 缓存击穿防护(单实例重建)

    主流实现:

    • Redis:SET NX EX原子操作,高性能(10万+ QPS),需处理锁过期、误释放、不可重入等问题,通过看门狗自动续期防业务超时
    • ZooKeeper:临时顺序节点,强一致性,自动释放,性能较低(1万+ QPS),适合强一致性场景

    关键技术:

    • 防死锁:设置TTL/临时节点
    • 防误释放:释放时校验客户端标识(Lua脚本)
    • 锁续期:看门狗(WatchDog)
    • 多数派算法:Redlock解决主从切换丢锁

    选型建议:

    • 高并发业务(99%场景)→ Redis主从 + 看门狗
    • 金融级强一致性 → ZooKeeper / Redlock

    一句话:分布式锁的核心是互斥、防死锁、高可用;Redis高性能适合大多数场景,ZooKeeper强一致适合核心金融系统。"


    觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

    赞(0)
    未经允许不得转载:171主机测评 » 【大白话说Java面试题 第91题】【Mysql篇】第21题:分布式锁的使用场景和原理?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址