对于分布式锁,我们希望有如下特点

资源互斥:能在分布式条件下对资源占用进行限制

死锁:可重入,可超时释放,避免死锁

高性能、高可用:加锁、解锁效率高

安全性:锁只能被锁持有的客户端删除

分布式锁实现方式

数据库分布式锁

创建一个锁表,其中一个唯一字段存储需要锁的方法或者资源,比如卖票接口,锁住接口或者票的库存

通过唯一字段的insert、delete操作,实现加锁、释放锁

缺点:

  • 锁没有失效时间,可能导致死锁(定时任务扫库)
  • 锁无法重入,同一个线程释放锁之前无法再次获得锁(参考synchronized的偏向锁,加个字段判断是否是本线程)
  • 访问数据库涉及I/O操作,效率较低(考虑使用缓存)
  • 非阻塞操作失败后,需要轮询重试,占用cpu资源

ZooKeeper分布式锁

暂时不了解,以后找时间再补充,性能没有Redis分布式锁好

Redis分布式锁

主要关注超时释放的死锁问题、以及锁误删的安全性问题

1
2
3
4
5
加锁
业务
finally{
解锁
}

Redis单机环境

使用redis原生

  1. 第一版:setnx

    锁没有超时时间,可能会死锁

    • 线程崩溃,可以在finally块释放锁
    • 服务宕机,锁无法释放
  2. 第二版:setnx+ expire

    无法保证加锁和添加锁过期时间的原子性,可能会死锁

  3. 第三版:set key value ex nx

    存在误删,比如线程一执行业务时间太久,此时是线程二持有锁,释放了线程二的锁

  4. 第四版:set key value ex nx + 创建当前线程的唯一标识UUID,在执行完业务之后,对照是否是本线程再删除锁

    判断锁和删除锁并不是原子性的,所以可能还是会存在误删

  5. 第五版:set key value ex nx + 创建当前线程的唯一标识UUID,在执行完业务之后,对照是否是本线程再删除锁 + lua

    1. 减少网络开销:原本我们需要向 Redis 服务请求多次命令,可以将命令写在 Lua 脚本中,这样执行只会发起一次网络请求。
    2. 原子操作:Redis 会将 Lua 脚本中的命令当作一个整体执行,中间不会插入其它命令。
    3. 复用:客户端发送的脚步会存储 Redis 中,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑。

缺点

  • 过期时间不好控制,需要略大于业务时间,否则业务还未结束锁就被释放了
  • 非阻塞操作失败后,需要轮询重试,占用cpu资源

使用Redisson框架

  1. 第六版:Redisson分布式锁

    底层使用了看门狗机制,每10s检查一次锁,自动续期,保证业务完成锁才释放

    缺点:主从复制不及时没有同步锁信息的同时,主节点宕机,导致锁被重复获取

Redis集群环境

  1. 第七版:RedLock + Redisson

    Redis集群的处理方式,获取到半数以上主节点的锁才能算上锁成功