Redis分布式锁的Python实现python-redis-lock

关于分布式锁有很多种实现方式,可以用数据库锁或者ZooKeeper这类的专业的分布式开源项目。本文讲的是用Redis实现的一个分布式锁库python-redis-lock.

Redis官方有推荐一个分布式锁的算法Redlock(这个库实现的并不是这个算法), 该算法自动释放锁没有考虑到客户端长期持有的情况,因此也有人对这个算法提出了质疑。那回到我们今天要讲的这个库python-redis-lock。作者:Ionel Cristian Mărieș, 这个库整体的思路作者也用很直观的图展现出来了,如下:

大致思路

从图上看出作者和其它大多数用Redis实现分布式锁的思路类似(),但是他对每个锁多用了一个类型键来做信号控制,如果客户端第一次尝试获取锁失败,可以选择在signal列表上阻塞一个timeout时间用来接收锁被释放的通知,Redis列表的这个特性保证了每次只有一个客户端接收到了锁释放的通知。而获取到锁的客户端在使用完后会在对应的信号列表上推送一个通知。另外,作者对锁超时还增加了一个刷新的功能来延长(Extend)对锁的占用,可以保证在持有锁的客户端上完成所有操作后才释放锁。个人认为这种设计的优点和需要注意的点如下:

优点

一方面避免客户端反复请求锁,另一方面通过来让客户端决定是否要block自己;

如果有设置超时,则等待超时后客户端仍然会再尝试获取一次锁而不是直接失败;

这个算法不依赖客户端时间戳,也就没有time drift问题;

结合Lua脚本做原子操作,如果再加上细粒度锁,个人认为基本可以满足各种高需求场景的分布式锁要求。

⚠️Warning

自动刷新可能会造成饥饿问题,如果持有锁的客户端因为某种未知原因阻塞,并且开启了自动刷新锁,那其它客户端就跪了,所以需要使用者慎用刷新机制;

如果没有设置超时,且持有锁的客户端无响应的情况下就会造成死锁;

源码分析

了解过大体思路后,我们来一步步分解作者的实现。首先这个库源码只有两个脚本(不含测试和示例代码), 结构很简单。

src/redis_lock

├──init.py

└── django_cache.py

核心代码在中,则是结合做的缓存后端,来避免缓存失效时遇上所谓的“”,这里不对它进行解析。

载入Lua脚本

作者用上面这种方式定义了,,... 等5个原子操作的Lua脚本,每个脚本也定义了对应的哈希值。关于Redis的Lua脚本支持可以看这篇文章。比较有意思的是下面这段代码,可以说是很了:

为了把使用时要指定的脚本ID与其脚本、哈希值关联起来,作者用了来自动生成索引 ID , 然后又用 来拍扁整个列表,最后再用把索引提取出来,把哈希值对应的索引ID用变量名存起来,没用的索引用忽略,其余内容依然在元组中。这样一来用下面这个函数执行Redis的Lua脚本就很舒服了:

创建锁

主要代码都在类中,创建锁对象时大部分都是常规操作,保存实例的一些设定。

需要注意的几个点:

可以用 来申明对锁所有权识别,例如客户端的主机名称或者进程号什么的,默认是16个随机字节。

如果指定了锁自动刷新,那刷新间隔会设定在超时的2/3时间。

获取锁

获取锁的相关代码如下,还是选择在代码注释中解析代码会比较直观点。这里和上一段代码一样,省略了参数校验,这是很重要的一步,并且是一个良好的编程习惯,但是限于篇幅这里不做介绍。

这里有一个问题,如果在 成功获取到信号,并不代表下一次 循环尝试获取就一定成功,如果在此间隙中被其它客户端获得了锁,那该客户端仍然会获取失败,并去阻塞一个 时间。也就是说假设这个客户端的网络质量很差,而又恰恰是一个高频请求的锁,那就可能造成它虽然设置了超时,但最终结果可能等待了不止一个 时间才拿到结果,而且还可能会一直获取不到锁。

获取锁的开头用一个 内部属性来判断当前实例是否已经拥有了锁,这里就是上一步中的 属性的用处,来判断锁的拥有者。代码如下:

刷新锁

刷新锁比较繁琐,作者用了一个线程在后台定时刷新,不过我们先来看刷新锁的实际操作:方法, 这个函数没加下划线前缀也就是允许锁的拥有者自己手动刷新。

这个 操作的 Lua 脚本如下:

刷新锁的线程相关代码如下:

这里主要是一个弱引用问题。传入了守护进程的变量要格外小心,很容易造成即使主线程已经不再引用这个变量,而守护进程不依赖该变量,却一直引用着,就导致内存无法释放。

释放锁

释放锁的实际操作和刷新锁一样,因为都涉及多个 Redis 命令,所以他们都放在了 Lua 脚本中。

至于 脚本操作在第一步里已经展示出来,如果持有该锁则先删除 列表,再 一个通知到 列表,最后删除锁。这些步骤都会在一个Lua函数中执行保证原子性。

上下文支持

作者也重载了 和 两个函数来支持 的上下文调用,也很简单:

示例代码

这里我就直接贴上作者的示例代码:

总结

这个库提供的分布式锁很灵活,是否需要超时?是否需要自动刷新?是否要阻塞?都是可选的。没有最好的算法,只有最合适的算法,用户应该根据自己是场景谨慎选择。喜欢的朋友可以去这个项目的GitHub页面点个。

另外,Redis这个东西感觉可以做很多事,而且可以做很多高性能的事。尤其在分布式环境下,重点是还支持各种有意思的特性。用它来实现分布式锁就显得再合适不过了。

分享就到这里,谢谢大家!

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180809G1MAMZ00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券