# 分布式锁
## 概念
#### 什么是分布式锁
在很多场景中,我们为了保证数据的最终一致性,需要一些方案来支持,比如分布式事务、分布式锁等,他的实现有很多种方式。
##### 一、为什么使用分布式锁
在单机应用中,如果需要对某一个共享变量进行多线程同步访问时,可以用到锁和一些机制来处理,但是在集群情况下,一个服务需要部署到几台服务器上通过负载均衡来访问,这时单机中的锁就会不起作用了,所以需要一个能处理集群情况下多线程访问共享变量却仍然能保证数据一致性的机制,就是分布式锁。
##### 二、分布式锁应具备哪些条件
1、在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
2、高可用的获取锁与释放锁;
3、高性能的获取锁与释放锁;
4、具备可重入特性;
5、具备锁失效机制,防止死锁;
6、具备非阻塞锁特性,即没有获取到锁将直接返回获取锁失败
##### 三、分布式锁的三种实现方式
分布式场景中的数据一致性问题一直是重要的话题,分布式有一个很重要的理论是CAP,而任何一个分布式系统都无法同时满足CAP三个(Consistency 一致性、Availability 可用性、Partition tolerance 分区容错性),最多只能同时满足两项,所以一般的分布式系统都是CP或者AP的,在很多时候都需要牺牲强一致性来换取系统的高可用,往往只要保证最终一致性,只要最终的这个时间是用户可以接受的范围内就行。
三种实现方式:
> 基于数据库实现分布式锁;
>
> 基于缓存(Redis等)实现分布式锁;
>
> 基于Zookeeper实现分布式锁
## 实现方式
##### 一、基于数据库的实现方式
设置数据库表中的字段为唯一索引,因为有唯一性索引,所以在多个请求同时提交到数据库时,数据库只会保证一个操作成功,那么我们就认为该线程获取了该方法的锁
``` sql
INSERT INTO method_lock (method_name, desc) VALUES ('methodName', 'testName');
```
这时只要能成功插入即获取锁成功,业务逻辑执行完毕删除对应的行数据释放锁
```sql
delete from method_lock where method_name ='methodName';
```
还有其他实现方式,待完善......
缺点:
> 1、因为是基于数据库实现的,数据库的可用性和性能直接影响了分布式锁的可用性和性能,所以数据库应该要双击部署、数据同步、主备切换等。
>
> 2、不具备可重入的特性,因为在一个线程释放锁之前,数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前相同,若相同则直接获取锁。
>
> 3、没有锁失效机制,有可能成功插入数据后,服务器宕机了,对应的数据没有被成功删除,所以其他线程一直获取不到锁,所以要在表中新增一列用于记录失效时间的,并定时清除这些数据。
>
> 4、在实施的过程中会遇到各种各样的问题,为了解决这些问题,实现方式会越来越复杂,资源开销会很大,性能问题需要考虑。
##### 二、基于Redis的实现方式
Redis实现分布式锁主要因为Redis有很高的性能,并且Redis命令对分布式锁的支持很好,实现很方便。
命令:
> SETNX:SETNX key val:当且仅当key不存在时,set一个key为val的字符串,返回1;若key存在,则什么都不做,返回0.
>
> expire:expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
>
> delete:delete key:删除key
实现思想:
1)、获取锁的时候,使用setnx加锁,并使用expire命令设置超时时间,超过时间则自动释放锁。
2)、获取所得时候也设置一个超时时间,如果超过此时间则放弃获取锁。
3)、释放锁的时候通过UUID等唯一性标识判断是不是该锁,若是该锁,则执行delete操作。
##### 三、基于ZooKeeper的实现方式
ZooKeeper是一个为分布式应用提供一致性服务的开源组件,内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一的文件名。
实现思想:
1)、创建一个目录mylock;
2)、线程A想获取锁就在mylock目录下创建临时顺序节点;
3)、获取mylock目录下所有的子节点,然后获取比自己小的兄弟节点,如果不存在,则说明当前线程顺序号最小,那么获取锁;
4)、线程B获取所有节点,判断自己不是最小节点,设置监听比自己小的节点;
5)、线程A处理完,删除自己的节点,线程B监听到变更时间,判断自己是不是最小的节点,如果是则获取锁。
优点:具备高可用,可重入,阻塞锁等特性,可解决失效死锁问题
缺点:因为需要频繁的创建和删除节点,性能上不如Redis方式。