分布式锁相关
超卖问题的解决
现象
- 一件商品,AB同时下单,扣减库存出了问题。A读到库存是1,B读到库存是1;AB同时更新数据库,更新为0,数据库中虽然是0了,但是产生了两笔订单。
- 解决办法:下沉到数据库扣减库存:
- 通过UPDATE语句,更新增量,通过UPDATE计算库存,用到了UPDATE行锁,其他的UPDATE不可以更新,需要等待。
- 解决办法:下沉到数据库扣减库存:
- 并发校验库存,造成库存冲突的假象,扣成负数;
1.传入库存增量,库存会扣成负数
2.库存扣为负数
解决办法:
1.更新完库存之后,读取库存,若<0则抛出异常,回滚事务。
这样做没有加分布式锁,且数据库压力较大。且性能不高。
2.统一加锁,使之成为原子操作。
分布式锁
1.基于数据库的悲观锁
select ** for update 加锁,只能加一次,加锁期间其他线程不能获取。
具体实现:
优缺点:
简单方便,易于理解。并发量大的时候,对数据库压力大。锁的库与业务的数据库需要分开。
2.redis分布式锁:
SET resource_name my_random_value NX PX 3000
NX :因为redis是单线程的,并行请求变串行执行,只有一个能设置成功,设置成功则获得锁
PX: 失效时间,出现异常情况,锁可以过期失效。
这个时间怎么设置,程序执行超过这个时间了怎么办
1.Watchdog用来给key延时,redission有这个功能。自动续期
延时时使用 :lua脚本(见下方)
释放锁:
使用redis的delete命令,正常释放的时候需要校验随机数是否相同,相同才能释放。
使用LUA脚本,去校验。
Redisson客户端
还有springboot的配置方法等
https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter
redis官网有介绍:
防止释放其他线程的锁。
加锁实现:
释放锁,使用lua脚本,加在finally里:
解锁脚本(lua)
private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; //
延时脚本
private static final String POSTPONE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return '0' end";
3.zookeeper分布式锁
存储机构类似树,有持久节点和瞬时节点
瞬时节点有序,没有子节点,会话结束会消失。
zookeeper官网有介绍
zk观察器
- getData()
- getChildren()
- exists()
观察器只能观察一次,节点有变化发送给客户端
锁原理
- 十个线程创建是个节点,瞬时节点,序号最小的线程获得锁。
- 每个线程监听前一个序号节点;
- 线程执行完,删除自己的节点;
- 下一个序号线程收到通知,开始执行;
zk的Curator客户端
直接调用即可
官网: https://curator.apache.org/
分布式锁解决超卖问题
Jmeter的使用,测试工具,压力测试
一等座8个,二等座35个。压一等座,如果不超卖,购票记录表应该有八条记录。
500个线程。
有些成功有些失败了。库存变成负数了。
结果是超卖了,看到了有90多张票被卖出。
synchronized和分布式锁的区别
synchronized就能锁单机的,分布式的不行。
分布式锁的问题及演进方案
redis锁
StringRedisTemplate
-
setIfAbsent()
-
操作字符串的,setIfAbsent(),放置成功就是true。
-
这个key要合理,车次+日期
-
抢到true的话,就继续向下执行,false就提示“抢票人数多”
-
超时时间怎么规定的???5s,处理不完怎么办
-
@Resource:jdk自带的。找的时候是根据引入的名称找,同名容易引用错。@Autowired可以解决。
-
处理完要释放锁
-
释放锁逻辑失败怎么办
-
怎么防止删别的线程的锁
-
压测结果: 库存8张,卖了20张,且后续线程执行很慢:报错:全局锁超时。
-
原因:@GlobalTransactional 导致的。
-
解决:注释掉@GlobalTransactional,不管这个
-
再次压测,还是没生效,请求是乱序的,现在各个线程的日志是交叉的。
-
原因:finally,不论成功还是失败,都要去执行删除操作。
-
-
没有拿到锁的线程把已经拿到的锁给释放了。
-
没有卖完,剩了一张没卖。
-
实际生产中,并不会这么用,会存在问题。!!!!!!
-
setIfAbsent的问题
-
执行时间超过5s,就不好续期。
-
看门狗续期,redission,开一个守护线程,关注这个key的60s超时时间,检测到key的倒计时到30了,就set一下时间到60s。
-
RLock lock = redissonClient.getLock(lockKey);
-
tryLock(0, TimeUnit.secons); 带看门狗 无限期刷新时长
-
tryLock(30, 10, TimeUnit.seconds)不带看门狗
-
finally : lock不是空 且 是当前线程 才释放锁。 isHeldByCurrentThread;
-
看门狗宕机问题
-
为了解决锁超时问题,引入看门狗
-
但是当redis宕机了,获取锁的动作没了,就业务中断了
-
主备可以解决吗?
-
1线程拿到锁。redis宕机了。1线程继续执行
-
redis主备切换,slave被选成主节点。
-
2线程进来之后,去新master拿到锁,2线程也拿到锁?
-
这样锁就有两个线程持有了。
-
引入红锁,解决看门狗问题
-
红锁:
-
ABCDE台机器,不是集群,每个有可能是主备,各个节点平等
-
1线程去 :A节点拿一个锁,B节点拿一个锁,C节点拿一个锁。
-
拿三次(超过半数),就认为拿到锁了
-
2线程只能拿DE了,没超过半数,就没获得锁。
-
只能奇数台机器。
-
RedLock; RedissonRedLock("");
-
redis自带的
-
1 AB 2 DE 3 C 这样总也拿不到锁。
-
规定都从A拿锁,按顺序获取。
-
性能慢
实际工作中,就用看门狗。红锁太复杂,写的逻辑多。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)