## 分布式锁

---
#### 附录

* [基于 Redis 的分布式锁到底安全吗](https://mp.weixin.qq.com/s/ctbcwV4hzdB2MwGqQAA0_A)


---

高并发时，同步调用应该去考量锁的性能损耗。能用无锁数据结构，就不要用锁；能锁区块，就不要锁整个方法体；能用对象锁，就不要用类锁。

对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
说明:线程一需要对表 A、B、C 依次全部加锁后才可以进行更新操作，那么线程二的加锁顺序也必须是 A、B、C，否则可能出现死锁。


### 1.使用redis的setnx()、expire()方法

这个方案相对于memcached()的add()方案，redis占优势的是，其支持的数据类型更多，而memcached只支持String一种数据类型。除此之外，无论是从性能上来说，还是操作方便性来说，其实都没有太多的差异，完全看你的选择，比如公司中用哪个比较多，你就可以用哪个。

首先说明一下setnx()命令，setnx的含义就是SET if Not Exists，其主要有两个参数 setnx(key, value)。该方法是原子的，如果key不存在，则设置当前key成功，返回1；如果当前key已经存在，则设置当前key失败，返回0。但是要注意的是setnx命令不能设置key的超时时间，只能通过expire()来对key设置。

**具体的使用步骤如下:**

1. setnx(lockkey, 1)  如果返回0，则说明占位失败；如果返回1，则说明占位成功

2. expire()命令对lockkey设置超时时间，为的是避免死锁问题。

3. 执行完业务代码后，可以通过delete命令删除key。

这个方案其实是可以解决日常工作中的需求的，但从技术方案的探讨上来说，可能还有一些可以完善的地方。比如，如果在第一步setnx执行成功后，在expire()命令执行成功前，发生了宕机的现象，那么就依然会出现死锁的问题，所以如果要对其进行完善的话，可以使用redis的setnx()、get()和getset()方法来实现分布式锁。  


### 2.使用redis的setnx()、get()、getset()方法

这个方案的背景主要是在setnx()和expire()的方案上针对可能存在的死锁问题，做了一版优化。

那么先说明一下这三个命令，对于setnx()和get()这两个命令，相信不用再多说什么。那么getset()命令？这个命令主要有两个参数 getset(key，newValue)。该方法是原子的，对key设置newValue这个值，并且返回key原来的旧值。假设key原来是不存在的，那么多次执行这个命令，会出现下边的效果：

1. getset(key, “value1″)  返回nil   此时key的值会被设置为value1

2. getset(key, “value2″)  返回value1   此时key的值会被设置为value2

3. 依次类推！

介绍完要使用的命令后，具体的使用步骤如下：

1. setnx(lockkey, 当前时间+过期超时时间) ，如果返回1，则获取锁成功；如果返回0则没有获取到锁，转向2。

2. get(lockkey)获取值oldExpireTime ，并将这个value值与当前的系统时间进行比较，如果小于当前系统时间，则认为这个锁已经超时，可以允许别的请求重新获取，转向3。

3. 计算newExpireTime=当前时间+过期超时时间，然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。

4. 判断currentExpireTime与oldExpireTime 是否相等，如果相等，说明当前getset设置成功，获取到了锁。如果不相等，说明这个锁又被别的请求获取走了，那么当前请求可以直接返回失败，或者继续重试。

5. 在获取到锁之后，当前线程可以开始自己的业务处理，当处理完毕后，比较自己的处理时间和对于锁设置的超时时间，如果小于锁设置的超时时间，则直接执行delete释放锁；如果大于锁设置的超时时间，则不需要再锁进行处理。



**常用的四种方案**

1. 基于数据库表做乐观锁，用于分布式锁。

2. 使用memcached的add()方法，用于分布式锁。

3. 使用redis的setnx()、expire()方法，用于分布式锁。

4. 使用redis的setnx()、get()、getset()方法，用于分布式锁。


**其它资料：**

http://mp.weixin.qq.com/s/JTsJCDuasgIJ0j95K8Ay8w?utm_source=tuicool&utm_medium=referral

















