redis 分布式锁

基于redis的分布式锁(redlock)

在分布式系统逐渐变为主流的情况下,开发者会遇到很多在单机系统中不会遇到的问题,而如何实现一个健壮的分布式锁就是很多开发者要面临的一个问题。

分布式锁的实现方式有很多种,本文将讲述一种基于redis实现的分布式锁(至于网上博客上说的那种仅仅利用SETNX命令去实现锁的,这种在单机下还行,如果需要在分布式环境下使用,呵呵~)。

PS:本锁的实现是官方提供的Redlock算法(其实就是翻译了一下,相较于谷歌翻译就是更易读,虽然这看起来没什么,但是很多时候这将最终影响你是否能够理解该算法)

首先,我们列举出满足我们需求的分布式锁的基本要素:

1、安全特性:相互排斥。也就是说在任何时刻,只有一个客
户端可以锁定而不会发生锁被两个客户端同时锁定。
2、活性A:无死锁。也就是说,无论什么情况,例如锁定资源    的客户端崩溃或者网络被分裂也总是可以获取锁的。
3、活性B:容错,只要大多数redis节点启动,客户端就可以获    取并释放锁。

然后我们看为什么基于故障切换(master-slave模型,master故障时slave升级为master)的实现是不够的:

使用redis锁定资源最简单的方法是在redis实例中创建一个密        钥,关键是通常使用redis过期功能实现在客户端崩溃后锁的自    动释放。

看起来该实现很好,但是存在这样一个问题:该实现存在单点故    障。考虑这样一个场景,如果redis-master挂掉会怎么样呢?为    了防止redis-master挂掉,我们添加一个slave,如果master挂    掉就切换到slave节点,但很不幸的是,这样是不可行的,通过    这样做无法实现上边的互斥安全属性,因为redis复制是异步        的。
上述模型有一个明显的竞争条件:
    1、客户端A获取master设备中的锁定;
    2、在将密钥写入slave节点前master崩溃;
    3、slave升级为master;
    4、客户端B使用同样的密钥获取客户端A本已获取到的资源        锁;
这样就发生了互斥安全问题!

那么如何解决上述问题呢?我们可以这样做:

在尝试克服单实例的节点故障问题前,让我们在这种简单的情况    下检查如何正确的执行它,因为这实际上是一个可执行的方案,    在    应用程序中不时会出现争用情况,并且实现单实例redis的    锁定是我    们描述分布式算法的基础;

要获得锁定,要走的路线如下:
SET resource_name my_random_value NX PX 30000
该命令只在密钥不存在时才会成功设置(NX选项),并且过期    时间为30000毫秒(PX选项)。该键被设置为一个随机值,该随    机值在所有的客户端和所有对该资源的锁定请求中必须唯一(否    则会出现互斥问题,下面会介绍)。

基于随机值是为了以安全的方式释放锁,使用随机值,通过脚本    告诉redis:只有当密钥存在并且存储在密钥中的值恰好与我期    望的值(随机值)一样时,才能移除该密钥。该过程是很重要        的,主要是为了防止一个客户端移除其他客户端创建的锁。例        如:客户端A当前获取到了锁,但是客户端A并不是因为崩溃而    超时,只是被阻塞超时,例如进行数据库操作时被阻塞,所以等    到客户单A阻塞结束后,他会认为他还持有锁,然后会通过DEL    命令尝试释放锁,但是实际上该锁此时是客户端B所有的,而客    户端A并不知道,这样就会造成客户端A将客户端B的锁删除而造    成客户端B锁失效,并且后续客户端B可能还会重复该操作,此    时锁实际上已经失去了作用,而如果加入随机数后,客户端A删    除的时候会校验该随机数,如果发现该随机数与客户端A设置的    不一致时则表示该锁已经不是A所有的,则A不会执行DEL操作,    B仍然会持有锁继而保持互斥安全,而这也是为什么要保证随机    数唯一的原因。

