大家好,我是北军。很多采访都提到了分布式锁,那么我们是不是应该去了解一下呢?一、前言在分布式应用中,有时我们需要一个方法一次只能由一个线程执行。这个时候我们可能会用到分布式锁。分布式锁需要具备以下特点:互斥锁在同一时间只能由一个线程持有。超时释放超时释放主要用于避免死锁,防止不必要的线程等待和资源浪费。Reentrancy当一个线程持有锁时,它可以请求再次加锁高性能、高可用的锁释放锁操作使用尽可能少的资源来提高性能。同时还要保证高可用,防止分布式锁意外失效。目前分布式锁有很多,解决方案如下:基于数据库实现分布式锁基于缓存(redis、Hazelcast)实现分布式锁等基于Zookeeper2实现分布式锁。数据库分布式锁2.1基于表记录的分布式锁在数据库中创建锁表,并在需要的字段上创建唯一索引,使用锁时插入数据,插入成功时获取锁。删除数据。也可以加入版本控制,使其成为乐观锁。获取锁:插入数据成功,执行业务逻辑释放锁:删除数据2.2基于数据库行锁的分布式锁使用select*forupdate获取数据库数据锁。如果where后面的条件加在唯一索引上,说明使用了行锁。分布式锁的使用顺序如下。获取锁:SELECT*FROMdatabase_lockWHEREid=1FORUPDATE;。执行业务逻辑。释放锁:COMMIT。3、Zookeeper分布式锁Zookeeper之所以能够实现分布式锁,主要是因为Zookeeper中多个线程创建同一个节点时,只有一个线程可以创建成功。Zookeeper中有临时节点和持久节点。其中,临时节点会在服务器会话过期后被删除。相对而言,持久节点不会在服务端session过期后被删除,需要客户端主动删除。在上述类型节点后添加数字后缀,即路径+数字后缀,以保证其唯一性和有序性。分布式锁的实现原理如下:创建一个锁目录,供分布式锁使用某个线程。如果要获取锁,在这个目录下创建一个临时的序号节点,获取这个目录下的所有子节点,然后找到序号比自己小的节点,如果不存在,则当前线程创建的节点为最小节点,此时获取锁。如果其他线程想要获取锁,它们也会创建一个临时的有序节点。如果是序号最小的节点,则获取锁,否则监听比自己小的次小节点。持有分布式锁的线程操作完成后,删除自己的临时节点。下一个最大的节点监听到变化事件后,如果判断是序号最小的节点,就会获得锁。Zookeeper对分布式锁的实现具有高可用、可重入、阻塞等特点。由于客户端断开连接时临时节点会自动删除,因此无需担心死锁。但是频繁的删除和创建节点会导致性能低于Redis分布式锁。4Redis分布式锁4.1SETNXsetnx命令只会在key不存在的情况下将key设置为value,key和value都可以设置为业务相关的名字。但是不满足超时释放的要求。如果使用expire设置过期时间,也有可能setnx成功后,由于各种原因,expire没有执行成功,导致超时无法释放锁。4.2SETNX扩展命令设置键值[EXseconds][PXmilliseconds][NX|XX]#EXsetexpirationtimeinsecondssetlockVALUEEX10#PXsetexpirationtimeinmillisecondssetlockVALUEPX10000#NX设置键值仅当键不存在时。setlockVALUEEX10NX#XX仅当密钥存在时才设置密钥。SETlockVALUEEX10XXset扩展命令可以完全替代SETNX、SETEX、PSETEX等的功能。设置过期时间可以使用set扩展函数来完成,它是一个原子操作。上面提到的分布式锁也存在一些问题:如果线程获取到锁后执行时间过长,会导致锁提前释放。如果线程A还没有执行完操作,随着时间的推移,锁被释放,此时线程B再次获取锁。线程B持有锁,但线程A可能会执行DEL操作来释放锁。需要避免在长时间运行的任务中使用上述分布式锁,没有按时执行的线程不会影响其最终结果。另外,可以在锁的值中设置一些唯一的值,用于在删除key之前验证锁是否持有。而校验和删除需要使用Lua脚本来保证其删除操作的原子性。上面的分布式锁还需要解决一个重入问题。4.3Redisson分布式锁Redisson是一个基于Redis的Java内存数据网格,它充分利用了Redis键值数据库提供的一系列优势。同时提供功能丰富的分布式锁。Resisson内部会有一个守护线程监控锁,在redisson实例关闭之前锁的有效期会不断延长。并且可以自定义超时检查的时间间隔,也可以指定锁定时间。此外,它还支持公平锁(FairLock)、互锁(MultiLock)、红锁(RedLock)、读写锁(ReadWriteLock)、RSemaphore和RCountDownLatch等类似Java提供的多线程工具。其中,RedLock是基于多个Redis集群关联的锁,可以大大提高锁的可用性和安全性。关于Redisson,我们后续文章会继续讲到,敬请期待!在分布式应用中,有时我们需要一个方法一次只能由一个线程执行。这个时候我们可能会用到分布式锁。
