Files
interview/questions/01-分布式系统/分布式锁.md
yasinshaw 0e46a367c4 refactor: rename files to Chinese and organize by category
Organized 50 interview questions into 12 categories:
- 01-分布式系统 (9 files): 分布式事务, 分布式锁, 一致性哈希, CAP理论, etc.
- 02-数据库 (2 files): MySQL索引优化, MyBatis核心原理
- 03-缓存 (5 files): Redis数据结构, 缓存问题, LRU算法, etc.
- 04-消息队列 (1 file): RocketMQ/Kafka
- 05-并发编程 (4 files): 线程池, 设计模式, 限流策略, etc.
- 06-JVM (1 file): JVM和垃圾回收
- 07-系统设计 (8 files): 秒杀系统, 短链接, IM, Feed流, etc.
- 08-算法与数据结构 (4 files): B+树, 红黑树, 跳表, 时间轮
- 09-网络与安全 (3 files): TCP/IP, 加密安全, 性能优化
- 10-中间件 (4 files): Spring Boot, Nacos, Dubbo, Nginx
- 11-运维 (4 files): Kubernetes, CI/CD, Docker, 可观测性
- 12-面试技巧 (1 file): 面试技巧和职业规划

All files renamed to Chinese for better accessibility and
organized into categorized folders for easier navigation.

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-03-01 00:10:53 +08:00

