一、分布式锁概述
在分布式系统架构下,资源共享不再是单机下的线程竞争,而是跨JVM进程之间的而资源共享,因此JVM锁不再满足业务需求,需要引入适合分布式系统的“锁”。
二、分布式锁设计原则
排他性:被共享资源同意时间内只能被一台机器上的一个线程使用,这点和jvm锁是一个道理。
避免死锁:线程获取到锁,在执行完业务之后,一定要释放锁(包括异常情况下释放)。
高可用:获取和释放锁要保证高可用和性能。
可重入:最好是可重入锁,即当前机器的当前线程如果没有获取到锁,那么在等待一定时间后一定要保证可以再被获取到。
公平锁:不是硬性要求,指的是不同线程获取锁时最好保证几率一样。
三、分布式锁实现方式
1. 基于数据库级别的锁
乐观锁:基于CAS(compare and swap)原理,即在表中添加一个version字段,每次更新的时候以version作为条件,只能有一个线程更新成功。
悲观锁:总是假设事情发生在最坏的情况,因此每次获取数据时都会上锁,阻塞其他线程,比如行锁、表锁、读锁、写锁。mysql和oracle是通过for update来实现的
select fields from table for update
2. 基于redis的原子操作
主要通过Redis原子操作SETNX和EXPIRE来实现,因为redis是单线程机制,所以同一时刻,同一节点只允许一个线程执行某种操作,所以满足原子性。
构造一个与共享资源相关的key
调用SETNX命令获取锁,并且设置过期时间,以防死锁
释放锁
3. 基于zookeeper的互斥排他锁
zookeeper分布式锁基于zk顺序临时节点和watcher机制,推荐使用框架curator。
原理:在每一个节点下面创建子节点的时候,选择EPHEMERAL_SEQUENTIAL或者PERSISTENT_SEQUENTIAL。新的子节点后面,会加上一个次序编号。这个次序编号,是上一个生成的次序编号加一。
所以,可以规定编号最小的那个节点获得锁,其他节点只要监听自己前一个节点(通过订阅比自己小的节点的删除事件),并判断自己是不是最小的那个节点就可以了。
步骤:
创建一个根节点,最好是持久节点,代表分布式锁
需要占用锁的时候,在根节点下创建临时有序节点。
判断自己是否为当前节点列表中最小子节点,如果是则获得锁,否则监听前一个子节点的变更消息。
处理业务流程,处理完成后,删除自己的子节点,释放锁。