数据库同步工具
sqlserver,Mysql数据同步软件

Redis数据库中实现分布式锁的方法

购买事宜请联系QQ:1793040

分布式锁定是在许多环境中非常有用的原语。这是不同进程相互共享资源的唯一方法。有许多开发库和博客描述了如何使用Redis来实现DLM(分布式锁管理器),但是每个开发库都使用不同的方法,与更复杂的设计和实现相比,许多库使用一些简单而可靠的方法来实现。

本文试图提供一种更标准的算法来使用Redis实现分布式锁。我们提出了一种称为Relock的算法,该算法实现了我们认为比香草单实例方法更安全的DLM(分布式锁管理)。我们希望社区能够对其进行分析并提供反馈,以实现更复杂或替代性的设计。

实施

在讨论特定算法之前,这里有一些特定的实现方式可供参考。

    \ Redlock-rb(Ruby实现)。 Redlock-php(PHP实现)。 Redsync.go(执行Go)。 Redisson(Java实现)。

安全和活动保证

就有效分布式锁的最小保证粒度而言,我们的模型中仅使用了以下三个属性:

1.属性安全性:互斥的行。在任何时候,只有一个客户端可以获取锁。

2.活动属性A:无死锁。即使客户端已经拥有一个已损坏或已被拆分的锁,它也可能会请求另一个锁。

3.活动属性B:容错。只要大多数Redis节点可用,客户端就可以获取和释放锁。

为什么基于容错的实现还不够

要了解我们所做的改进,我们必须首先分析基于Redis的分布式锁的当前实践。

使用Redis锁定资源的最简单方法是创建一对键值值。使用Redis超时机制,密钥被创建为具有一定的生存期,因此最终将被释放。当客户端想要释放密钥时,只需直接删除密钥即可。

通常,这很好用,但是有一个问题:这是系统的单点。如果Redis主节点出现故障怎么办?当然,我们可以添加一个子节点,而主节点可以在出现问题时进行切换。不幸的是,此解决方案不可行,因为Redis的主从复制是异步的,因此我们无法使用它来实现互斥的安全性功能。

这显然是该模型的竞争条件:

    \ 客户端A在主节点上获得了锁。 主节点已挂起,并且尚未完成对从节点的写入同步。 从节点升为主节点。 客户端B获得与A相同的锁。请注意,该锁的安全性已被破坏!

有时,这在某些情况下效果很好,例如,当发生错误时,多个客户端可以获得相同的锁。如果这正是您想要的,则可以使用主从复制方案。否则,建议您使用本文中介绍的方法。

单实例正确实施解决方案

在尝试解决上述单实例解决方案的缺点之前,让我们确保对于这种简单情况,正确的做法是,因为该解决方案对于某些程序也是可接受的,并且这也是分布式方案的基础我们将在稍后描述。

为了获得锁,方法是这样的:

SET resource_name my_random_value NX PX 30000

此命令将设置key的值,该值仅在不存在时才有效(NX选项),并将其生存期设置为30000毫秒(PX选项)。与该键关联的值是” my_random_value \\。此值在所有客户端和所有锁定请求中必须唯一。

使用随机值主要是为了能够安全地释放锁,必须将其与这种处理逻辑结合使用:如果且仅当键值已经存在并且其值是我们期望的值时,才删除键值。看一下下面的lua代码:

如果redis.call(”获得\”,键[1])== ARGV [1],则

返回redis.call(\ quot; del \ quot;键[1])

其他

返回0

结束

这对于避免意外删除其他客户端创建的锁很重要。例如,客户端获得了锁,但是其处理时间超过了锁的有效时间。之后,它将删除该锁,并且此时其他客户端可能会获取该锁。仅删除是不安全的,可以删除其他客户端的锁。结合以上代码,每个锁都有一个唯一的随机值,因此只有当该值仍然是客户端设置的值时,才会将其删除。

那么,该随机值应如何生成?我们使用了从/dev/urandom读取的20个字节,但是只要您能够完成任务,您还可以找到一个更简单的方法。例如,您可以使用/dev/urandom初始化RC4算法,然后使用它生成随机数流。比较简单的方法是结合使用unix时间戳和客户端ID,这虽然不安全,但对于许多环境而言已经足够了。

我们所指的关键时间是指”锁定的有效持续时间”。它代表两种情况,一种是指自动释放锁的长度,另一种是指客户端在另一个客户端获得该锁之前的状态。占用该锁的时间长度,该时间长度限制为自从该锁释放后的一段时间。获取锁。

