分布式锁
分布式锁
锁存在的意义是为了保证在多CPU,多个线程的环境中,某一个时间点上,只能由一个线程进入临界区代码,从而保证临界区操作数据的一致性。
进程内的锁,是操作系统直接提供的,对于同一台机器上的多进程,可以直接通过操作系统的锁来实现,只不过是协调了多个进程,需要将锁放在所有进程都可以访问的共享内存中,所有进程通过共享内存中的 锁来进行加锁和解锁。
但是分布式是在不同机器上,通过操作系统的锁已经无法实现。
怎么实现分布式锁
实现分布式锁,需要满足以下几个特性:
互斥:保证不同节点、不同线程的互斥访问。
超时机制:即超时设置,防止死锁。因为锁服务和请求锁的服务分散在不同的机器上面,它们之间是通过网络来通信 的,所以我们需要用超时机制,来避免获得锁的节点故障或者网络异常,导致它持有的锁不能 归还,出现死锁的情况。
同时还要确保留有线程不断延长锁的时间,防止事务还没处理完,而时间到了,导致释放了锁。
完备的锁接口:比如说lock接口和trylock接口等。
可重入性:即一个节点的一个线程已经获取了锁,那么该节点持有锁的这个线程 可以再次成功获取锁。我们在加锁时,记录好当前获取锁的节点+线程组合的唯一标识,后续如果标识相同,直接返回加锁成功即可,否则按照正常流程处理。
公平性:即对于 Lock 接口获取锁失败被阻塞等待的加锁请求,在锁被释放后,如果按 先来后到的顺序,将锁颁发给等待时间最长的一个加锁请求,那么就是公平锁,否则就是非公 平锁。
分布式锁的挑战
分布式锁面临的挑战有:正确性、高可用和高性能。
首先考虑进程内的锁,如果一个线程持有锁,只要它不释放,就只有它能操作临界区的资源。同时,因为进程内锁的场景中,不会出现部分失败的情况,所以它崩溃无法释放锁时,会导致整个进程崩溃,不会出现死锁(这里不包括业务逻辑出错而导致的死锁)。
另一个方面,进程内锁的解锁操作是进程内部的函数调用,这个过程是同步的。只要发起加锁操作,就会成功(这个操作会成功,但是加锁不一定),解锁操作同样。
当存在多个进程时,加锁操作仍然是函数调用,和上边一样,但是存在一个进程获取锁后崩溃,导致无法释放锁,出现死锁。但是可以通过操作系统来判断一个进程是否存活,并查看它是否获取锁,从而解决问题。
但是在分布式场景下,发起获取锁的操作需要利用不可靠的网络,意味着发起获取锁的这个操作就有可能失败(这里称为获取锁的时延),而且获取锁后,该进程与服务器通信出现了问题,导致无法释放锁,造成死锁。这就需要设置一个超时时间,如果获取锁的进程超时,就自动释放锁。
在获取锁的时延上,如果采用超时加心跳,可能会导致服务端给客户端分发了锁,但是由于响应超时,导致客户端以为自己没有获取到锁。
为了解决这个问题,我们可以在生成锁的时候一并生成一个全局唯一且递增的版本号,当操作共享数据时,会检查版本号,如果出现版本号倒退的现象,则说明出了问题,拒绝该次请求即可。
分布式锁的权衡
我们无法保证分布式锁100%的有效,而且正确性要求越高,它的性能会越低。一般来说,会在成本可接受的范围内,提供性能最好的分布式锁服务,如果性能不佳,则需要告知。
参考
《深入浅出分布式技术原理》