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>
This commit is contained in:
456
questions/01-分布式系统/分布式锁.md
Normal file
456
questions/01-分布式系统/分布式锁.md
Normal file
@@ -0,0 +1,456 @@
|
||||
# 分布式锁
|
||||
|
||||
## 问题
|
||||
|
||||
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 上获取锁(冲突!)
|
||||
```
|
||||
|
||||
**解决方案:Redlock(Redis 分布式锁算法)**
|
||||
|
||||
**原理**:
|
||||
- 向多个独立的 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 等多种分布式锁实现
|
||||
- 能根据业务特点选择合适的方案
|
||||
- 有自研分布式锁的经验
|
||||
Reference in New Issue
Block a user