分布式锁需要具备的条件


  1. 获取锁和释放锁的性能要好。
  2. 判断是否获得锁必须是原子性的,否则可能导致多个请求都获取到锁。
  3. 网络中断或宕机无法释放锁时,锁必须被清除,不然会发生死锁。
  4. 可重入一个线程中可以多次获取同一把锁,比如一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁。
  5. 阻塞锁和非阻塞锁,阻塞锁即没有获取到锁,则继续等待获取锁;非阻塞锁即没有获取到锁后,不继续等待,直接返回锁失败。

分布式锁实现方式


数据库锁

一般很少使用数据库锁,性能不好并且容易产生死锁。

  1. 基于MySQL锁表

    该实现方式完全依靠数据库唯一索引来实现,当想要获得锁时,即向数据库中插入一条记录,释放锁时就删除这条记录。这种方式存在以下几个问题:

    (1) 锁没有失效时间,解锁失败会导致死锁,其他线程无法再获取到锁,因为唯一索引insert都会返回失败。

    (2) 只能是非阻塞锁,insert失败直接就报错了,无法进入队列进行重试

    (3) 不可重入,同一线程在没有释放锁之前无法再获取到锁

  2. 采用乐观锁增加版本号

    根据版本号来判断更新之前有没有其他线程更新过,如果被更新过,则获取锁失败。

Redis 缓存锁

可以基于 Redis 等缓存中间件来实现分布式锁,主要实现思路如下:

  1. 基于 setnx、expire 两个命令来实现

    基于setnx(set if not exist)的特点,当缓存里 key 不存在时,才会去 set,否则直接返回 false。如果返回 true 则获取到锁,否则获取锁失败,为了防止死锁,我们再用 expire 命令对这个 key 设置一个超时时间来避免。

    但是这里看似完美,实则有缺陷,当我们 setnx 成功后,线程发生异常中断,expire 还没来的及设置,那么就会产生死锁。

    解决上述问题有两种方案: