前言
在分布式系统中, 每个服务器都是一个单独的进程, 因为进程之间的执行顺序又是随机的, 很容易导致严重问题.
以买票系统为例, 如果没有分布式锁的话, 由于进程的随机性, 可能会导致一张票卖给两个人引发"超卖"问题. 所以引入分布式锁来解决:

例如当买票服务器1想进行买车次001的票时, 会先访问Redis, 寻找有没有其他服务器在买车次001的票, 如果没发现就在Redis上设置一个关于001的key. 此时如果其他服务器也想买001的票但是发现Redis上已经存在了这个key, 则不会再买票. 这样就达到了加锁的目的.
用Redis进行"加锁"的具体流程
利用Redis中的"set nx"
Redis中的"set nx"命令格式如下:
SET key value NX
这样的效果是: 如果Redis上存在这样的Key就不会进行设置, 没有才会设置Key.
当以这样的方式进行"加锁"操作后, 我们同样需要对"锁"进行释放. 而利用del命令即可删除Key达到释放的效果.
为Key设定一个过期时间
如果一个服务器已经对Redis添加了Key, 但是这个服务器意外崩溃, 此时的"锁"还没有释放, 其他服务器只能进行等待. 为避免这一情况, 设置Key时需要添加一个"过期时间"的选项, 命令格式如下:
SETNX key value NX EX (时间, 单位为秒)
避免这个服务器长时间占用"锁".
为服务器设置校验ID
有一种情况: 服务器1进行了set nx加锁了, 但服务器2执行了del释放了锁. 为避免这一情况, 需要对释放锁操作进行一个条件判定: 为所有服务器设置一个ID, 在释放锁时对服务器ID进行判定, 是否为加锁的服务器, 确定后再删除.
设置校验ID的流程如下(以伪代码形式说明): 
引入Lua
根据上面设置服务器ID的过程中, 可以发现这个操作并非原子性的, 这就意味着可以被其他操作来"插队", 例如可能在验证的后另外一个服务器又进行了"set"操作, 当执行到del时就会释放另外一个服务器的锁了

为了解决这种问题, 采用Lua脚本的方式将这种非原子性的命令写入进去, 因为Redis在执行Lua脚本时是原子性的. 达到了事务的效果
写入Lua脚步执行的代码:
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end;
引入看门狗(watch dog)
在设置Key的过期时间时, 需要考虑到: 时间不能过短,防止服务器的操作未执行完就进行释放锁. 也不能设置时间过长,导致长时间占用锁,使其他服务器无法进行操作.
而引入看门狗机制可以有效解决问题: 在服务器中设置一个专门监视的线程, 如果快要到过期时间且服务器的操作还未结束, 那么将继续延长过期时间, 直到操作完成.
使用ReadLock算法
在使用Redis实现分布式锁时, 需要注意Redis节点的宕机, 这是致命性的. Redis也给出了对应办法
RedLock算法:
在Redis中使用"哨兵"机制, 实现主节点宕机,从节点晋升. 我们引入多 组 Redis 节点. 其中每⼀组 Redis 节点都包含⼀个主节点和若干从节点. 并且组和组之间存 储的数据都是⼀致的, 相互之间是 "备份" 关系(而并非是数据集合的⼀部分, 这点有别于 Redis cluster). 加锁的时候, 按照⼀定的顺序, 写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". 比如 50ms. 即如果 setnx 操作超过了 50ms 还没有成功, 就视为加锁失败.如果给某个节点加锁失败, 就⽴即再尝试下⼀个节点. 当加锁成功的节点数超过总节点数的⼀半, 视为加锁成功.

如上图, ⼀共五个节点, 三个加锁成功, 两个失败, 此时视为加锁成功. 这样的话, 即使有某些节点挂了, 也不影响锁的正确性. 而释放锁时同样需要对所有Redis服务器进行释放锁操作