分布式锁相关

 


超卖问题的解决

现象

  • 一件商品,AB同时下单,扣减库存出了问题。A读到库存是1,B读到库存是1;AB同时更新数据库,更新为0,数据库中虽然是0了,但是产生了两笔订单。
    • 解决办法:下沉到数据库扣减库存:
      •   通过UPDATE语句,更新增量,通过UPDATE计算库存,用到了UPDATE行锁,其他的UPDATE不可以更新,需要等待。
  • 并发校验库存,造成库存冲突的假象,扣成负数;

1.传入库存增量,库存会扣成负数

更新库存,通过update

 

 

 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客户端

https://redisson.org/

 

 

还有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拿锁,按顺序获取。

      • ​性能慢

实际工作中,就用看门狗。红锁太复杂,写的逻辑多。

 

posted @   CodingOneTheWay  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
回到顶部
点击右上角即可分享
微信分享提示