redis 作为分布式锁的东西 分布式锁的应用
redis,zk,数据库这些都可以实现分布式锁
我们今天主要基于redis实现的分布式锁,而且要求性能要好
基于一个小的业务场景来说,就比如说秒杀中的减库存,防止超卖
这种代码就会有并发问题,比方说3个线程 同时查出来 之后会set 299 此时就超卖了
这就是我们典型的超卖问题,我们可以加锁,比如说我们常见的sy,JVM进程级别的锁
这样就可以解决我们的问题,如果说我们此时上线部署之后就一台服务器,也就一个tomcat
单机环境 是没有问题 假设我们做集群项目的时候 此时就会出现问题
我们的项目基本上都是 基于nginx 进行转发,利用nginx做负载均衡,
在nginx的upstream中配置负载均衡的地址
一般来说我们都会搞这些集群架构部署,这种场景下我们jvm 级别锁就不可用了
我们可以模拟高并发场景利用jmeter基于高并发下发请求
jmeter--->nginx---->tomcat1/tomcat2
发现如果此时利用jvm级别的锁就会出现问题
一般情况下这种问题要解决的话 就用分布式锁 分布式锁 可以用redis
我们一般使用redis的setnx来实现分布式锁
基于setnx命令我们就可以实现一个分布式锁,在redis这边 只会有1个请求执行成功
这样我们就写完了
我们基于这种简单的setnx 实现的分布式锁有什么问题呢
Q1 如果我第一个线程拿到锁执行一半的时候 就会抛异常 抛异常之后 我就不会delete掉了
这个时候就相当于死锁了,key永远在redis中 其他线程要想来执行就会执行不成功
我写个try catch finally 意思是如果跑异常了 我还可以执行redis.delete
如果现在执行一半后 客户端宕机了,或者被运维重启了,呢么此时finally也是不能被执行的,我们可以设置一个超时时间,比方说10s,
我们可以加个超时时间,意味着如果你业务宕机了,过段时间 redis内部自己就释放锁了
如果并发特别大的情况下.这种代码也会造成超卖,在并发情况下 接口的响应可能会变慢
如果线程一执行了一半,超时时间到了, 这个时候线程2就会进来,然后此时线程一就会吧线程2的这个锁解开
线程1 删除(释放了)了线程2的锁 此时就会出现问题
出这个问题的根本点就在于 我自己加的锁被别人释放了
我们可以加个uuid就是说 我只能释放我自己加的锁,这个时候的代码就很完善了
假设一个线程枷锁成功了执行到这行代码的时候,卡顿一会,我们的10s到期了
其他线程又可以基于这个Lock进行加锁,这个时候线程1还是释放了线程2的锁
所以我们要保证这2行代码的原子性
我们的业务没有执行完毕 锁的超时时间就结束了
针对于分布式锁 有个锁续命机制
分线程给锁进行续命,判断主线程有没有业务执行完毕,如果没有结束就续命
每过10s之后就续命,我的主线程一直执行 我的锁就不会失效,因为他一直被分线程做续命操作
以后我主线程要是把锁释放了,分线程会判断锁还被主线程持有 如果持有 就会做续命操作
如果不持有就不再跑任务了
Redisson 就实现了这种机制
lock.lock 就可以获取得到锁了
Lock.unLock 加锁 解锁
https://www.cnblogs.com/xiaoyangabc/p/16906922.html
redis枷锁的核心流程, 假设有2个线程同时都是一个key执行 redis调用他的lock方法
只有1个线程执行成功,假设线程1执行成功。 线程2 执行没有成功
他会whlie循环自璇 尝试加锁
线程1 加锁成功 他会另外开启一个分线程,分线程就会对这个key 进行续命,
这就是整个redis分布式锁续命的核心业务流程
redis分布式锁底层是基于Lua脚本来写的,
lua脚本,减少网络开销。可以批量执行redis命令,类似于管道可以把一批命令打包发给redis 服务端去执行
比如说原来有4条命令,我就需要发送4次远程交互(网络调用)
但是我用管道或者lua脚本的话 就只需要一次网络调用
对于Lua脚本也一样,假设我有10条redis命令,我也可以放到lua脚本来一次性发给服务端去执行
lua脚本是支持原子操作的,一段Lua脚本,要么同时成功,要么同时失败
lua脚本都执行完毕 之后 其他命令才可以执行(因为redis服务端是单线程来执行任务的)
我这个命令执行的中间是不可以被其他线程插入的
Lua 支持事务在redis中可以执行lua脚本
用lua脚本是个原子操作
redis分布式锁的原理,当多个线程进行抢锁时候,只有一个线程来设置成功,其他没有抢到锁的线程就会while自旋尝试加锁,通过lua脚本进行加锁,加锁成功后就会对其进行续命,加锁失败就会自璇,续命锁
(Lua脚本进行续命) 每次续命1/3(判断主线程持有的呢吧锁是否还存在,如果存在我就进行续命,吧主线程的超时时间重新设置为30S ,如果不存在,我就结束)
说白了redis实现分布式锁的核心东西也就完成了,基本上是基于Lua来实现的 借助redis的单线程帮我们实现原子性.来解决并发问题
当我们其他线程如果没有加锁成功,lock 没有加锁成功会怎么办.假设有10个线程while循环枪锁 他的cpu 就会100%,如果说我们redis 没有抢到锁 他自选再去加锁 他是怎么来做的
其他线程会返回锁剩余加锁时间的剩余时间 并且阻塞让出cpu
比如说第一个线程执行了5S之后
假设我我第二个线程会尝试的过来加锁 如果没有加锁成功会再这里等25S 阻塞等待会让出cpu
25s过后之后再尝试加锁while循环间断性的加锁
假设有1000个线程来了同时超时 同时while循环 此时是非公平锁 也就是redis 默认 是非公平锁
所以 多个线程来抢锁时,底层会基于lua脚本进行加锁,但是只有一个线程来加锁成功,抢锁成功后后台开启一个分线程来对其进行续命,其他线程如果没有抢到锁 会while尝试加锁 但是 并不是死循环
而是会阻塞等待,返回第一次尝试加锁返回的时间,并且此时会让出cpu片段
如果此时业务时间比较快,快速吧这把锁释放了. 呢我其他线程不能一直阻塞 在解锁的方法中有一个唤醒机制 通过redis的发布订阅 他会往Queue发消息
没有加锁成功的线程会去监听Queue,呢么其他线程就会唤醒阻塞 继而继续while循环继续抢锁