现在,我们有了获取和释放锁的好方法。在单实例非分布式系统中,只要不挂断节点,此方法都是安全的。因此,让我们将此概念扩展到没有这种保证的分布式系统。

Redlock算法

在此算法的分布式版本中,我们假设有N个Redis主节点。这些节点彼此独立,因此我们不使用复制或其他隐式同步机制。我们已经描述了在单个实例的情况下如何安全地获取锁。我们还指出,该算法将使用此方法从单个实例获取和释放锁。在以下示例中,我们设置N = 5(这是一个中等值),因此我们需要在不同的物理机或虚拟机上运行5个Redis主节点,以确保它们的错误尽可能独立。

为了获取锁,客户端执行以下操作:

    \ 获取当前时间(以毫秒为单位)。 尝试使用相同的密钥值和相同的随机值以串行方式从所有N个实例获取锁。当从每个实例获取锁时,客户端设置连接超时,该超时比锁的自动释放时间短得多。例如,如果锁的自动释放时间为10秒,则连接超时可能设置为5到50毫秒。这可以避免在Redis节点挂起时长时间阻止客户端:如果一个节点没有及时响应,则应尽快将其转移到下一个节点。 客户端通过使用当前时间减去步骤1中的时间戳来计算获取所有锁所需的时间。当且仅当客户端可以从大多数节点(至少3个)获得锁,并且花费的时间为小于锁的有效期,可以认为已获取了锁。 如果获取了锁,则其最终有效持续时间将重新计算为其原始持续时间减去在步骤3中获取锁所花费的时间。 如果锁获取失败(N/2 + 1个节点未锁定,或者锁定的最终有效持续时间为负),则客户端将解锁所有实例(即使它们未锁定),成功的示例也是如此)。

算法是异步的吗?

该算法基于这样的假设:处理时它不是(基于)同步时钟,并且每次处理中仍使用本地时间,它仅以大致相同的速率运行,因此它将有一个很小的错误,自动打开和关闭的时间要短一些。这个假设非常类似于真实世界的计算机:每台计算机都有一个本地时钟,通常我们使用另一台时钟差很小的计算机。

基于此观点,我们需要更好地指定我们的共同互斥规则:这是为了确保客户端可以长时间维护状态锁,这将在有效时间内(在步骤3中获得)在一定时间内终止工作。 (减去几毫秒以补偿处理中的时间差)。

如果想更多地了解系统所需的时间差范围可以获取更多信息,则本文是一个很好的参考:租约:一种有效的容错机制,用于分布式文件缓存的一致性。

重试失败

当客户端无法获取锁时,应在随机延迟后重试,以避免多个客户端尝试同时获取与同一同时请求相对应的锁(这可能会导致崩溃,并且不会有人崩溃)赢得)。同样,在大多数情况下,客户端尝试获取锁的速度越快,崩溃的窗口就越少(并且所需的重试次数也越少),因此在实践中,客户端应尝试使用多路复用将SET命令发送到多个实例。