现在我们有了一个很好的方法来获得和释放锁,该系统对于由一    个总是可用的redis实例组成的非分布式系统(redis是非分布式    的)是安全的,现在我们尝试将该概念扩展到一个分布式系统。

在上述算法的分布式版本中,我们假设我们有N个redis-master    节点,这些节点完全独立,所以我们不使用复制或任何其他隐式    协调系统。我们已经描述了如何在单个实例中安全的获取和释放    锁,我们理所当然的认为该算法将使用此方法在单个示例中获取    并释放锁。在我们的例子中,我们设置N=5,这是一个合理的    值,所以我们需要在不同的计算机或虚拟机上运行5个redis-ma    ster节点,以确保他们以最独立的方式失败(单个节点挂掉不会    影响其他节点)。

为了获得锁,客户端执行以下操作:
    1、它以毫秒为单位获取当前时间;
    2、它试图依次获取所有N个实例中的锁,在所有实例中使用        相同的密钥名和随机值。在步骤2中,当在每个实例中设置锁        时,客户端使用与锁自动释放时间相比较小的超时以获取它,        例如:如果自动释放时间是10秒,则超时可能在5至50毫秒范        围内。这样当某个redis挂掉后可以防止客户端长时间阻塞在        尝试与该redis实例创建连接的状态,如果redis实例不可用,        那么我们应该尽快尝试与下一个redis实例通讯。
    3、客户机通过从当前时间中扣除步骤1中获得的时间戳来计        算获取锁定所花费的时间。当且仅当客户机能获得大多数redi        s实例的锁定(至少N/2+1个redis实例的锁定),并且获取该        锁的总时间小于锁有效时间,则认为该锁被获取;
    4、如果获得锁,则其有效时间被认为是初始有效时间减去经        过时间,该经过时间如步骤3中计算的那样。(因为如果超过        该时间那么获取的第一个redis实例的锁将会超时自动释                放),该锁将有可能不满足[至少N/2+1个redis实例的锁定]        条件;
    5、如果客户端由于某种原因未能获得锁(或者无法锁定N/2        +1个实例或者获得的锁有效时间为负),他将尝试解锁所有        实例(即使他认为他没有获取到锁,方便后续其他客户端重新        获取锁);

算法是异步的吗?

上述算法依赖于这样一个假设:即虽然跨进程没有同步时钟,但    每个进程中的本地时间仍以大约相同的速率步进,与锁的自动释    放时间相比误差较小。这个假设与真实世界的计算机非常相似,    每台计算机都有一个本地时钟,我们通常可以依靠不同的计算机    来产生很小的时钟漂移。

在这一点上,我们需要更好地指定我们的互斥规则:即锁的有效    期只有持有锁的客户端在有效时间(有效时间为步骤3中获得        的)减去某个时间(仅几毫秒以补偿不同主机之间的时钟漂        移)。

参考文献

[2]H. Berenson, P. Bernstein, J. Gray, J.Melton, E. O’Neil,and P. O’Neil. A critique of ANSI SQL isolation levels. InProceedings of the SIGMOD International Conference on Management of Data, pages1–10, May 1995.

[3]Michael J. Cahill, Uwe Röhm, and Alan D.Fekete. 2008. Serializable isolation for snapshot databases. In SIGMOD ’08:Proceedings of the 2008 ACM SIGMOD international conference on Management of data, pages 729–738, New York, NY, USA. ACM.

[4]Michael James Cahill. 2009. Serializable Isolation for Snapshot Databases. Sydney Digital Theses. University of Sydney, School of Information Technologies

[5] A. Fekete, D. Liarokapis, E. O’Neil, P.O’Neil, andD. Shasha. Making snapshot isolation serializable. www.codexueyuan.com In ACM transactions on database systems, volume 39(2), pages 492–528, June 2005.
版权声明:本文内容

上一篇:查询sql server 2005 内存瓶颈


下一篇:[python] 小游戏 - play_plane