实现在redis数据库分布式锁的方法

实现在redis数据库分布式锁的方法
分布式锁是在许多环境中的一个非常有用的原始,它是分享不同的进程互斥操作资源的唯一途径。有许多开发库和博客描述如何使用redis实现DLM(分布式锁管理器),但每个开发库采用不同的方式,和更复杂的设计和实现的比较,许多图书馆使用一些简单可靠的实现路径

本文试图用Redis实现分布式锁提供更标准的算法,我们提出的算法称为锁定,它实现了DLM(分布式锁管理),我们认为比香草单实例更安全。我们希望社会分析并提供反馈是一个更复杂的或替代的设计。

实现

在具体算法之前,有一些具体的实现供参考

Redlock rb(Ruby实现)。
Redlock php(PHP实现)。
Redsync。(实现)。
Redisson(java实现)。
安全主动保障

从有效的分布式锁的最小保证粒度来看,我们的模型中只有3个属性,如下所示:

1。属性安全性:互斥。在任何时候,只有一个客户可以得到锁。

2。活动属性A:无死锁。即使客户机有一个已损坏或已被分割的锁,也可以请求其他锁。

三.活动属性B:容错。As long as most Redis nodes are available, the client can get and release the lock.

为什么容错实现还不够

要了解我们所做的改进,我们应该首先分析当前使用分布式锁。

用Redis锁资源的最简单的方法创建一对密钥值。使用Redis的超时机制,关键是有一定的寿命,所以它最终会被释放。当客户想要释放它,它是删除键直接右。

总的来说,这是一个很好的工作,但是有一个问题:这是一个单点系统。如果redis主节点挂当然,我们可以添加一个子节点,可以切换时主节点出问题。不幸的是,这个方案是不可行的,因为从复制的redis的所有者是异步的,我们不能用它来实现互斥锁的安全功能

这显然是模型的竞争条件

客户机A在主节点上获得一个锁。
主节点挂起,来自节点的写同步尚未完成。
将节点提升到主节点。
客户机B获得与A相同的锁。注意锁的安全性已被销毁!
有时,在某些情况下这很好,例如,当出现错误时,多个客户机可以得到相同的锁。如果这正是您想要的,您可以使用复制方案中的主程序。否则,我们建议使用本文中描述的方法。

单实例的正确实现

在试图破坏上面描述的单实例解决方案之前,让我们确保在这个简单的情况下,怎样做是正确的,因为一些程序的这个方案是可以接受的,这是我们的基本分布式方案。

为了得到锁,这就是它的方式:

复制代码代码如下所示:

集resource_name my_random_value NX PX 30000

该指令集的关键价值,只有当它不存在(NX的选项),并将其30000毫秒的寿命(PX选项)。与关键值是my_random_value。该值必须是唯一的,在所有的客户所有的锁请求。
一个随机的使用价值是能够安全地释放锁,而应结合这样一个逻辑:当且仅当它已经存在的价值是什么我们希望删除的关键。看看下面的示例代码:

复制代码代码如下所示:

如果redis.call(得到钥匙{ 1 })= argv { 1 }然后

