超卖问题分析过程
setnx
并发不高的系统,这样也就够了。超卖一两个问题不大,能够容忍,并且好维护
在高并发下,上图不可,可能会造成特别严重的超卖问题。
每秒几千,乃至上万订单下来,假如有一个订单(线程1)花费时间比较多,总共花了15秒;我们这边的超时时间设置为10秒,那这个订单的锁就过期了,此时处于高并发之下,有新的请求(线程2)进来,此时锁失效了,是不是就可以加锁成功了。新的请求执行了5秒钟,之前的那个订单在执行了5秒钟以后,释放了锁(此时释放的锁为新请求加的锁),那就出大问题了,线程1把线程2的锁释放掉了。此时线程3(加锁释放锁总共5秒钟),线程3执行3秒钟,线程2把线程3的锁给释放掉了(锁一直处于失效状态,高并发下,导致大量的超卖)。
只能释放自己加的锁
根本原因:我自己加的锁被别人给释放了。解决方案:只能释放自己加的锁(如下图)
高并发下,在finally里面。if判断成功,还未执行释放锁操作,此时刚刚好9.9秒-10秒。锁被释放了,此时再有一个线程,进来又可以加锁了,然后上一个线程释放了当前线程的锁,又导致了上面的问题。
锁续命-redisson(本质上是lua脚本)

抢到锁,开始执行业务,搞一个分线程,做定时任务,每过一段时间(你这个锁时间是30秒),你这个定时任务设置为10秒钟(这个时间一定要小于30秒),先判断主线程执行业务有没有结束,没有结束,那这把锁还在,代表业务并没有结束,那我把这个锁的时间重新设置为30秒
tryLockInnerAsync方法里执行的就是lua脚本,实际上后台是异步来执行的,返回一个future,加上一个监听器,监听器里面有方法operationComplete。而tryLockInnerAsync方法是异步执行的,可能后面lua脚本还没有执行,我主线程就往下执行了。而tryLockInnerAsync方法执行完了,会回调我们addListener方法,然后会进行判断是否成功,不成功直接返回,成功则去执行下面的scheduleExpirationRenewal方法,去重新刷新时间
scheduleExpirationRenewal方法如下:

一个延时任务:internalLockLeaseTime默认是30s,这里internalLockLeaseTime/3=10s,也就是说每10秒刷新一次。
这里判断,主线程加的那把锁是否还存在,如下
如果还存在,则给锁续命10s,如下图
嵌套方法的嵌套,实现锁续命
如上图,如果一直加锁不成功,会不断的去获取锁么?
如果没有加锁成功,返回的是第一个线程加锁后剩余的时间,会进入这个while(true)里面,再加一次锁,成功则返回,失败则执行下面的getEntry方法,获取一个许可,即在这边阻塞ttl的时间,时间到了以后再次进行循环获取锁(这边的阻塞不会占用CPU)
这里还有一种业务场景:设置锁30s,来了10个线程来抢锁,主线程还没有结束,所以都没有抢到,都在等着(锁的剩余时间还有25s),主线程再执行5s(总共10s)就结束了,释放了锁,而此时那10个线程还在阻塞20s?这不合适,这里会有唤醒机制,去唤醒那等待的10个线程去抢锁。这才是一个比较完美的设计。
唤醒机制:用的发布订阅功能
即,那些没有抢到锁的线程,都会去订阅一个频道,(主线程执行完毕,释放锁,会给这个频道发一个消息,告诉那些阻塞等待的线程来抢锁)
总结

1、多个线程都来抢锁,底层lua脚本保持原子性,主线程抢锁成功;
2、后台开启一个分线程,进行锁续命(每隔10秒检察是否还持有锁,如果持有则延长锁的时间)
3、其他线程没有抢到锁,则while循环,尝试加锁,并不是死循环不断加锁,而是阻塞等待,等待时间则是我们加锁失败返回的主线程执行剩余时间(即30秒-主线程已执行时间);
4、如果主线程执行时间比较快,大于其他线程阻塞等待时间,这里有唤醒机制,加锁失败的线程会监听一个channel,锁被释放则会给这个channel发送消息,唤醒之后,让阻塞等待的线程去while循环去抢锁。
Redisson分布式锁原理分析
Redis主从架构锁失效问题解析
刚刚A线程加了redis分布式锁,还没有同步到slave节点,此时master就挂掉了?(zk就没有,基于ap,超过半数的从节点写入成功了,才算成功,zk性能不如redis )
从CAP角度剖析Redis和zk分布式锁区别
Redlock分布式锁原理与存在的问题

存在问题:Client1线程在redis2加了锁,此时redis1挂了,Client2线程再redis3加了锁,此时Client1线程和Client2线程都不可能加锁成功。
如上图所示,此时Client1在redis1和redis2加锁成功,代表加锁成功,而此时redis2挂掉了,Client2线程加锁redis3和redis2从节点成功,也代表加锁成功,此时造成冲突,如何解决?
大促场景如何将分布式锁性能提升100倍
将我们多个并行的请求串行化,用redis单线程的串行话实现的,并发问题串行话,肯定不会有问题,这是他设计的语义的角度决定的。实际上redis的分布式锁的实现和我们的高并发架构是有所违背的,尽管我们用redis实现,性能比较高。在某些特别高并发下,当然这种情况并不多见,在秒杀场景下,有大量的请求去获取同一个商品,才可能有那样的问题。如果秒杀场景,不同的用户针对的是不同的商品都不会存在并发。
从粒度去分析,粒度是否合适,越小越好,有必要加锁的地方才让他串行执行,其他都给他并行执行;此时粒度已经很小了,该如何继续进行分布式锁优化?
此时就不得不讲到分段锁,通过分段锁的性能,来提升分布式锁的性能。
对于秒杀场景的商品库存,都是提前缓存到redis中来,此时,我们就可以多分几个key值;
此前,product_101_stock商品库存 = 1000;
现在,product_101_stock_1 = 100、
product_101_stock_2 = 100。。。。。分成10个key值。
这样有什么好处呢?
原来有10个线程,大不了串行排队执行,现在每个线程都可以基于不一样的key值,去减库存,这样就没有并发问题了,针对同一个商品的不同key值都搞分布式锁,此时的redis的性能是不是就提升10倍。
当然实现起来还有很多细节,比如如何取选择key值(轮训等)
可以参考concurrenthashmap实现
redis的重入锁

加的还是同一把锁,只是加锁次数+1变成了2,随之而来的也要减两次锁
大厂线上大规模商品缓存数据冷热分离实战
小商家几千上万商品信息,够了;而京东、淘宝等有上亿的商品信息,用如下代码就不合适了。
京东淘宝把所有的商品信息全部放入redis,也不太现实;毕竟能够被经常访问的商品不到整体的1%,大量的商品都是比较冷门的商品,所有并没有必要去放入缓存,浪费资源,也没有人经常去访问他们;其实我们要用到缓存的地方在哪里呢,就是那些会被经常访问的热点数据,这些是需要被放入缓存的。
1、加入缓存的商品信息设置为24H有效时间
2、读延期,即每被读一次,这个商品有效时间就重新被设置为24H;每天都有人访问的商品都能够被放在缓存中
实现一套简单的数据冷热分离方案
