目录
一、什么是分布式锁
在一个分布式系统中,也会涉及到多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于"线程安全"的问题。
二、分布式锁的基础实现
思路非常简单,本质上就是通过一个键值对来标识锁的状态。
举个例子:考虑买票的场景,现在车站提供了若干个车次,每个车次的票数都是固定的,现在存在多个服务器节点,都可能需要处理这个买票的逻辑:先查询指定车次的余票,如果余票>0,则设置余票-=1.
很显然上述场景是存在“线程安全”问题的,需要使用锁来控制,否则会出现超卖现象。
此时我们可以在上述架构中引入Redis作为分布式锁的管理器。
此时,如果买票服务器1尝试买票就需要先访问Redis服务器,在Redis设置一个键值对,比如key就设置为车次,value随便设置个值。如果这个操作设置成功,则说明当前没有节点对该车次加锁,就可以进行数据库的读写操作,操作完成之后,再把Redis上刚才这个键值对删除掉。
如果在买票服务器1操作数据库的过程中,买票服务器2也想买票,也会尝试给Redis上写一个键值对,key同样是车次,但此时发现Redis上已经存在这个key了,则认为有其他服务器正在持有锁,此时买票服务器2就需要等待或者暂时放弃。
三、引入过期时间
在上述过程中,服务器1加锁之后,开始处理买票逻辑时如果意外宕机了, 就会导致解锁操作(删除key的过程)无法执行,就可能会引起其他服务器始终无法获取到锁的情况。
为了解决这个问题,我们可以在设置key的同时引入过期时间,即这个锁最多持有多久,就应该被释放。(可以使用 set ex nx 的方式在设置锁的同时把过期时间设置进去,这里的过期时间只能用一个命令的方式设置)
四、引入校验id
对于Redis的加锁键值对,其他的节点也是可以删除的。比如服务器1写入一个键值对,服务器2完全可以将这个键值对给删除掉的。
为了解决上述问题我们可以引入一个校验id
比如可以把设置的键值对的值,不再是简单的设为一个 1 ,而是设成服务器的编号,形如 “001”:“服务器1”.
这样就可以在删除key(解锁)的时候,先校验当前删除 key 的服务器是否是当初加锁的那个服务器,如果是,才能删除。
伪代码表示如下:
String key = [要加锁的资源 id];
String serverId = [服务器的编号];
// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");
// 执⾏各种业务逻辑, ⽐如修改数据库数据.
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {
redis.del(key);
}
五、引入 watch dog(看门狗)
六、引入 Redlock 算法

如果给某个节点加锁失败就立即尝试下一个节点,当加锁成功的节点超过了总节点数的一半,才视为加锁成功,这样即便有节点挂了,也不影响锁的正确性。