📌 一、什么是分布式锁?
在多实例部署或高并发系统中,为了防止同一个资源被重复操作(如重复下单、重复生成任务),我们需要一种“跨进程、跨节点”的资源访问控制机制,这就是分布式锁。
✅ 分布式锁的主要特性
- 多个进程/服务间可见,协调资源访问
- 设置超时时间,自动释放锁
- 原子性保证,不会出现两个进程同时获得锁
- 常用中间件:Redis、Zookeeper、Etcd
💡 二、为何使用分布式锁?
以以下真实接口为例:
// Step 7: 获取 Redis 分布式锁,防止重复生成
redisStore := models.RedisStore{}
lockId := fmt.Sprintf("%s", req.NoteId)
lockResult, err := redisStore.TryLock(models.LockKeyPrefix, lockId, 60)
if err != nil {
ErrorResult(c, constants.ErrorCode, "AI 正在生成中,请稍后再试")
return
}
defer lockResult.Unlock()
该接口中用户上传图文请求 AI 生成行程图像,存在以下风险:
问题 | 描述 |
---|---|
并发请求 | 用户重复点击或多端同时发起同一请求 |
重复生成 | 造成重复资源生成、写入 |
资源浪费 | 多次执行高成本操作(如调用 AI) |
状态错乱 | 相同任务多次记录,造成逻辑混乱 |
解决办法:
使用 Redis 分布式锁,以
NoteId
为唯一标识,让相同请求只能有一个获得“生成许可”,其余请求直接失败或排队重试。
⚙️ 三、实现思路与代码解析
1. 加锁流程
lockResult, err := redisStore.TryLock(models.LockKeyPrefix, lockId, 60)
TryLock
尝试创建 Redis key,如果 key 已存在,则返回失败。- 设置过期时间
60秒
,防止死锁。
2. 解锁流程
defer lockResult.Unlock()
- 任务处理结束后自动释放锁。
defer
保证即使出错也会解锁,防止死锁。
3. 核心要点
key = LockKeyPrefix + NoteId
:锁粒度控制到每个请求TTL = 60s
:合理设置超时时间,防止异常崩溃导致永久锁定TryLock
要具有原子性,确保不会出现并发同时成功的情况
🧰 四、常见应用场景
场景 | 说明 |
---|---|
AI图像/视频生成 | 防止同一素材重复提交、重复计算 |
电商下单/抢购 | 避免重复订单、库存扣减冲突 |
用户注册/手机号绑定 | 避免一个手机号被多个账号绑定 |
帖子发布/评论提交 | 避免多次点击造成多条记录 |
🔄 五、除了分布式锁,还有哪些防高并发方案?
机制 | 应用说明 |
---|---|
✅ 幂等机制 | 每次请求附带唯一 requestId ,重复请求不重复执行 |
🧰 缓存锁/本地锁 | 轻量级控制,如内存 map、sync.Mutex(单节点生效) |
🔁 消息队列削峰 | 请求写入 MQ,后台异步消费,适合慢任务、高峰控制 |
📉 接口限流 | 控制单位时间内请求次数,如令牌桶算法、固定窗口算法等 |
🔒 数据库约束 | 底层唯一索引兜底,即使上层失控,数据库层也不重复插入 |
🧠 六、对比总结
工具/方式 | 应用场景 | 优点 | 注意事项 |
---|---|---|---|
Redis 分布式锁 | 多节点并发任务控制 | 简洁、跨节点、自动过期 | 死锁/TTL设置需合理,性能需考量 |
幂等设计 | 所有写操作接口 | 核心设计原则,确保操作唯一性 | 请求唯一ID必须可控、持久存储 |
本地锁/缓存锁 | 单机任务控制 | 快速、轻量 | 不适合分布式环境,多实例失效 |
消息队列 | 异步耗时任务 | 弹性强,削峰填谷 | 架构复杂度提升,可能延迟响应 |
限流器(RateLimit) | 接口防刷、防爆 | 降低系统压力,保护核心服务 | 用户体验需平衡,适配不同请求优先级 |
数据库约束 | 数据写入最终保障 | 最后一层防线 | 应结合业务逻辑设计唯一性字段 |
📎 七、常用 Go 工具推荐
类别 | 工具名称/链接 | 说明 |
---|---|---|
分布式锁 | bsm/redislock | Redis 分布式锁实现 |
Redis客户端 | go-redis/redis | 最主流 Redis 客户端 |
限流器 | uber-go/ratelimit | 高性能令牌桶限流 |
幂等 ID | segmentio/ksuid | 分布式唯一 ID 生成 |
。