什么时候需要用到分布式锁?
需要对一个共享变量进行多线程访问时,为了保证在高并发的情况下,一个方法或者变量在同一时间内只能被一个线程执行。若是单机部署,我们可以用并发控制来保证。若是多机部署,多线程分布在不同的机器上,简单的并发控制已经不能满足要求,这时就需要用到分布式锁。
分布式锁的目的:
系统部署在多个服务器上时,当有多个客户端同时对某个变量进行操作,要保证变量更新的有序性和操作唯一性。
即:不能多个客户端同时对变量进行操作。
分布式锁的三种实现方式
1. 基于数据库实现分布式锁;MySQL…
在数据库中创建一个表,表中包含方法名等字段,在方法名字段上创建一个唯一索引,要想执行某个方法时,需要使用这个方法名向表中插入数据,成功插入则获取锁,执行完再删除对应 的行数据释放锁。
步骤:
① 创建一个表 method_lock(id, method_name, desc, ip, update_time)
② 多个进程同时执行某个方法,那么需要同时在method_lock中插入数据,因为method_lock 的 method_name做了唯一性约束,所以最终只能有一个进程能执行此方法。
③ 成功插入则获取锁,执行完成后删除对应的行数据释放锁。
基于数据库实现分布式锁的缺点:
① 因为这是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
② 不具有可重入的特性
③ 没有锁失效机制。(redis就可以,是指锁失效时间)
④ 不具有锁阻塞特性
…
2. 基于缓存(Redis等)实现分布式锁;
3. 基于Zookeeper实现分布式锁;
(这里先记录基于缓存的分布式锁)
2. 基于缓存(Redis等)实现分布式锁
2.1、Redis要实现分布式锁,需要满足一下条件:
- 互斥性:在任意时刻,只能有一个客户端能持有锁
- 不能死锁:客户端在持有锁的期间崩溃而没有主动释放锁,也要保证其他客户端能加锁
- 容错性:只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
2.1、使用命令介绍:
加锁:
方法一:SETNX + expire
(1)SETNX
SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0。
- val 要具有唯一性,客户端可以使用UUID.randomUUID().toString()方法生成,用来标识这把锁是属于哪个请求加的,在解锁的时候就可以有依据。
(2)expire
expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
方法二:SET key value px milliseconds nx / set()函数,
设置‘NX’参数,过期时间参数
- 替代 setnx + expire 需要分两次执行命令的方式,保证了原子性;
解锁
(3)delete
delete key:删除key
在使用Redis实现分布式锁的时候,主要就会使用到这三个命令。
2.2、实现思想:
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。