强调客户端无法获取主锁是值得的。有必要尽快释放(或部分释放)以获取锁,因此无需等待密钥过期即可获取锁(但是如果网络分区发生更改,则客户端无法与Redis通信接下来,需要明确的提示和等待超时。

释放锁

释放锁很简单,尽管客户端认为它具有成功锁定给定实例的能力,但只需要释放所有实例的锁即可。

安全参数

算法安全吗?然后尝试了解在不同情况下发生的情况。我们从假设客户端在大多数情况下可以获取锁开始,并且所有实例都包含具有相同生存期的密钥。由于按键设置在不同的时间,按键也将在不同的时间超时。但是,如果在时间t1(即在样本联系第一台服务器之前)建立了第一节点,则最后一个密钥在时间T2(从先前的服务器获得答复的时间)建立。可以确定的是,第一个密钥在超时之前至少可以保留MIN_VALIDITY = TTL-(T2-T1)-CLOCK_DRIFT。在所有其他键都失效之后,至少在同一时间将同时设置这些键。

在设置了一半以上的密钥的时间段内,另一个客户端无法获得该锁。如果已经存在N/2 + 1键,则N/2 + 1 SET NX操作将失败。因此,不可能同时获取和重复获取锁(违反互斥)。

但是,我们还希望多个客户端无法同时获取锁。

如果客户端锁定大多数实例的时间超过了锁定的最大有效时间(TTL基本设置),它将认为该锁定无效并将其解锁。因此,我们仅考虑大多数实例在有效时间内获得锁的情况。上面已经讨论了这种情况,对于MIN_VALIDITY,没有客户端会重新获取该锁。因此,只有当大多数实例的锁定时间超过TTL时间时,多个客户端才能同时锁定N/2 + 1个实例(在步骤2中的”时间”结束时),并使锁定无效。

您能否提供一个正式的证明,说明是否存在足够类似的算法或错误?那我们将不胜感激。

生存能力证明

系统的生存能力基于以下三个主要特征:

    \ 自动释放钥匙(钥匙将过期):最终所有钥匙将被重新锁定; 一般而言,如果客户端没有成功获取锁,或者获取了锁并且工作已完成,则锁会及时释放,这样我们就不需要等待自动释放键来重新获取。 当客户端重新获取锁时,它等待的时间比获取锁本身要长得多。这是为了最小化由资源竞争引起的裂脑情况的可能性。

但是,在网络碎片化的情况下,我们必须支付相当于\的可用性成本。 TTL \\”时间。如果网络继续分裂,我们将无限期地支付此价格。当客户端获取锁,并且在删除锁之前断开网络连接时,会发生这种情况。

基本上,如果网络无限期地继续分裂,则系统将无限期地不可用。

性能,故障恢复和文件同步

许多用户将Redis用作需要高性能的锁服务器,可以根据延迟动态获取和释放锁,并且每秒可以成功执行大量锁获取/释放操作。为了满足这些需求,多路复用策略是与N个Redis服务器合作以减少延迟(或互助差,即,将端口置于非阻塞模式,发送所有命令,并延迟读取所有Command,假设客户端与每个Redis实例的往返时间相似)。

但是,如果我们打算为故障系统实施恢复模型,则这是与持久性有关的另一种想法。

考虑到这个基本问题,假设我们根本没有配置Redis持久性。客户端需要锁定5个实例中的3个。其中之一允许客户端获取的锁重新启动。尽管我们可以为某些资源再次锁定3个实例,但其他客户端也可以锁定它,这违反了排它锁的安全性。

如果我们启用AOF持久性,情况将得到极大改善。例如,我们可以通过发送SHUTDOWN并重新启动服务器来升级服务器。由于Redis的截止日期是通过语义设置的,因此关闭服务器后虚拟时间仍然会流逝,并且满足了我们的所有需求。无论如何,只要服务器完全关闭,所有事务都将正常运行。如果电源中断会发生吗?如果配置了Redis,默认情况下,文件将每秒同步写入磁盘,并且重启后数据很可能会丢失。从理论上讲,如果我们要保证安全性,在任何类型的实例重新启动后锁定,我们需要确保在持久性配置中设置了fsync = always,这将在相同级别的CP系统上失去性能,而后者通常用于更安全地分配锁。

无论如何,情况看上去比我们初看时要好。基本上,算法的安全性得到保留,即使实例在失败后重新启动,它也将不再参与任何当前活动锁的分配。因此,当实例重新启动时,除了重新加入系统之外,将从锁定的实例获取所有活动锁的当前设置。

为了确保这一点,我们只需要创建一个实例。超过最大TTL后,它会崩溃并且不可用。然后,需要时间来获取所有现有锁的钥匙。当实例崩溃时,它将变为无效。被自动释放。

使用延迟重新启动基本上可以实现安全性,甚至不需要使用任何Redis持久性功能,但是还有其他副作用。例如,如果大量实例崩溃并且系统变得全局不可用,则为TTL(全局此处表示根本没有资源可用,并且在此期间所有资源都将被锁定)。

使算法更可靠:扩展锁定

如果客户端的工作执行由小的步骤组成,则默认情况下它可以在默认时间内使用较小的锁,并扩展该算法实现的锁扩展机制。当锁的有效性接近较低时值,则客户端通常在操作的中间。获取锁后,可以通过发送Lua脚本将扩展锁发送到所有实例。此实例是扩展TTL的密钥。 value是客户端复制的随机值。

客户只应考虑重新获得锁。如果可以扩展,则锁定将在有效时间内输入大量实例(基本算法与获取锁定的用法非常相似)。

尽管这不是对该算法的技术更改,但无论如何,需要限制获取锁的最大尝试次数,否则它将违反活动中的属性。

输入原始文字以参与互动

未经允许不得转载:数据库同步软件|Mysql数据同步软件|sqlserver数据库同步工具|异构同步 » Redis数据库中实现分布式锁的方法

分享到:更多 ()

syncnavigator 8.6.2 企业版

联系我们联系我们