cursor-init/docs/distributed-lock-guide.md
expressgy ed92f32389 feat: 优化分布式锁自动续期机制
- 添加进程退出时的自动清理逻辑

- 优化锁配置策略,短期操作不续期,长期操作续期

- 添加完整的分布式锁使用指南文档

- 修复自动续期可能导致死锁的问题
2025-07-06 18:39:25 +08:00

5.4 KiB
Raw Blame History

分布式锁使用指南

概述

本文档介绍了项目中分布式锁的使用策略和最佳实践,帮助开发者正确使用分布式锁来保护关键业务操作。

分布式锁的作用

分布式锁主要用于解决以下问题:

  1. 防止并发冲突:避免多个进程同时操作同一资源
  2. 保证数据一致性:确保关键操作的原子性
  3. 防止重复操作:避免重复执行相同的业务逻辑

使用策略

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();
    }
}

总结

分布式锁是保证系统一致性的重要工具,但使用不当也会带来问题。遵循以下原则:

  1. 合理选择锁策略:短期操作不续期,长期操作要续期
  2. 控制锁粒度:避免锁的粒度过粗
  3. 设置合理超时:避免无限等待
  4. 确保锁释放使用try-finally模式
  5. 监控和日志:及时发现问题
  6. 定期清理:防止死锁积累

通过合理使用分布式锁,可以有效保证系统的数据一致性和业务正确性。