返回redis.call(删除

其他的

返回0

终点

重要的是这样做,以避免意外删除其他客户端来创建锁。例如,客户端获得一个锁,但其处理时间比锁的有效时间长。之后,它删除了其他客户端可能同时获取的锁。简单地删除对于删除其他客户端锁是不够安全的。结合上述代码,每个锁都具有唯一的随机值,所以只有当该值仍然是客户机设置的值时,才会删除该锁。
那么我们应该怎样生成这个随机值呢我们使用的是20字节/ dev / /dev/urandom相似读,但你也可以找到一种更简单的方式来满足任务,只要你能。例如,你可以使用/ dev / /dev/urandom相似初始化RC4算法,然后用它来生成随机数流,比较简单的办法是结合Unix时间戳和客户ID,这是不安全的,但这是不够的许多环境。

我们说的键,指的是锁的有效长度。它代表两种情况,一种是自动释放锁时间,另一种是指另一个客户端在客户端占用锁时间之前获取锁,它是在捕获后从锁开始的时间窗口中被限制的。

现在我们有了一个获取和释放锁的好方法。在一个单实例的非分布式系统中,只要节点不被连接,这个方法是安全的,所以我们将这个概念扩展到一个没有保证的分布式系统中。

Redlock算法

在该算法的分布式版本,我们假设有n个redis主节点,这些节点是相互独立的,所以我们不使用复制或其他隐式的同步机制,我们描述了如何在一审案件保密锁。我们还指出,该算法将使用此方法从一个实例获取和释放锁。在下面的例子中,我们设定N = 5(这是一个相对温和的价值),所以我们需要运行5个redis主节点在不同的物理设备或虚拟机保证误差尽可能独立。

为了获得锁,客户机执行以下操作:

以毫秒为单位获取当前时间。
试着从所有的实例在一个串行的方式得到锁,使用同一个密钥值和相同的随机值。当锁是从每个实例,客户端设置连接超时,并且锁的自动释放时间比锁短得多。例如,如果锁自动释放时间是10秒,连接超时约5至50毫秒。这可以防止redis节点被挂起和长时间阻塞客户端:如果一个节点没有响应时,它应该被转移到下一个节点尽快。
客户端计算得到所有锁代价的长度,并且该方法是使用当前时间来减少步骤1中的时间戳。当客户端可以从大多数节点(至少3)中获得锁,并且所花费的时间小于锁的有效性时,它被认为已获得锁。
如果获得的锁,锁的最终有效期将重新计算在原来的时间长度减去步骤3步消耗的锁的长度。
如果锁捕获失败,则n 2 + 1节点没有锁定,或者锁的最终有效时间为负,客户端将解锁所有实例,即使对于那些没有锁定的实例。

该算法是异步的。

该算法依赖于一个假设,它不是在处理时间(基于同步时钟),每个处理采用的仍然是当地时间,大概是在同样的速度,所以它会有一个小的错误,而这将是一个小型的自动开启时间。这个假设很像一个真正的世界计算机:每台计算机都有一个本地时钟,通常我们使用不同的计算机有一个非常小的钟差。

基于这一观点,我们需要更好地表明我们的普通法的排他性:这是为了确保客户可以长期保存它将终止他们的锁状态,有效的工作时间(在步骤3),减去一些时间(在处理时间差时减去一些毫秒)。

我们想知道更多的信息,系统需要一系列的时间差异。本文是一个很好的参考:租约:一种高效的分布式文件缓存一致性容错机制。

重试失败的时候

当客户无法获得锁,它应该随机延迟后再试,所以多个客户在同一时间尝试获得锁,相应的在相同的时间相同的请求(这可能导致崩溃,没有人会赢)。同样,当客户端试图在大多数情况下获得锁,更快的Windows崩溃的数量,不需要重试,所以实际上,客户应尽量复用的多个实例发送设置命令

在主要的收购失败客户锁定值,释放(或部分)获取锁,尽快,所以不需要获取锁和钥匙等待期满(但是如果客户不能与明确的暗示下redis和等待超时的网络分区的变化)。

释放锁

释放锁很简单,只是为了释放所有实例的锁,尽管客户端认为它能够成功锁定给定实例。

安全参数

问算法安全吗然后你可以试着去了解在不同的情况下会发生什么。我们先假设客户可以在大多数情况下获得锁,所有的实例包含相同的生命周期的关键。因为在不同的时间是钥匙,钥匙也会挤出时间在不同的时间。但是,如果第一个节点是在新建立的T1时间(样品前接触的第一个服务器),最后的关键是建立在T2时间最晚(从最后一个服务器得到回复的时间)。可以肯定的是,第一个关键至少能存活min_validity = TTL(T2-T1)-在超时之前clock_drift。在所有其他的键会失效,关键是设置至少一次在同一时间。
在设置密钥时,另一个客户机无法获得锁,如果n 2 + 1键已经存在,则n 2 + 1集NX操作将不成功。因此不可能同时获得并重复锁(违反互斥)。

然而,我们也想让多个客户在获得锁的同时不成功。

如果客户端锁大多数情况下比锁的最大有效时间(TTL基本设置),它将考虑锁定无效和解锁。所以我们只考虑锁定在大多数情况下,在有效的时间的情况下,这种情况已经在前面的文章中讨论,为min_validity没有客户会得到锁再次,当锁大多数情况下是在TTL时间,许多客户可以锁定n / 2 + 1的情况下,在同一时间(在步骤2的结束),所以锁定失败。

无论你能否提供一个正式的证明,指出现有的算法足够相似,或者发现一些bug,我们将感激不尽。

生存能力的证明

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

自动释放锁(钥匙到期):最后所有的钥匙都会再次锁住;
一般来说,如果客户端没有得到锁,或者获得锁并完成了工作,它将及时释放锁,这样我们就不必等待键自动释放。
当客户机再次获得锁时,它将等待一段时间,这个时间比锁本身要长得多。这是为了减少资源竞争引起的脑裂状况。
然而,在网络碎片的情况下,我们必须支付与TTL时间相当的可用性成本。如果网络继续分离,我们将不得不支付这个价格。这是在客户端获取锁和网络在锁被删除前断开时发生的。

基本上,如果网络连续无限期地分散,系统将无法无限期地使用。
性能故障恢复和文件同步

很多用户使用Redis作为一种高效锁定服务器。他们可以获取和释放锁可以根据延迟,并能成功地执行大量的访问/每秒释放锁。为了满足这些要求,在N个redis服务器以减少延迟合作的多元化战略(或合作不好,也就是说,端口设置为非阻塞模式,把所有的命令,延迟读出所有的命令,往返时间假定客户端和每个redis实例相似)。

然而,如果我们的目标是实现故障系统的恢复模式,还有另一种考虑持久性的方法。

考虑到这个基本问题,假定我们没有配置Redis的持久性在所有。一个客户需要锁定3的5个实例,其中一个让客户得到锁启动。虽然我们可以再次锁定某些资源的3个实例,但其他客户端也可以锁定它,这违背了独占锁的安全性。

如果我们使AOF持久化,情况会大大改善。例如,我们可以升级重新启动服务器,它通过发送关机。因为使用期限是由语义,所以虚拟的时候还是会去当服务器关闭。我们所有的需求都是满意的。在任何情况下,只要服务器完全关闭,所有的事务都会工作。如果电源中断了怎么办如果使用配置,默认的文件同步到磁盘的每一秒,和它是可能的,我们的数据将丢失,重新启动后,在理论上,如果我们想确保在任何实例重新启动锁的安全性,我们需要确保fsync =总是在坚持配置。这将对CP系统相同的水平输性能,这是传统上用于分配锁更安全。

无论事情看起来比我们第一次看到他们。基本上,该算法的安全性是保留的,甚至如果实例失败后重新启动时,它将不再参加任何活动锁分配。所以当一个实例重新启动,所有的主动锁的当前设置将得到锁定实例并重新加入系统。

为了确保这一点,我们只需要做一个例子。在打破了最大的TTL,它是不可用的。然后我们需要时间来获取现有锁的所有密钥。当实例崩溃时,它将变得无效,它将自动释放。

利用延时启动基本可以达到安全,甚至不需要利用任何Redis的持久性的特点,但也有其他的副作用。例如,如果大量的实例,系统崩溃,成为全球范围内可用,然后TTL(这里全世界意味着没有在所有和所有的资源,资源将被锁定在这个时间)。

使算法更可靠:扩展锁

如果客户工作的执行是由小的步骤,它可以通过在默认时间默认时间使用较小的锁,并延伸通过算法锁扩展机制。当锁的有效性是一个低值,客户通常是在运行中,当锁收购,扩大锁可以被发送到所有实例发送一个Lua脚本。这个实例是扩展TTL的关键。如果键存在,其值是客户机复制的随机值。

客户应该只考虑重新获取锁。如果它可以被扩展,锁将在有效时间内输入大量实例(基本算法非常类似于获取锁)。

虽然这不是一个技术改变算法,但获取锁的最大次数是有限的。否则,它将违反活动的一个属性。

免责声明:本网信息来自于互联网,目的在于传递更多信息,并不代表本网赞同其观点。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,并请自行核实相关内容。本站不承担此类作品侵权行为的直接责任及连带责任。如若本网有任何内容侵犯您的权益,请及时联系我们,本站将会在24小时内处理完毕。
相关文章
返回顶部