redis分布式锁的应用

redis 作为分布式锁的东西 分布式锁的应用
redis,zk,数据库这些都可以实现分布式锁
我们今天主要基于redis实现的分布式锁,而且要求性能要好
基于一个小的业务场景来说,就比如说秒杀中的减库存,防止超卖

redis分布式锁的应用_第1张图片

这种代码就会有并发问题,比方说3个线程  同时查出来 之后会set 299  此时就超卖了
这就是我们典型的超卖问题,我们可以加锁,比如说我们常见的sy,JVM进程级别的锁

redis分布式锁的应用_第2张图片

这样就可以解决我们的问题,如果说我们此时上线部署之后就一台服务器,也就一个tomcat 
单机环境 是没有问题 假设我们做集群项目的时候 此时就会出现问题
我们的项目基本上都是 基于nginx 进行转发,利用nginx做负载均衡,
在nginx的upstream中配置负载均衡的地址

redis分布式锁的应用_第3张图片

一般来说我们都会搞这些集群架构部署,这种场景下我们jvm 级别锁就不可用了
我们可以模拟高并发场景利用jmeter基于高并发下发请求
jmeter--->nginx---->tomcat1/tomcat2
发现如果此时利用jvm级别的锁就会出现问题

redis分布式锁的应用_第4张图片

一般情况下这种问题要解决的话 就用分布式锁 分布式锁 可以用redis
我们一般使用redis的setnx来实现分布式锁
基于setnx命令我们就可以实现一个分布式锁,在redis这边 只会有1个请求执行成功
这样我们就写完了

redis分布式锁的应用_第5张图片

我们基于这种简单的setnx 实现的分布式锁有什么问题呢
Q1 如果我第一个线程拿到锁执行一半的时候 就会抛异常 抛异常之后 我就不会delete掉了
这个时候就相当于死锁了,key永远在redis中  其他线程要想来执行就会执行不成功

redis分布式锁的应用_第6张图片

我写个try catch finally 意思是如果跑异常了 我还可以执行redis.delete 	
如果现在执行一半后 客户端宕机了,或者被运维重启了,呢么此时finally也是不能被执行的,我们可以设置一个超时时间,比方说10s,

redis分布式锁的应用_第7张图片

我们可以加个超时时间,意味着如果你业务宕机了,过段时间 redis内部自己就释放锁了
如果并发特别大的情况下.这种代码也会造成超卖,在并发情况下 接口的响应可能会变慢

如果线程一执行了一半,超时时间到了, 这个时候线程2就会进来,然后此时线程一就会吧线程2的这个锁解开
线程1 删除(释放了)了线程2的锁  此时就会出现问题
出这个问题的根本点就在于  我自己加的锁被别人释放了

redis分布式锁的应用_第8张图片

我们可以加个uuid就是说 我只能释放我自己加的锁,这个时候的代码就很完善了

redis分布式锁的应用_第9张图片

假设一个线程枷锁成功了执行到这行代码的时候,卡顿一会,我们的10s到期了
其他线程又可以基于这个Lock进行加锁,这个时候线程1还是释放了线程2的锁
所以我们要保证这2行代码的原子性

我们的业务没有执行完毕 锁的超时时间就结束了
针对于分布式锁 有个锁续命机制 
分线程给锁进行续命,判断主线程有没有业务执行完毕,如果没有结束就续命
每过10s之后就续命,我的主线程一直执行 我的锁就不会失效,因为他一直被分线程做续命操作
以后我主线程要是把锁释放了,分线程会判断锁还被主线程持有 如果持有 就会做续命操作
如果不持有就不再跑任务了
Redisson  就实现了这种机制 

redis分布式锁的应用_第10张图片

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分布式锁的应用_第11张图片

redis分布式锁底层是基于Lua脚本来写的,
lua脚本,减少网络开销。可以批量执行redis命令,类似于管道可以把一批命令打包发给redis 服务端去执行
比如说原来有4条命令,我就需要发送4次远程交互(网络调用)
但是我用管道或者lua脚本的话 就只需要一次网络调用
对于Lua脚本也一样,假设我有10条redis命令,我也可以放到lua脚本来一次性发给服务端去执行
lua脚本是支持原子操作的,一段Lua脚本,要么同时成功,要么同时失败
lua脚本都执行完毕 之后 其他命令才可以执行(因为redis服务端是单线程来执行任务的)
我这个命令执行的中间是不可以被其他线程插入的
Lua  支持事务在redis中可以执行lua脚本
用lua脚本是个原子操作

redis分布式锁的应用_第12张图片
redis分布式锁的应用_第13张图片

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 默认 是非公平锁

redis分布式锁的应用_第14张图片

所以 多个线程来抢锁时,底层会基于lua脚本进行加锁,但是只有一个线程来加锁成功,抢锁成功后后台开启一个分线程来对其进行续命,其他线程如果没有抢到锁 会while尝试加锁 但是 并不是死循环
而是会阻塞等待,返回第一次尝试加锁返回的时间,并且此时会让出cpu片段

如果此时业务时间比较快,快速吧这把锁释放了. 呢我其他线程不能一直阻塞 在解锁的方法中有一个唤醒机制 通过redis的发布订阅 他会往Queue发消息
没有加锁成功的线程会去监听Queue,呢么其他线程就会唤醒阻塞  继而继续while循环继续抢锁
 

你可能感兴趣的:(1024程序员节)