291 lines
5.4 KiB
Markdown
291 lines
5.4 KiB
Markdown
# 分布式锁使用指南
|
||
|
||
## 概述
|
||
|
||
本文档介绍了项目中分布式锁的使用策略和最佳实践,帮助开发者正确使用分布式锁来保护关键业务操作。
|
||
|
||
## 分布式锁的作用
|
||
|
||
分布式锁主要用于解决以下问题:
|
||
|
||
1. **防止并发冲突**:避免多个进程同时操作同一资源
|
||
2. **保证数据一致性**:确保关键操作的原子性
|
||
3. **防止重复操作**:避免重复执行相同的业务逻辑
|
||
|
||
## 使用策略
|
||
|
||
### 1. 短期操作(推荐不开启自动续期)
|
||
|
||
**适用场景**:
|
||
|
||
- 用户登录
|
||
- Token刷新
|
||
- 数据查询
|
||
- 简单的数据更新
|
||
|
||
**配置建议**:
|
||
|
||
```typescript
|
||
const lock = await DistributedLockService.acquire({
|
||
key: 'user:login:username',
|
||
ttl: 15, // 15秒过期
|
||
timeout: 8000, // 8秒超时
|
||
autoRenew: false, // 不开启自动续期
|
||
});
|
||
```
|
||
|
||
**优点**:
|
||
|
||
- 简单可靠,不会出现死锁
|
||
- 性能开销小
|
||
- 适合快速操作
|
||
|
||
### 2. 长期操作(需要开启自动续期)
|
||
|
||
**适用场景**:
|
||
|
||
- 用户注册(包含邮件发送)
|
||
- 密码重置(包含邮件发送)
|
||
- 文件上传
|
||
- 复杂的数据处理
|
||
|
||
**配置建议**:
|
||
|
||
```typescript
|
||
const lock = await DistributedLockService.acquire({
|
||
key: 'user:register:username:email',
|
||
ttl: 60, // 60秒过期
|
||
timeout: 15000, // 15秒超时
|
||
autoRenew: true, // 开启自动续期
|
||
renewInterval: 20000, // 20秒续期一次
|
||
});
|
||
```
|
||
|
||
**注意事项**:
|
||
|
||
- 必须确保在操作完成后手动释放锁
|
||
- 进程退出时会自动清理锁
|
||
- 续期失败时会记录警告日志
|
||
|
||
## 锁键名设计规范
|
||
|
||
### 1. 命名规则
|
||
|
||
```
|
||
{业务模块}:{操作类型}:{关键标识}
|
||
```
|
||
|
||
### 2. 示例
|
||
|
||
```typescript
|
||
// 用户注册锁
|
||
'user:register:username:email';
|
||
|
||
// 用户登录锁
|
||
'user:login:username';
|
||
|
||
// 密码重置锁
|
||
'password:reset:email';
|
||
|
||
// Token刷新锁
|
||
'token:refresh:token_value';
|
||
```
|
||
|
||
### 3. 注意事项
|
||
|
||
- 键名要具有唯一性
|
||
- 避免使用过长的键名
|
||
- 使用有意义的标识符
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 锁的粒度控制
|
||
|
||
**好的做法**:
|
||
|
||
```typescript
|
||
// 针对特定用户加锁
|
||
const lock = await DistributedLockService.acquire({
|
||
key: `user:login:${username}`,
|
||
ttl: 15,
|
||
autoRenew: false,
|
||
});
|
||
```
|
||
|
||
**避免的做法**:
|
||
|
||
```typescript
|
||
// 锁的粒度太粗,影响其他用户
|
||
const lock = await DistributedLockService.acquire({
|
||
key: 'user:login', // 所有用户登录都被阻塞
|
||
ttl: 15,
|
||
autoRenew: false,
|
||
});
|
||
```
|
||
|
||
### 2. 超时时间设置
|
||
|
||
**原则**:
|
||
|
||
- 超时时间应该大于预期的操作时间
|
||
- 但不要设置过长,避免长时间阻塞
|
||
|
||
**建议**:
|
||
|
||
```typescript
|
||
// 快速操作
|
||
timeout: 5000; // 5秒
|
||
|
||
// 中等操作
|
||
timeout: 10000; // 10秒
|
||
|
||
// 慢速操作
|
||
timeout: 30000; // 30秒
|
||
```
|
||
|
||
### 3. TTL设置
|
||
|
||
**原则**:
|
||
|
||
- TTL应该大于操作时间
|
||
- 对于自动续期的锁,TTL可以设置得相对较短
|
||
|
||
**建议**:
|
||
|
||
```typescript
|
||
// 快速操作
|
||
ttl: 10; // 10秒
|
||
|
||
// 中等操作
|
||
ttl: 30; // 30秒
|
||
|
||
// 慢速操作
|
||
ttl: 60; // 60秒
|
||
```
|
||
|
||
### 4. 错误处理
|
||
|
||
**必须使用 try-finally**:
|
||
|
||
```typescript
|
||
const lock = await DistributedLockService.acquire(config);
|
||
|
||
try {
|
||
// 执行业务逻辑
|
||
await doSomething();
|
||
} finally {
|
||
// 确保锁被释放
|
||
await lock.release();
|
||
}
|
||
```
|
||
|
||
### 5. 监控和日志
|
||
|
||
**监控指标**:
|
||
|
||
- 锁获取成功率
|
||
- 锁等待时间
|
||
- 锁释放情况
|
||
- 死锁检测
|
||
|
||
**日志记录**:
|
||
|
||
```typescript
|
||
Logger.info(`获取分布式锁成功: ${lockKey}`);
|
||
Logger.warn(`锁续期失败: ${lockKey}`);
|
||
Logger.error(`获取锁超时: ${lockKey}`);
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
### 1. 死锁问题
|
||
|
||
**原因**:
|
||
|
||
- 进程崩溃但锁未释放
|
||
- 网络中断导致无法续期
|
||
- 业务逻辑异常导致锁未释放
|
||
|
||
**解决方案**:
|
||
|
||
- 设置合理的TTL
|
||
- 使用try-finally确保锁释放
|
||
- 进程退出时自动清理锁
|
||
- 定期检查并清理过期锁
|
||
|
||
### 2. 性能问题
|
||
|
||
**原因**:
|
||
|
||
- 锁的粒度太粗
|
||
- 锁的持有时间过长
|
||
- 频繁的锁竞争
|
||
|
||
**解决方案**:
|
||
|
||
- 细化锁的粒度
|
||
- 优化业务逻辑,减少锁持有时间
|
||
- 使用读写锁分离
|
||
- 考虑使用乐观锁
|
||
|
||
### 3. 一致性问题
|
||
|
||
**原因**:
|
||
|
||
- 锁释放时机不当
|
||
- 业务逻辑异常
|
||
- 并发控制不当
|
||
|
||
**解决方案**:
|
||
|
||
- 确保锁的原子性操作
|
||
- 使用事务保证数据一致性
|
||
- 添加业务层面的幂等性检查
|
||
|
||
## 工具函数
|
||
|
||
### 1. 装饰器使用
|
||
|
||
```typescript
|
||
class UserService {
|
||
@withDistributedLock('user:register', 30, 10000)
|
||
async register(userData: UserData) {
|
||
// 业务逻辑
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 手动管理锁
|
||
|
||
```typescript
|
||
async function complexOperation() {
|
||
const lock = await DistributedLockService.acquire({
|
||
key: 'complex:operation',
|
||
ttl: 60,
|
||
autoRenew: true,
|
||
});
|
||
|
||
try {
|
||
// 复杂业务逻辑
|
||
await step1();
|
||
await step2();
|
||
await step3();
|
||
} finally {
|
||
await lock.release();
|
||
}
|
||
}
|
||
```
|
||
|
||
## 总结
|
||
|
||
分布式锁是保证系统一致性的重要工具,但使用不当也会带来问题。遵循以下原则:
|
||
|
||
1. **合理选择锁策略**:短期操作不续期,长期操作要续期
|
||
2. **控制锁粒度**:避免锁的粒度过粗
|
||
3. **设置合理超时**:避免无限等待
|
||
4. **确保锁释放**:使用try-finally模式
|
||
5. **监控和日志**:及时发现问题
|
||
6. **定期清理**:防止死锁积累
|
||
|
||
通过合理使用分布式锁,可以有效保证系统的数据一致性和业务正确性。
|