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