一、为了确保分布式锁的可用性,需要确保锁在任意时刻,能同时满足以下四个条件
1.互斥性:在任意时刻,只有一个客户端能持有锁
2.不会发送死锁,即时有一个客户端在持有锁期间崩溃而没有主动解锁,也需要保证其他客户端能加锁
3.具有容错性,只要大部分的Redis节点正常运行,客户端就可以加锁解锁
4.加锁和解锁必须是同一个客户端

二、Redis分布式锁和Java锁的区别
1. 如果是分布式部署的话,那么Java锁是锁当前机器上的请求,无法对其他机器的请求进行加锁,因为Java锁用的是jvm的机制,只在本机生效
2. 姑且说项目就是单机部署的,那么在加锁的时候,Java锁的粒度会更大,Java锁会对接口的其他请求全部阻塞,但是分布式锁,只会对接口的某一个key进行请求的时候,加锁;因为Redis分布式锁有key的入参,如果同时key1和key2都来请求接口,那么Redis分布式锁可以同时执行,但是Java锁,不行,会阻塞key2的请求

三、Redis分布式锁和zk分布式锁怎么选择:
1.Redis采用的是AP模式,zk采用的是CP模式
2.Redis分布式锁简单粗暴,获取不到锁,就直接不断的重试,比较消费资源、性能
3.Redis本身的设计就不是强一致性的,所以,在一些极端的场景下,会出现问题,但是大部分情况下,是不会遇到所谓的极端复杂场景,所以,使用Redis锁也是一个选择
4.zk的设计就是强一致性,如果获取不到锁,就添加一个监听,不用一直轮询,但是zk也有其缺点,如果有较多的客户端频繁的申请加锁解锁,对zk集群的压力比较大

Redis是典型的AP模型,也就是保证高可用,和数据的最终一致性,基于这个模型,在使用Redis分布式锁的时候,就有可能会出现问题

redisson分布式锁:
1.线程A加锁,加锁成功,然后master节点将数据同步到slave,在同步的时候,出现异常,导致master宕机,但是数据也没有同步到slave
2.此时slave升级为master,那此时的话,线程B继续对同一个key加锁,会加锁成功,因为此时的master,没有这把锁记录

redLock:
要求多个节点独立部署,在加锁的时候,会依次去多个节点上加锁,只要有半数以上的节点,加锁成功,就会认为当前线程加锁成功,那基于这个前提,我们实际举例
1.当前是三台机器集群部署,线程A加锁,加入此时对1、2、3三台机器加锁成功,
2.线程B也过来加锁,对3、4、5机器进行加锁,但是3这台机器会加锁失败
3.假如出现极端场景,线程A在释放锁的时候,先释放了3这台机器,然后在去释放1和2的时候,由于网络或者其他原因,导致线程暂停,此时线程B会加锁成功
4.线程B加锁成功之后,会去持有锁,执行自己的业务逻辑,但是在线程A唤醒的一瞬间,当前三台Redis机器上,会同时有两个线程同时持有同一把锁

四、分布式锁加锁思路

1.进入方法之后,首先查询缓存,如果缓存命中,return
2.如果缓存未命中,则查询数据库,但是在查询数据库之前,进行加锁
3.加锁之后,再查询一遍缓存,这里查询缓存,是为了防止第二个阻塞在加锁这里的线程,在获取到锁之后,直接查询数据库
4.如果缓存命中,就返回,如果未命中,查询数据库,并放入到缓存中
5.最后释放锁


String value = redisTemplate.get(key);
if(value != null){
    return value;
}
redisson.lock();
try{
  /**
  * 这里加锁之后,还是再次查询Redis,防止这种场景:
  * A和B连个线程同时执行该方法,A线程获取到锁,B线程阻塞到redisson.lock()这里;那么A在从数据库中查询到数据之后,会将数据写入Redis,在A释放了锁之后。B获取了执行权限,在B获取执行权限的时候,A已经将数据写入了Redis,此时,B就无需再次查询数据库
  */
  String valueTemp = redisTemplate.get(key);
  if(valueTemp != null){
        return valueTemp;
    }
  OerChannelEntity operChannelEntity = operChannelDao.selectById(key);
  redisTemplate.set(key,operChannelEntity.getName);
  return operChannelEntity.getName;
}finally{
  redisson.unlock();
}