文章目录
-
- 前言
- 一、先搞懂:什么是分布式锁?什么时候必须用?
-
- [1. 单机锁为什么失效?](#1. 单机锁为什么失效?)
- [2. 什么场景需要Redis分布式锁(贴合我们实战项目)](#2. 什么场景需要Redis分布式锁(贴合我们实战项目))
- [3. 为什么选Redis做分布式锁?](#3. 为什么选Redis做分布式锁?)
- 二、最简单的初级版Redis锁
- [三、生产可用进阶方案:看门狗自动续期 + Lua释放锁](#三、生产可用进阶方案:看门狗自动续期 + Lua释放锁)
-
- [1. Lua脚本释放锁,保证原子性](#1. Lua脚本释放锁,保证原子性)
- [2. 看门狗(自动续期机制)解决业务超时锁失效](#2. 看门狗(自动续期机制)解决业务超时锁失效)
- [3. 自旋重试抢锁,提升用户体验](#3. 自旋重试抢锁,提升用户体验)
- 四、高并发场景终极优化:Lua脚本加锁+业务原子操作(秒杀专用)
- 五、实战避坑:新手最容易踩的5个错误
- 六、结合抖粉智算平台真实业务落地案例
- [七、Redis分布式锁 vs 数据库悲观/乐观锁 实战对比](#七、Redis分布式锁 vs 数据库悲观/乐观锁 实战对比)
- 八、总结(记忆要点)
前言
在我刚学锁的时候,因为会数据库悲观锁,一到高并发场景就踩坑:秒杀超卖、AI算力额度重复扣减、支付重复发权益。
在二阶段项目抖粉智算短视频AI营销平台中,我采用了redis分布式锁,用户额度扣减、限时秒杀下单、3D任务并发提交 全部依赖 Redis 分布式锁解决并发冲突。
本文不讲晦涩理论,我将从「为什么要用、基础实现、踩坑问题、完整实战方案、项目落地代码思路」一步步拆解,零基础也能看懂,写完直接能用在项目里。
一、先搞懂:什么是分布式锁?什么时候必须用?
1. 单机锁为什么失效?
单机项目用 synchronized、本地锁就可以控制并发,但现在项目都是微服务多实例部署:
- 支付、AI生成、秒杀拆分成独立服务;
- 同一服务部署多台服务器做负载均衡;
本地锁只能锁住当前服务器的请求,其他机器的请求不受限制,最终出现: - 秒杀商品超卖;
- 用户算力额度多次扣减,造成资金损失;
- 同一AI任务重复创建,重复消耗算力。
跨服务器、跨实例统一控制并发的锁,就是分布式锁。
2. 什么场景需要Redis分布式锁(贴合我们实战项目)
- AI任务提交:并发请求同时扣用户算力额度,防止超扣;
- 限时秒杀活动:防止库存超卖、重复下单;
- 支付权益发放:支付宝重复回调时,避免多次赠送会员/算力;
- 定时任务执行:多服务定时任务重复执行,重复更新数据。
3. 为什么选Redis做分布式锁?
- Redis内存操作,读写毫秒级,高并发性能远超数据库锁;
- 支持自动过期,不怕程序宕机死锁;
- 支持Lua脚本实现原子操作,不会出现锁逻辑拆分导致并发漏洞;
- 项目本身已引入Redis做缓存、库存存储,无需额外中间件。
二、最简单的初级版Redis锁
核心思路
占坑思想:想操作数据时,先往Redis写入一个唯一key,代表拿到锁;操作完成后删除key释放锁。
命令:SET lock_key unique_value NX EX 过期时间
- NX:key不存在才能设置成功,等价加锁;
- EX:给锁设置过期时间,防止程序崩溃锁永久不释放;
伪代码逻辑(通俗易懂)
python
# 1. 生成唯一标识,区分不同服务的锁(防止误删别人的锁)
lock_id = 随机uuid
lock_key = "user_quota_lock:10086" # 用户10086额度锁
# 2. 尝试加锁,过期时间10秒
lock_success = redis.set(lock_key, lock_id, nx=True, ex=10)
if lock_success:
try:
# 执行业务:扣减用户算力额度、创建AI任务
update_user_quota()
finally:
# 释放锁:先判断当前锁是自己加的,再删除
if redis.get(lock_key) == lock_id:
redis.delete(lock_key)
else:
# 获取锁失败,返回提示:操作繁忙,请重试
return "当前操作人数过多,请稍后再试"
初级版本存在3个致命问题(项目不能直接用)
- 业务执行超时,锁自动过期,并发问题复现
假设锁过期时间10秒,但AI生成任务执行15秒,锁提前失效,其他请求拿到锁,出现超扣。 - 释放锁非原子操作,极端情况误删锁
先get判断、再del删除,两条命令中间线程阻塞,会删掉其他线程新加上的锁。 - 没有重试机制,用户体验差
抢锁失败直接返回报错,高并发下大量用户操作失败。
三、生产可用进阶方案:看门狗自动续期 + Lua释放锁
针对上面三个缺陷,我们在抖粉智算平台落地两套解决方案,分开讲解。
1. Lua脚本释放锁,保证原子性
把「判断锁归属+删除锁」合并成一条Lua脚本,Redis单线程执行,不会被其他命令打断,杜绝误删锁。
Lua脚本:
lua
-- KEYS[1]锁key,ARGV[1]当前线程唯一标识
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end
调用时传入锁key和uuid,一条命令完成释放,无并发漏洞。
2. 看门狗(自动续期机制)解决业务超时锁失效
适用场景:AI视频、3D建模等耗时任务,无法预估业务执行时长。
实现逻辑:
- 线程拿到锁后,开启一个异步守护线程(看门狗);
- 每隔
过期时间/3(如锁10秒,3秒执行一次)自动延长锁过期时间; - 业务执行完毕,主线程释放锁,看门狗线程同步关闭;
- 如果服务宕机,看门狗停止,锁到期自动释放,不会死锁。
3. 自旋重试抢锁,提升用户体验
抢锁失败不直接返回,循环重试3次,每次间隔50ms,重试全部失败再提示繁忙。
适合秒杀、充值等高频操作,大幅降低用户报错概率。
四、高并发场景终极优化:Lua脚本加锁+业务原子操作(秒杀专用)
像秒杀库存扣减、额度扣减这种简单短逻辑,不需要看门狗,直接用Lua脚本把「判断库存、扣减库存、加锁」全部写在脚本里,一次Redis请求完成,性能拉满。
以额度扣减举例,一条Lua脚本完成:
- 判断用户是否持有锁;
- 校验用户剩余算力额度;
- 原子扣减额度;
- 生成流水记录标识。
优势:仅一次网络IO,极大降低并发下Redis请求压力,也是我们平台秒杀模块核心方案。
五、实战避坑:新手最容易踩的5个错误
坑1:不加过期时间,程序宕机永久死锁
如果忘记EX设置过期时间,服务异常崩溃后key永久存在,所有用户永远无法操作对应数据,线上事故。
解决:所有锁必须设置过期时间。
坑2:释放锁直接del,不校验锁归属
线程A的锁过期,线程B拿到锁,此时A执行到删除锁逻辑,直接删掉B的锁,并发击穿。
解决:必须携带唯一uuid,Lua脚本校验后再删除。
坑3:过期时间设置太短,业务没执行完
短任务(文案生成)设5s,长任务(3D模型)搭配看门狗,不能统一写死过期时间。
坑4:锁粒度太粗,全部用同一个lock_key
所有用户共用一把锁,所有请求串行排队,接口响应极慢。
正确做法:按业务维度拆分key,用户额度锁 user_quota:{uid}、秒杀商品锁 flash_sale:{goods_id},不同资源互不阻塞。
坑5:分布式锁替代数据库事务
Redis锁只控制并发,不能替代MySQL事务,更新数据库数据仍要保证事务,防止数据不一致。
六、结合抖粉智算平台真实业务落地案例
场景:用户提交3D生成任务,并发扣算力额度
整体流程:
- 用户上传提示词提交3D生成任务;
- 后端生成唯一UUID,使用
user_quota_lock:{用户ID}尝试获取分布式锁; - 抢锁失败自旋重试3次,失败返回操作繁忙;
- 加锁成功,开启看门狗线程持续续期锁;
- 事务内校验用户剩余算力,扣减对应额度,生成扣减流水;
- 通过RabbitMQ异步推送3D生成任务;
- AI任务执行完成,主线程通过Lua脚本释放锁,关闭看门狗;
- 若中途服务崩溃,锁10秒后自动过期,不会阻塞其他用户。
场景:限时秒杀算力额度
秒杀属于短逻辑,无需看门狗,直接Lua脚本原子操作:
- 活动预热库存存入Redis;
- 用户发起秒杀请求,执行Lua脚本:判断锁→校验库存→扣库存;
- 扣减成功推送订单到MQ,异步同步MySQL库存;
- 脚本自带过期时间,无需额外续期,百万并发稳定运行。
七、Redis分布式锁 vs 数据库悲观/乐观锁 实战对比
| 锁类型 | 并发性能 | 死锁风险 | 适用场景 | 项目选择 |
|---|---|---|---|---|
| MySQL悲观锁 | 差,串行阻塞 | 高 | 极低并发内部后台 | 不用于秒杀、额度扣减 |
| MySQL乐观锁 | 中等,冲突需要重试 | 无 | 支付回调、订单更新 | 辅助兜底,不做主锁 |
| Redis分布式锁 | 极高,内存操作 | 极低(有过期兜底) | 秒杀、AI额度、高并发任务 | 平台核心并发解决方案 |
八、总结(记忆要点)
- 分布式场景本地锁失效,必须使用Redis分布式锁;
- 基础加锁命令
SET key uuid NX EX time,过期时间必不可少; - 释放锁要用Lua脚本,校验唯一标识,防止误删;
- 长耗时AI任务搭配看门狗自动续期,避免锁提前失效;
- 短并发秒杀场景直接Lua脚本完成全部业务逻辑,性能最优;
- 锁按业务维度拆分key,细化粒度,提升系统吞吐量;
- 不能忽略重试、过期、宕机兜底三大异常场景,上线前完整测试。