目录
一 互斥锁
二 分布式锁
Redis实现分布式锁
redisson实现分布式锁
可重入性:
主从一致性(性能差):
一 互斥锁
假设我们现在有一个业务要实现秒杀优惠券的功能,如果是一个正常的流程,线程之间应该是这样运作的。
线程1先查询优惠券,如果库存充足,那么扣减库存,然后线程2来查询优惠券信息,如果充足,那么就扣减库存,但是这只是理想的情况。
那么如果出现图2的这种情况呢?
线程1在查询优惠券信息的时候发现库存是充足的,线程2查询的时候也是充足的,他俩都可以进行减库存的操作,假如优惠券只剩1张了,那么谁得到这个优惠券呢?这就是业务中可能出现的问题。
此时就需要用互斥锁来解决问题了
如图,假如线程1在获取互斥锁以后,线程2来了之后就只能发起获取锁的请求,只有当线程1操作完了之后释放锁,线程2获取到锁,才可以进行接下来的操作,这样就不会出现库存超卖的情况。
但是还有一个问题,上面所说的这种情况是针对单个Redis服务器进行加锁的,但是实际的业务逻辑中可能会有多个用户,访问多个Redis服务器,那么这时候要怎么解决呢?
二 分布式锁
假设我的代码部署到了多个tomcat中,每一个tomcat操控一个JVM,此时8080的虚拟机知道8081的线程1也在同时访问优惠券信息吗?这显然是不知道的。
此时就需要用到一个独立于tomcat之外的分布式锁来进行判断
如图,当8080的线程1进行访问时,其余的端口和线程都不可以进行访问,此时就达到了分布式锁的效果,有效的解决了超卖问题。要注意,分布式锁是加在Redis上的,不是自己的代码中。
Redis实现分布式锁
Redis实现分布式锁主要是利用Redis的setnx命令
SET LOCK VALUE NX EX 10 //加锁
DEL key //释放锁
第一条代码是加锁并且给锁设置过期时间,第二行代码是删除锁。如果不给锁设置过期时间,那么就有可能产生死锁的现象。即线程1执行时间过长,线程2一直在等待锁释放。
redisson实现分布式锁
redisson中也有分布式锁的实现
RLock lock = redissonClient.getLock("lock");
try{boolean isLock = lock.tryLock(10,TimeUnit.SECONDS);if(isLock){System.out.println("执行业务");}
} finally{lock.unlock();}
首先,线程1获取到锁之后,然后去操作Redis,也会去通知看门狗系统,而看门狗系统会每隔释放时间(redisson默认30秒)/3去给锁续期,希望业务完成之前不要因为锁到期而引发线程安全问题,此时线程1执行完,线程2获取到锁就可以继续执行了。而加锁和设置过期时间都是基于lua脚本完成的,这样可以保证操作的原子性。
可重入性:
假设下图是线程1所执行的代码,在redis当中会按照哈希结构去进行存储,key为锁的key,value会存储一个键值对,键为线程名称,value为锁的次数默认为0,加一次锁就+1,释放一次锁就-1.
如上图,执行add1( )时,Thread1第一次加锁时,value会被改成1,当add2( )想获取锁时,此时Redis会进行判断,你是不是线程1来的,发现add2( )的线程名为Thread1,那么此时该锁可以重入的,value值会变成2,当add2( )执行完以后,释放锁,value又变为1,add1( )执行完以后,value变成0,此时的锁才是真正被线程1所释放了。
主从一致性(性能差):
如图,如果是在集群模式下的Redis服务器势必会有主节点和从节点,当线程1过来获取到一把锁时,主节点刚好宕机了,集群又重新选了一个主节点,线程2此时又获取到了和线程1同样的一把锁,这样就会产生锁冲突的现象。
针对这样的情况就要加红锁(RedLock):在多个Redis实例上加锁,而不是只在一个节点上进行加锁,这样就可以避免锁重复。
但是这样实现相对来说比较复杂,因此AP思想和CP思想(zookeeper)可以解决该问题。
所以说性能和复杂是负相关的。