Redis实现分布式锁的实例讲解
作者:青灯夜游
在一个分布式系统中,会遇到一些需要对多个节点共享的资源加锁的情况,这个时候需要用到分布式锁。分布式锁通常保存在一个共享的存储系统中,可以被多个节点共享和访问。
锁的本质
简单来讲,锁可以用一个变量来表示。比如,在一个单机多线程的程序来说,某个资源的锁用一个 bit 的数据就可以表示。即 0 表示没有资源可以访问,1 表示资源的锁已被别的线程获取,不能访问。
获取和释放特定资源的锁,本质上就是为获取和修改这个变量的值。如果值是 0 则将其修改为 1,就完成了获取的过程,如果访问到的值不是 0,则获取锁失败;如果之前获取了锁,将表示锁的变量的值修改为 0 的操作,其实就是释放锁的操作。
在一个分布式场景中,实现锁的方式也是一样的,只不过这个表示资源锁的变量,需要保存在一个共享的存储系统中。这个共享的存储系统,可以是 Redis,也可以是其他的任何可以提供数据存储的系统。
基于 Redis 的分布式锁实现
第一步:初步实现功能
对于将 Redis 作为这个共享存储系统的情况来说,代表某个资源的锁的变量,就是 Redis 中的一个键值对。假如,需要添加分布式锁的资源叫 resource_a,我们可以将 Redis 中 resource_a 的锁变量的 key 叫做 lock_a。
例如,节点一需要获取锁,它会访问 Redis 中 lock_a 的值,假设获取到的值为 0,则节点一将这个值设置为 1 后,就完成了加锁操作。此时,节点二也需要获取 resource_a 的锁,它去访问 Redis 中 lock_a 的值,发现值是 1,说明锁已经被别的节点获取,并且还没有释放,因此,节点二对资源 resource_a 加锁失败。
当节点一需要释放锁的时候,只需要将 Redis 中的 lock_a 的值设置为 0 就完成了锁的释放,之后,其他的节点就可以再次获取资源的锁。
第二步:加锁操作原子化
以上的描述中,加锁的并不是一个单一的操作,而是包含了多个步骤:读取锁变量、判断变量的值、修改锁变量。这三个操作需要原子化。
在 Redis 中,有一个 SETNX 命令,用于设置键值对的值,与 SET 命令不同,它在是先事会判断键值对是否存在,只有当指定的 KEY 不存在的时候,才会执行值的设定,否则什么都不执行。SETNX 就是「SET if Not eXist」的意思。它的用法与 SET 相同:
SETNX lock_a 1
这样,当需要获取锁的时候,使用 SETNX 命令为 lock_a 设置一个值,如果设置成功则获取到了锁,如果失败则没有获取到锁;当需要释放锁的时候,使用 DEL 操作删除键值对即可。
这样就实现了获取和释放锁的原子化操作。
第三步:防止加锁后不释放
接下来早考虑一个问题,如果节点一获取到锁之后,由于程序异常等原因,导致一直么有释放锁,此时,锁会一直被它持有,无法释放,其他节点也无法访问资源。
为了避免这种情况的发生,我们必须给锁变量设置过期时间,当锁变量过期后,就可以重新请求加锁,这样就可以避免这个问题。
SETNX 的命令并没有设置过期时间的选项,所幸的是,Redis 为 SET 命令提供了模拟 SETNX 的 NX 选项,我们可以这样设置过期时间:
SET lock_a 1 NX PX 10000
以上命令代表,如果 lock_a 不存在,则将它的值设置为 1,并且在 10 秒后过期。
第四步:谁加锁谁释放
最后一个问题是,如果节点一获取了锁,而由于某种原因,节点二执行了 DEL 操作,那么,其他节点又可以获取锁了。
为了解决这个问题,我们可以修改一下锁变量保存的内容。在前面的逻辑中,我们申请锁的时候,是去判断锁变量是否存在,而与其中保存的值关系不大,因此,我们可以把这个值利用起来。
在加锁的时候,如果把值保存为每个节点唯一的标识,那么,在释放锁执行 DEL 之前再对这个值进行判断,那么,就可以先判断锁是否是当前节点加上的,是的话再进行释放,这样就实现了「谁加锁谁是放」。
这一部分,没有一个单一的指令可以完成读取锁变量、判断、删除的操作,因此,可以使用 Lua 脚本实现。在脚本中获取到当前锁变量的值,与给定的节点标识进行比对,符合的话才进行删除操作,否则不操作。
在释放锁时,执行 Lua 脚本即可。
第五步:实现高可用
完善了功能之后,最后再来实现高可用。如果我们使用单一的 Redis 作为分布式锁的共享存储系统,那么,如果这个 Redis 不可用了,那涉及到分布式锁的部分都不可用了,这样是很脆锁的,这是高可用非常有必要的原因。
此时,需要搬出 Redis 的作者 Antirez 提出的分布式锁算法 Redlock。简而言之,是让锁的申请者,想多个独立的 Redis 实例请求加锁,如果能在半数以上的 Redis 完成枷锁操作,那么就成功的获取了锁,反之获取失败。
在释放锁的操作时,同样只要在超过半数的实例上执行成功删除锁变量的 Lua 脚本,即可视为成功。
到此这篇关于Redis实现分布式锁的实例讲解的文章就介绍到这了,更多相关Redis如何实现分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!