什么是分布式锁?
在一个分布式系统中,会涉及到多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于线程安全的问题。
但java中的synchronized或者c++的std::mutex这样的锁都只能在当前进程中生效,在分布式这种多个进程多个主机的场景下就无能为力了,所以此时就需要用到分布式锁~
本质上就是使用一个公共的服务器,来记录加锁状态
分布式锁的基础实现
本质上是通过一个键值对来标识锁的状态:
引入过期时间
上述方案,当服务器1加锁之后,开始处理买票的过程中,如果服务器1意外宕机了,没法进行解锁操作,就会导致其他服务器始终无法获取到锁的情况。
所以为了解决这个问题,可以在设置key的时候引入过期时间,这样这个锁持有一定时间后就会被释放。
注意:严格来讲,redis是不具备原子性的,引入过期时间的操作最好使用一个命令,如果分开设置锁跟过期时间,即便使用事务也不能保证这两个操作都一定成功。
引入校验id
对于redis中写入的加锁键值对,其他节点是可以删除的,比如服务器1写入一个01:1这样的键值对,服务器2是可以把这个键值对删除掉的,虽然服务器2不会恶意删除,但不能保证因为一些bug导致服务器2把锁误删的情况。
为了解决上述问题,我们可以引入一个校验id:
比如可以把设置的键值对的值,不单单设置成1,而是设成服务器的编号,如“001”:“服务器”.这样就可以在删除key的时候,先校验当前删除的key的服务器是不是当初加锁的服务器,是才删除,不是就不能删除。
引入watch dog
虽然我们设置了key过期时间之后,但仍然存在当任务没执行完,key就先过期了,导致锁提前失效。
这个时候就需要一个单独的线程(watch dog),通过这个线程对锁过期时间进行续约:
加入过期时间为10s,同时设定看门狗线程每隔3s检测一次,那么3s之后,看门狗就会判断当前任务是否完成,如果完成,则直接通过lua脚本的方式释放锁;如果没完成,则把过期时间重写设置为10s。
引入Redlock算法
实践中其实还会出现一种很大怨种的情况:
服务器1 向 master 节点进⾏加锁操作. 这个写⼊ key 的过程刚刚完成, master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写⼊的这个 key 尚未来得及同步给 slave 呢, 此时 就相当于 服务器1 的加锁操作形同虚设了, 服务器2 仍然可以进⾏加锁 (即给新的 master 写⼊ key. 因为新的 master 不包含刚才的 key).