457 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 分布式锁
## 问题
1. 什么是分布式锁?为什么需要分布式锁?
2. 如何用 Redis 实现分布式锁?
3. Redis 分布式锁有哪些坑?如何解决?
4. Zookeeper 如何实现分布式锁?
5. Redis 分布式锁和 Zookeeper 分布式锁的区别?
6. Redisson 是如何实现分布式锁的?
7. 在实际项目中如何使用分布式锁?
---
## 标准答案
### 1. 为什么需要分布式锁
#### **场景**
**单机锁synchronized、ReentrantLock**
```java
@Service
public class OrderService {
private synchronized void createOrder(Order order) {
// 单机环境下有效
// 分布式环境下无效(不同 JVM
}
}
```
**问题**
- 单机锁只对单个 JVM 有效
- 分布式环境下,多个实例的锁互不排斥
- 需要跨 JVM、跨机器的锁机制
---
### 2. Redis 分布式锁
#### **基本实现**
```java
public class RedisDistributedLock {
private final StringRedisTemplate redisTemplate;
// 加锁
public boolean lock(String key, String value, long expireTime) {
// SET key value NX PX expireTime
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, value, expireTime, TimeUnit.MILLISECONDS);
return success != null && success;
}
// 释放锁
public boolean unlock(String key, String value) {
// Lua 脚本:保证原子性
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key), value);
return result != null && result == 1;
}
}
```
---
#### **使用示例**
```java
@Service
public class OrderService {
@Autowired
private RedisDistributedLock lock;
public void createOrder(Order order) {
String lockKey = "order:" + order.getProductId();
String lockValue = UUID.randomUUID().toString();
try {
// 加锁(过期时间 30 秒)
boolean locked = lock.lock(lockKey, lockValue, 30000);
if (!locked) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// 执行业务逻辑
// 1. 查询库存
// 2. 扣减库存
// 3. 创建订单
} finally {
// 释放锁
lock.unlock(lockKey, lockValue);
}
}
}
```
---
### 3. Redis 分布式锁的坑
#### **坑 1锁超时问题**
**场景**
```
线程 A 获取锁(设置 30 秒过期)
线程 A 执行业务(耗时 50 秒)
30 秒后,锁自动过期
线程 B 获取锁
线程 A 执行完毕,释放锁(把线程 B 的锁释放了!)
```
**解决方案看门狗Watchdog**
**原理**
- 启动后台线程
- 定期检查锁是否还存在
- 如果存在,续期(延长过期时间)
**Redisson 实现**
```java
RLock lock = redisson.getLock("myLock");
lock.lock(); // 自动续期(默认 30 秒)
try {
// 业务逻辑
} finally {
lock.unlock();
}
```
---
#### **坑 2主从切换导致锁丢失**
**场景**
```
线程 A 在 Master 获取锁
锁未同步到 Slave
Master 宕机Slave 升级为 Master
线程 B 在新 Master 上获取锁(冲突!)
```
**解决方案RedlockRedis 分布式锁算法)**
**原理**
- 向多个独立的 Redis 节点请求加锁
- 大多数节点加锁成功才算成功
```java
// Redisson Redlock
RLock lock1 = redisson1.getLock("myLock");
RLock lock2 = redisson2.getLock("myLock");
RLock lock3 = redisson3.getLock("myLock");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
redLock.lock();
// 业务逻辑
} finally {
redLock.unlock();
}
```
---
#### **坑 3释放锁时误删**
**场景**
```
线程 A 获取锁
线程 A 执行时间过长,锁超时过期
线程 B 获取锁
线程 A 执行完毕,释放锁(删除了线程 B 的锁!)
```
**解决方案Lua 脚本 + 唯一标识**
```lua
-- Lua 脚本(原子操作)
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
```
**Java 实现**
```java
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";
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(key), value);
return result != null && result == 1;
}
```
---
### 4. Zookeeper 分布式锁
#### **原理**
利用 Zookeeper 的**临时顺序节点**
```
/lock
/lock/node-0000000001 (客户端 1)
/lock/node-0000000002 (客户端 2)
/lock/node-0000000003 (客户端 3)
```
**流程**
1. 客户端创建临时顺序节点
2. 获取所有子节点,判断自己是否是序号最小的
3. 如果是最小节点,获取锁
4. 如果不是,监听前一个节点的删除事件
---
#### **Curator 实现**
```java
@Component
public class ZkDistributedLock {
private final InterProcessMutex lock;
public ZkDistributedLock(CuratorFramework curatorFramework) {
this.lock = new InterProcessMutex(curatorFramework, "/lock");
}
public void acquire() {
try {
lock.acquire();
} catch (Exception e) {
throw new RuntimeException("获取锁失败", e);
}
}
public void release() {
try {
lock.release();
} catch (Exception e) {
throw new RuntimeException("释放锁失败", e);
}
}
}
```
**使用**
```java
@Service
public class OrderService {
@Autowired
private ZkDistributedLock lock;
public void createOrder(Order order) {
try {
lock.acquire();
// 业务逻辑
} finally {
lock.release();
}
}
}
```
---
### 5. Redis vs Zookeeper
| 特性 | Redis | Zookeeper |
|------|-------|-----------|
| **性能** | 高(内存操作) | 低(磁盘 + ZAB 协议) |
| **可靠性** | 中(可能丢锁) | 高CP 一致性) |
| **实现复杂度** | 简单 | 复杂 |
| **获取锁方式** | 轮询 | Watcher 通知(事件驱动) |
| **锁释放** | 超时自动释放 | 会话结束自动释放 |
| **适用场景** | 高并发、对一致性要求不高 | 严格一致性要求 |
---
### 6. Redisson 原理
#### **看门狗(自动续期)**
```java
RLock lock = redisson.getLock("myLock");
lock.lock(); // 默认 leaseTime = -1启用看门狗
```
**原理**
```
1. 加锁成功,启动看门狗线程
2. 看门狗每 10 秒检查一次
3. 如果锁还存在,续期 30 秒
4. 客户端宕机,会话结束,看门狗停止,锁自动过期
```
---
#### **公平锁**
```java
RLock fairLock = redisson.getFairLock("myLock");
fairLock.lock();
```
**原理**
- 加锁时,添加到队列尾部
- 按照请求顺序获取锁
- 性能比非公平锁低
---
#### **读写锁**
```java
RReadWriteLock rwLock = redisson.getReadWriteLock("myLock");
rwLock.readLock().lock(); // 读锁(共享)
rwLock.writeLock().lock(); // 写锁(独占)
```
---
### 7. 实际项目应用
#### **场景 1秒杀系统**
```java
@Service
public class SeckillService {
@Autowired
private RedissonClient redisson;
public void seckill(Long userId, Long productId) {
RLock lock = redisson.getLock("seckill:" + productId);
try {
// 尝试加锁(最多等待 3 秒,锁自动释放时间 10 秒)
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("抢购人数过多,请稍后再试");
}
// 1. 查询库存
Integer stock = redisTemplate.opsForValue().get("stock:" + productId);
if (stock == null || stock <= 0) {
throw new BusinessException("库存不足");
}
// 2. 扣减库存
redisTemplate.opsForValue().decrement("stock:" + productId);
// 3. 创建订单
createOrder(userId, productId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("系统异常");
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
```
---
#### **场景 2定时任务分布式锁**
```java
@Component
public class ScheduledTask {
@Autowired
private RedissonClient redisson;
@Scheduled(cron = "0 */5 * * * ?") // 每 5 分钟
public void execute() {
RLock lock = redisson.getLock("scheduled-task");
try {
// 只有一个实例执行
boolean locked = lock.tryLock(0, 5, TimeUnit.MINUTES);
if (!locked) {
return; // 其他实例正在执行
}
// 执行任务
doTask();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
```
---
### 8. 阿里 P7 加分项
**深度理解**
- 理解 CAP 理论在分布式锁中的体现
- 理解 Redis 的 ZSet 实现延迟队列的原理
- 理解 ZAB 协议和 ZK 的一致性保证
**实战经验**
- 有处理分布式锁死锁的经验
- 有分布式锁性能优化的经验
- 有分布式锁监控的经验
**架构能力**
- 能设计高可用的分布式锁方案
- 能设计分布式锁的容灾方案
- 能设计分布式锁的降级方案
**技术选型**
- 了解 Redis、Zookeeper、Etcd 等多种分布式锁实现
- 能根据业务特点选择合适的方案
- 有自研分布式锁的经验