文章目录
目录
文章目录
1. 什么是分布式锁
2. 分布式锁的基本实现
3. 引入过期时间
4. 引入校验Id
5. 引入 watch dog(看门狗)
6. 引入redlock算法
工作原理
Redlock的优点:
总结
1. 什么是分布式锁
在一个分布式系统中,也可能会出现多个节点访问一个共享资源的情况,此时就需要通过互斥锁来进行控制。
而像是Java中的synchronized 或者 C++ 的 std::mutex,这样的锁只能在当前线程生效,无法作用于分布式系统。
本质上就是使=用一个公共的服务器, 来记录加锁状态。这个公共的服务器可以是 Redis, 也可以是其他组件(比如 MySQL 或者 ZooKeeper 等), 还可以 是我们自己写的一个服务。
2. 分布式锁的基本实现
思路非常简单. 本质上就是通过一个键值对来标识锁的状态。
引入一个场景(如下图)来帮助理解分布式锁:在高并发的购票系统中,多个用户可能同时请求购买同一张票。为了避免超卖(即同一张票被多次售出),需要对购票操作进行控制。
上面这张图所示显然是没有任何安全保障的,我们使用Redis来存储键值对来作为分布式锁来使用。
此时, 如果 买票服务器1 尝试买票, 就需要先访问 Redis, 在 Redis 上设置⼀个键值对。 比如 key 就是车次。如果这个操作设置成功, 就视为当前没有节点对该 001 车次加锁, 就可以进行数据库的读写操作。 操作完成之后, 再把 Redis 上刚才的这个键值对给删除掉。
如果在买票服务器1 操作数据库的过程中, 买票服务器2 也想买票, 也会尝试给 Redis 上写一个键值对, key 同样是车次。 但是此时设置的时候发现该车次的 key 已经存在了, 则认为已经有其他服务器正在持有锁, 此时 服务器2 就需要等待或者暂时放弃。
Redis 中提供了 setnx 操作, 正好适合这个场景。 即: key 不存在就设置, 存在则直接失败。
3. 引入过期时间
如果正在买票的过程中, Redis服务器直接挂了, 就会导致解锁操作无法进行,可能会导致其他服务器一直无法获取到锁。
为了解决这个问题,可以在设置Key的时候引入过期时间,即这把锁最多持有多久就会被释放。
可以使用 set ex nx 的方式, 在设置锁的同时把过期时间设置进去。
4. 引入校验Id
对于 Redis 中写入的加锁键值对, 其他的节点也是可以删除的。
为了解决这个问题,我们可以引入一个校验Id, 用来判断加锁和解锁是不是一个服务器进行的,如果不是就无法进行解锁。比如设置key的时候将Key的值设置为服务器Id。
String key = [要加锁的资源 id];
String serverId = [服务器的编号];
// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");
// 执⾏各种业务逻辑, ⽐如修改数据库数据.
doSomeThing();
// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {
redis.del(key);
}
5. 引入 watch dog(看门狗)
上述方案仍然存在一个重要问题。当我们设置了 key 过期时间之后 (比如 10s), 仍然存在一定的可能性, 当任务还没执行完成, key 就先过期了。 这就导致锁提前失效。
所谓 watch dog, 本质上是加锁的服务器上的一个单独的线程, 通过这个线程来对锁过期时间进行 "续 约"
举个具体的例子:
初始情况下设置过期时间为 10s. 同时设定看门狗线程每隔 3s 检测⼀次。那么当 3s 时间到的时候, 看门狗就会判定当前任务是否完成。
- 如果任务已经完成, 则直接通过 lua 脚本的方式, 释放锁(删除 key)。
- 如果任务未完成, 则把过期时间重写设置为 10s. (即 "续约")。
Redis的Lua脚本是一种在Redis服务器上执行的脚本,允许用户通过Lua语言编写自定义命令,以实现复杂的操作。使用Lua脚本可以提高性能,因为它们在Redis服务器端执行,减少了客户端与服务器之间的网络往返。
原子性:Lua脚本在Redis中是原子执行的,这意味着在脚本执行期间,其他命令不会干扰它。这对于需要保证数据一致性的操作非常重要。
性能:通过在服务器端执行脚本,可以减少网络延迟和数据传输,提高性能。
灵活性:用户可以编写复杂的逻辑,结合多个Redis命令,处理数据并返回结果。
6. 引入redlock算法
实践中的 Redis 一般是以集群的方式部署的 (至少是主从的形式, 而不是单机)。那么就可能出现以下极端情况:
服务器1 向 master 节点进行加锁操作. 这个写入 key 的过程刚刚完成, master 挂了; slave 节 点升级成了新的 master 节点. 但是由于刚才写入的这个 key 尚未来得及同步给 slave 呢, 此时 就相当于 服务器1 的加锁操作形同虚设了, 服务器2 仍然可以进行加锁 (即给新的 master 写 入 key。 因为新的 master 不包含刚才的 key。(倒霉 O(∩_∩)O哈哈~)
Redlock是一种分布式锁算法,旨在解决在分布式系统中管理锁的挑战。它是由Redis的创始人Antirez提出的,主要用于确保在多个Redis实例之间的互斥访问。
工作原理
获取锁:
- 客户端向多个Redis实例请求获取锁。
- 客户端在每个实例上设置一个唯一的锁键,并设置一个过期时间(TTL)。
- 客户端需要在大多数实例上成功设置锁(例如,如果有5个实例,则需要在3个实例上成功设置)。
锁的有效性:
- 锁的有效性由设置的过期时间决定。如果客户端在持有锁期间未能完成任务,锁会在过期后自动释放,避免死锁。
释放锁:
- 客户端完成任务后,可以释放锁。释放锁时,客户端需要确保只释放自己持有的锁,以避免其他客户端的锁被错误释放。
Redlock的优点:
- 高可用性:通过在多个Redis实例上获取锁,Redlock可以容忍部分实例的故障。
- 简单易用:Redlock的实现相对简单,易于集成到现有的Redis应用中。
- 避免死锁:通过设置过期时间,Redlock可以避免因客户端崩溃或网络问题导致的死锁。
工作原理图解:
有5个实例,则需要在3个实例上成功设置: 高可用,可以容忍部分实例的故障。
设置一个过期时间(TTL):避免因客户端崩溃或网络问题导致的死锁。
总结
以上就是这篇博客的主要内容了,大家多多理解,下一篇博客见!