1、什么是分布式锁?
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。
2、使用场景
如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。
3、实现方式
单机Redis锁
基本锁
原理:利用Redis的setnx如果不存在某个key则设置值,设置成功则表示取得锁成功。
缺点:如果获取锁后的进程,在还没执行完的时候挂调了,则锁永远不会释放。
改进型
改进:在基本型是锁上的setnx后设置expire,保证即使获取锁的进程不主动释放锁,过一段时间后也能自动释放。
缺点:
setnx与expire不是一个原子操作,可能执行完setnx该进程就挂了。
当锁过期后,该进程还没执行完,可能造成同时多个进程取得锁。
Redlock
Redlock是Redis的作者antirez给出的集群模式的Redis分布式锁,它基于N个完全独立的Redis节点(通常情况下N可以设置成5)。
步骤
1. 获取当前时间(毫秒数)。
2. 按顺序依次向N个Redis节点执行获取锁的操作。获取锁的操作与单机锁一样。
3. 如果获取锁成功的节点数>=N/2+1,则再计算获取锁的时间有没有超过锁过期时间(可考虑设置一个必须留多长的时间给代码块执行),如果超过了则认为取锁失败。
4. 如果取锁失败则应该对所有节点进行释放锁的操作。
优化
当有5个节点,某次上锁对a,b,c三个节点上锁成功,而后c马上down了,此时还没通过AOF或RDB写入磁盘。而后c又马上恢复,此时c没有上锁数据,因此此时可能出现c,d,e三个节点被别的进程上锁。所以在节点恢复时应该延时起码一个锁的过期时间。
zookeeper分布式锁
zookeeper基本锁
原理:利用临时节点与watch机制。每个锁占用一个普通节点/lock,当需要获取锁时在/lock下创建一个临时节点,创建成功则表示获取锁成功,失败则watch/lock节点,有删除操作后再去争锁。临时节点好处在于当进程挂掉后能自动上锁的节点自动删除即取消锁。
缺点:所有取锁失败的进程都监听父节点,很容易发生羊群效应,即当释放锁后所有等待进程一起来创建节点,并发量很大。
zookeeper锁 优化
原理:上锁改为创建临时有序节点,每个上锁的节点均能创建节点成功,只是其序号不同。只有序号最小的可以拥有锁,序号不是最小的锁则watch序号排在前面的一个节点(公平锁)。
执行步骤:
1. 在/lock节点下创建一个有序临时节点(EPHEMERAL_SEQUENTIAL)。
2. 判断创建的节点序号是否最小,如果是最小则获取锁成功。不是则取锁失败,然后watch序号比本身小的前一个节点。
3. 当取锁失败,设置watch后则等待watch事件到来后,再次判断是否序号最小。
4. 取锁成功则执行代码,最后删除本身节点,释放了锁。
分布式锁存在的问题
均可能存在多进程拥有锁的情况。redis锁主要是expire时间与代码执行时间的问题,zk锁的问题在于zk是通过心跳监控进程存活状态,如果进程进行GC pause或者因为网络原因导致很长时间没与zk联系,则将导致zk认为进程已挂,而后锁自动释放,而此时进程并未挂任然在执行。
Redlock锁的时间问题。由于redis的expire的实现是通过pexpireat,如果某个节点发生时钟跳跃,则该节点可能过早释放锁导致一系列问题。