分布式锁介绍
什么是分布式锁?
是能在集群情况下多个节点之间保证同一时间的数据一致性和安全性的方式
分布式锁的基本概念
想要清晰的认识分布式锁,就必须认识以下的几种概念。
- 锁(Lock):一种同步机制,确保需要访问的资源一致性和安全性的判断依据。
- 共享资源(Shared Resource):指的是多个节点会访问的数据。
- 锁状态(Lock State):锁状态用于判断共享资源是否被别人在使用。
- 竞态条件(Race Condition):指的是“进门上锁”的条件,因为在多个请求同时发起的时候,可能有些请求在某种条件下是能够同时进行的,这个条件就是竞态条件。
- 数据不一致(Data Inconsistency):指的是多个节点对同一数据进行修改,导致数据不一致。
- 死锁(Deadlock):指的是“进门上锁”后没有及时的“出门”,导致后面排队想要进门的被一直阻塞,资源一直没被释放。
分布式锁的实现方式
1. 通过数据库实现分布式锁
原理很简单,建一张分布式锁的表且需要有一个唯一的字段。请求的时候先尝试插入锁记录并生成当次操作的唯一约束,如果成功则获取到锁,失败则是有请求先行一步了,当操作执行完成后需要主动去释放锁,也就是删除锁记录,在以上基础上也可以为锁加上过期时间防止死锁的情况。
2. 通过缓存实现分布式锁
基本上都是使用中间件完成分布式锁,且原理也和数据的实现方式很接近,但更加的高效减轻了数据库的压力。例如Redis就是一个非常适合做分布式锁的中间件,Redis的键支持自动过期,还有SETNX命令来保证获取锁的原子性,效率也是相当不错。
总结优缺点
- 缓存:性能优秀,不易造成系统瓶颈,但可能会因为缓存崩溃导致出问题以及就是增加了系统的复杂性。
- 数据库:性能较差,对数据IO的开销大,不适用于高并发环境,但是接入相对简单不需要考虑。
Redis实现分布式锁
SETNX命令
SETNX是SET if Not eXists的缩写,也就是设置的同时判断键是不是不存在,不存在则成功返回1,存在则失败返回0,通过这样的机制,我们可以很好的控制分布式锁的一致性。
EXPIRE过期时间
同样我们在设置锁后呢也需要释放锁,但是如果释放操作因为某种原因没有被执行,那么我们也同样需要使用过期这样的操作来完成锁过期的操作,避免死锁。
可重入
分布式锁需要考虑的一个问题是可重入性,即同一个线程是否可以多次获取同一把锁而不被阻塞。通常情况下,分布式锁是不具备可重入性的,因为每次获取锁都会生成一个新的标识(如
requestId),不会与之前的标识相同。
为了解决可重入性的问题,我们可以引入一个计数器,记录某个线程获取锁的次数。当线程再次尝试获取锁时,只有计数器为0时才会真正获取锁,否则只会增加计数器。释放锁时,计数器减少,直到为0才真正释放锁。
需要注意的是,为了保证分布式锁的线程安全性,我们应该使用线程本地变量来存储requestId,以防止不同线程之间的干扰。
锁粒度
在分布式锁的应用中,选择合适的锁粒度是非常重要的。锁粒度的选择会直接影响系统的性能和并发能力。 一般而言,锁粒度可以分为粗粒度锁和细粒度锁。
- 粗粒度锁:将较大范围的代码块加锁,可能导致并发性降低,但减少了锁的开销。适用于对数据一致性要求不高,但对并发性能要求较低的场景。
- 细粒度锁:将较小范围的代码块加锁,提高了并发性能,但可能增加了锁的开销。适用于对数据一致性要求高,但对并发性能要求较高的场景。
在选择锁粒度时,需要根据具体业务场景和性能需求进行权衡,避免过度加锁或锁不足的情况。
RedLock机制
在单机模式下Redis锁可能会导致不可用,结果部分业务都瘫痪了,那么可以使用RedLock去提高锁的可靠性,RedLock有个(N/2)+1的算法,也就是必须大于一半的节点可用锁一致才能使用。
本人有时间还会继续完善内容。