技术分享 | Redis 之分布式锁

作者: admin 分类: 公众号存档            2 次浏览 发布时间: 2023-01-03 22:37

【转载声明】本文为转载文章,原作者「贲绍华」,版权归原作者所有。 原文链接:https://mp.weixin.qq.com/s/TqaNTOXt5Gf00u9QZy8dSA

公众号:杨建荣的学习笔记 · 作者:贲绍华 · 发布:2023-01-03 22:37:00 · 原文链接

作者:贲绍华

爱可生研发中心工程师,负责项目的需求与维护工作。其他身份:柯基铲屎官。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


引言:之前的一篇《缓存穿透 – Redis Module之布隆过滤器》中,介绍了布隆过滤器的使用,本篇主要通过实际业务场景来讲述 Redis 中关于分布式锁与 Red lock 的相关内容。

一、什么是分布式锁

分布式锁指的就是分布式系统下使用的锁(说了好像等于没说),在分布式系统中,常常需要协调组件间的动作。

如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,这个时候,便需要使用到分布式锁。

二、场景案例

举个例子,假设ATM机A、ATM机B同时对同一账户入账,它们是两个独立且相同的业务系统。由于余额是要在现有金额上进行增加的,中间的前后操作会出现时差。

则A或B增加余额时都需要先获取互斥锁,锁住需要操作的资源,增加余额后释放锁。

三、使用Redis实现分布式锁

3.1 带TTL的key

在 Redis 中创建一个 key ,这个 key 有一个失效时间(TTL),以保证锁最终会被自动释放掉(这个对应上边脑图的活性A)

即:

get->不存在,获取成功->set->ttl->del

当客户端释放资源(解锁)时,会删除掉这个 key 。

3.2 setNX

使用3.1的方式存在一个问题,那就是每次都得先 get 一下看看这个 key 是否存在,插入之后还需要再设置 TTL 。

非常的繁琐且 get->set->ttl 操作并不是原子性的,需要额外处理类似 get 不存在但 set 又存在、锁被其他客户端释放掉的场景。

Redis 提供了 setNX 命令(如果不存在就插入,并设置 key 的超时时间。如果存在则什么也不做)

使用命令:

set key value px milliseconds nx

3.3 setNX + Lua

在3.2中还存在一个问题,例如:

  1. ATM-A 获取锁成功

  2. ATM-A 操作中,持续阻塞

  3. 设置 key 过期了,锁被自动释放

  4. ATM-B 获取到锁

  5. ATM-A 操作此时恢复处理,操作完成开始释放锁

  6. ATM-B 持有的锁被 ATM-A 释放掉了

为了解决这个问题,释放锁之前需要对比 value 值(需要保证值的唯一性),释放锁的时候只有 key 存在并且存储的值和当前客户端指定的值一样才能删除成功。

同样的,get->delete 操作并不是原子性的,需要使用 lua 脚本来同时完成:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

3.4 锁的续期

锁的过期时间是插入 key 时直接设置的一个大概时间区间,实际业务执行过程中不能精确预估具体的执行的时间。会出现客户端正在处理时 key TTL 过期导致的被提前释放问题。

解决方式:可以让获得锁的客户端开启一个守护进程,用于给快要过期的key增加超时时间。当业务执行完成时,再主动关闭该守护进程。

3.5 高可用问题

单节点 Redis 实例会导致获取锁失败,业务直接停摆。但想通过增加 slave 节点解决这个问题其实是行不通的,因为 Redis 的主从同步通常是异步的。

会出现以下的问题:

  1. ATM-A 从 Redis master 节点获取到锁

  2. 在 master 将锁同步到 slave 之前,master 宕机

  3. slave 节点被晋级为 master 节点

  4. ATM-B 获取到了锁,ATM-A 业务还在进行中,导致安全失效

四、Redis Module – RedLock

RedLock 正是为了防止单点故障而设计的基于 Redis 的分布式锁实现。

它是由N(大于等于3的奇数个)个 Redis master 节点组成的,节点与节点之间不使用复制或任何隐式协调系统。

当客户端需要获取锁时,会尝试顺序从N个实例中获取,在所有实例中使用相同的key与value。

  • 官方文档:https://redis.io/docs/manual/patterns/distributed-locks

  • 使用方式:https://github.com/wujunwei/redlock/blob/master/README.md

4.1 获取锁:

当 N / 2 + 1 个节点获取到锁时则成功得到锁(因为如果小于一半判断为成功的话,有可能出现多个客户端都成功获取锁的情况, 从而使锁失效)

4.2 释放锁:

客户端应该向所有 Redis 节点发起释放锁的操作。即使当时向某个节点获取锁没有成功,在释放锁的时候也不应该漏掉这个节点

4.3 延迟重启:

一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间(lock validity time)。

这样的话,这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

4.4 优点:

  • 有效防止单点故障

4.5 缺点:

  • 需要维护多台 Redis Master ,使用起来相当笨重

  • RedLock 算法对时钟依赖性强,若集群中的某个节点发生时钟异常问题,可能会因此而引发锁安全性问题

  • 如果有节点发生崩溃重启,还是会对锁的安全性有影响的。具体的影响程度跟 Redis 对数据的持久化程度有关

admin

杨建荣,《Oracle DBA工作笔记》《MySQL DBA工作笔记》作者,dbaplus社群发起人之一,腾讯云TVP,现任竞技世界系统部经理,拥有十多年数据库开发和运维经验,目前专注于开源技术、运维自动化和性能调优

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注