简介
为什么需要分布式锁
一个进程中当多线程的去竞争某一资源的时候,我们通常会用一把锁来保证只有一个线程获取到资源,在单体项目也就是部署在一台服务器上的时候,加锁只需要加synchronize关键字或ReentrantLock锁就行
那么在微服务中加synchronize关键字或ReentrantLock锁不管用了,因为每个服务都用自己的实例,锁的对象不是同一个对象。
那么这时候为了防止多个服务器同时访问一个共享变量,就需要分布式锁,例如redis分布式锁
示例
保证全局唯一性:
示例: 一个系统需要为每个新订单生成一个唯一的订单号。在分布式环境下,如果多个节点同时生成订单号,可能会导致生成重复的订单号,这会影响业务的正确性。
防止重复消费消息
示例: 在某个任务处理系统中,多个应用实例订阅同一个消息队列。如果没有锁机制,当一个任务被多个实例同时处理时,会导致任务的重复执行,影响系统性能和一致性。
总而言之,多个实例操作一个共享变量,也就是说多个请求通过不同的服务器同时访问接口中的共享变量导致最终数据不一致就需要分布式锁来控制同一时间只能有一个实例执行操作
redis实现分布式锁
redis的操作具有原子性(在 Redis 中,所有的命令执行是串行的,不会发生多个命令同时执行的情况,因此在执行某个命令时不会受到其他命令的干扰。),多个实例或者多个客户端访问redis设置同一个值的时候能保证同一时刻只有一个实例或者一个客户端能写入成功
下面依次从非安全-》安全锁的演变
简单锁(SETNX 锁)
使用 Redis 的 SETNX(Set if Not Exists)命令来实现简单的互斥锁。
实现原理:SETNX 命令会在键不存在时设置值,返回 1;如果键已经存在,则返回 0。通过这个特性,多个客户端尝试设置相同的键时,只有第一个设置成功的客户端能获得锁。
SETNX lock_key "unique_lock_identifier"
lock_key:表示锁的名字,通常是共享资源的标识符。
unique_lock_identifier:是一个唯一标识符,通常可以是当前客户端的 ID 或 UUID,用来标记当前锁的拥有者。
使用场景:适用于一些不需要过期时间、仅仅进行简单加锁和解锁的场景。
问题:
SETNX是可以加锁,但是需要手动释放锁,如果服务加锁后还没来得及释放锁就宕机了,那么这个锁就会一直存在redis中,其他服务就一直获取不到锁了
因此出现了SET NX PX
Redis 自带的锁(SET 命令的 NX 和 PX 选项)
Redis 3.0 引入了 SET 命令,可以通过 NX 和 PX 选项来实现更加简洁的分布式锁。
实现原理:通过 SET 命令的 NX(仅当键不存在时设置)和 PX(设置超时时间)选项,可以在一次命令中实现加锁和超时设置。
SET lock_key "client_id" NX EX 30
使用场景:适用于需要简单分布式锁的场景。
问题:虽然SET NX PX可以加锁并设置过期事件,但是依旧会出现问题,如果咱们设置过期时间为10s,但是业务执行时间是20s,那么在业务还没执行完成的时候锁就释放了,那么其他服务就可以获取到锁来执行这个方法中的业务,这样就会出现锁过期和释放其他服务锁这两个问题
Redisson
对于锁过期问题,我们是也可以手动处理的,例如我们可以写一个定时任务,来询问这个方法执行完没有,没有就为这个锁续时间,这样比较麻烦
为了克服 SET NX PX实现分布式锁的缺点,Redisson(一个基于 Redis 的客户端)提供了一种更为强大和灵活的分布式锁实现,支持更复杂的锁机制和更高的可靠性。
Redisson其实就是内置了一个守护线程(看门狗),他会自动检查方法是否结束,以及锁释是否过期,并自动续时这样一个功能
特点如下:
自动解锁
Redisson 提供了自动解锁的功能,避免了死锁的问题。如果持有锁的客户端在持有锁时发生崩溃或超时,Redisson 会自动释放锁。
锁的过期时间
Redisson 会为每个锁设置一个合理的过期时间(TTL),以防止死锁。通过这种方式,即使客户端在没有释放锁的情况下崩溃,其他客户端仍然可以在超时后获取锁。
可重入锁
Redisson 提供了可重入锁(ReentrantLock)的实现,即同一个客户端可以多次获得锁,并且在每次获得锁时计数,只有在锁计数归零时才会释放锁。
公平锁
Redisson 支持公平锁,它确保锁的获取顺序是按照请求锁的顺序来分配的。公平锁避免了某些客户端可能一直得不到锁的问题。
锁的监控与可扩展性
Redisson 提供了对锁的监控,可以检测锁的持有者和锁的状态,允许开发者更好地控制和优化锁的使用。
问题:虽然reddisson能很好的解决锁过期问题,但是依旧有缺陷,当我们redis为了高可用,分布式需要部署集群模式,这时候就有主节点和从节点,而redisson存锁的位置其实就是主节点,如果当主节点挂掉了,从节点还没同步主节点数据的时候,其他服务来加锁了,这时候第一个服务器也加了锁,导致2个服务都开始执行加锁的业务代码,这时候就是不安全的。
Red lock
Redlock 是由 Redis 创始人 Salvatore Sanfilippo(Antirez)提出的一种分布式锁算法,旨在解决 Redis 分布式锁的可靠性问题,尤其是在多个 Redis 实例之间提供一致性和容错性。简单来说,Redlock 通过多个 Redis 实例来增强分布式锁的可靠性,从而避免了单点故障的问题。
Redlock 的基本思路是通过多个独立的 Redis 实例来实现分布式锁,减少单点故障的风险,并且通过一定的机制确保锁的可靠性。具体步骤如下:
1.准备多个 Redis 实例:
Redlock 要求至少有 5 个 Redis 实例(奇数个实例)来保证锁的一致性和可用性。
这些 Redis 实例最好分布在不同的机器或数据中心中,以避免单点故障。
2.客户端获取锁:
客户端向多个 Redis 实例请求获取锁。它依次向每个 Redis 实例发送加锁请求,直到获取到一定数量的锁(比如多数节点上获取锁)。
获取锁的过程中,会设置一个合理的过期时间(TTL),确保锁不会被永久占用。
锁的唯一性是通过在每个 Redis 实例上设置一个唯一的标识符(通常是 UUID)来实现的。
3.确保锁的有效性:
客户端在所有 Redis 实例中获取锁后,会检查是否有至少大多数实例上成功加锁。如果是,则认为获取锁成功。
如果无法在大多数 Redis 实例上获得锁,则认为加锁失败,并释放已经获得的锁。
4.释放锁:
客户端释放锁时,向每个 Redis 实例发送解锁请求,确保锁能够被正确释放。
Redlock 的优点
容错性:Redlock 的设计允许部分 Redis 实例不可用时仍然能够工作,保证了锁的高可用性。
保证一致性:通过设置多个 Redis 实例,Redlock 能够减少单点故障带来的影响,保证分布式锁的一致性。
Redlock 的缺点和问题
尽管 Redlock 相比传统单节点 Redis 分布式锁具有更高的容错性和一致性,但它也有一些缺点:
时钟偏差问题:Redlock 的一个关键问题是时钟同步。Redis 实例的时钟如果不同步,可能导致锁的过期时间不一致,进而导致锁争用问题。
延迟问题:Redlock 要求访问多个 Redis 实例,这可能会引入额外的网络延迟,尤其在跨数据中心部署时,延迟可能影响锁的效率。
实现复杂性:相比单个 Redis 实例的分布式锁,Redlock 的实现和维护更加复杂,需要处理多个 Redis 实例之间的协同和故障恢复。
总结
因此一般项目中不建议使用RedLock,因占用太多服务器了!一般还是使用Redisson,因此主从不一致情况很少,而且切换都是很快的,在此期间如果高并发确实用到这个锁了也可以使用兜底方案,
例如实现 Redis 主从切换监控和通知机制,可以利用 Redis Sentinel 或 Redis Cluster 提供的事件通知机制。这些机制能够检测主节点宕机、切换以及其他状态变化,并通过发布/订阅机制(Pub/Sub)将事件通知给客户端应用。
实现步骤
- 依赖添加
- 连接到 Redis Sentinel
- 订阅 Sentinel 事件
- 处理主节点切换事件