设为首页 收藏本站
查看: 1106|回复: 0

[经验分享] 使用redis构建可靠分布式锁

[复制链接]

尚未签到

发表于 2017-12-20 17:39:52 | 显示全部楼层 |阅读模式
分布式锁的多种实现方式

  分布式锁总结
  对于分布式锁的几种实现方式的优劣,这里再列举下
  1. 数据库实现方式
  优点:易理解
  缺点:操作数据库消耗较大,性能较低。为了处理一些异常,会使得整个方案越来越复杂
  2. 缓存实现方式
  优点:性能好,实现起来较为方便。
  缺点:通过超时时间来控制锁的失效时间并不是十分的靠谱。
  3 zookeeper实现
  优点:有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。
  缺点:性能上不如使用缓存实现分布式锁
  第二篇帖子中,谈到redis实现分布式锁时,提了一些建议
  "redis如果能像ZooKeeper一样,实现了和客户端绑定的临时key,一旦redis客户端挂了,临时key删除,通知watchkey的其他客户端(感觉这个是一个不错的需求,不知redis未来是否要实现),就可以消除锁超时,再使用Redlock实现的分布式锁,这时候可靠性就更高了。"
  就性能而言,redis比zookeeper具有天然优势,而它的缺点也可以通过一些机制来另外改进。所以就尝试着修改了redis的源码,看能否解决上述问题。
  修改点一:增加一条命令settp
  settp(tp 可以理解为temporary的缩写),故名思议,就是一个临时的key。
  命令格式:settp key value
首先使用这条命令,必须保证key是不存在的,即这个命令具有setnx命令的属性,然后在添加完key之后,将这个key加入到执行这条命令client的一个list里面。这个list专门用来保存临时键。那么在redis客户端挂了,或者意外断开连接时,在调用freeclient()函数时,便可以将临时键清理掉。就不会影响其他client再次获取锁


修改点二:增加命令watchex

命令格式:watchex key

返回:redisReply是一个字符串类型

        如果key存在,则str内容为"EXIST"

        如果key不存在,则str内容为"NOEXIST"

        如果key被添加,返回"ADD";key被删除时,返回"DEL"

watchex,ex可以认为是exist的缩写,也是为了区别redis本身带有的watch命令。自带的watch命令,是为了在执行事务时,保证事务执行过程中键不被修改的一种乐观锁机制。而我们要实现的watchex命令,是为了监视某个键是否存在。在执行命令时,立即会返回一个结果,表示这个键是否存在。然后在运行过程中,如果这个键被创建,或者被删除,也会通知到watchex该key的所有客户端。

示例如下:

首先运行hiredis-example-ae,对应的源文件是example-ae.c

DSC0000.png

在另一个窗口中执行如下命令

DSC0001.png

可以看到在删除或者添加某个key时,在第一个窗口中都会收到通知

如果不想再watchex某个key,执行unwatchex key命令即可。

这个命令的实现原理其实有点类似redis 自身的pubsub机制,但是pubsub有一个局限就是,执行了该命令之后,就不能执行其他命令,只能等待channel上的信息。这种方式显然不适用于我们的场景。

我们的实现方式是,首先需要在client中保存一个所有watchex的list,然后在系统增加一个dict,用于保存每个被watchex的key。这个dict的键就是被watchex的key,值就是所有watchex这个key的client组成的一个链表。

无论在添加或者是删除某个key时,都去检查一下这个dict里面,有没有这个key。如果有,取出所有的client,发一份通知消息。

由于这个watchex这个命令,是一个典型的异步通知。所以在客户端调用这个命令时,要使用redis的异步执行命令接口redisAsyncCommand。具体调用方式,可以参考example-ae.c文件。

当然在客户端解析请求时,也要做一些变化。在async.c这个文件中,redisProcessCallbacks()这个函数专门解析服务器发回来的相应。每次从读缓冲区组装出一个redisreply结构,然后从redisCallbackList 里面取出头结点,其实就是一个回调函数,将redisreply传入到这个回调函数。这就是一次正常的调用过程。但是对于watchex命令,它是一个永久命令,故而不能回调函数不能插到redisCallbackList里面,所以另外建了一个dict用于保存watchex命令的回调函数,键是watchex命令的key,值即是回调函数。这样每次客户端解析出一个redisreply,首先判断这个reply是不是一个watchex命令的返回,如果是就从dict里面获取相应的回调函数,否则执行原有的解析流程。

整个过程即是如此,那么下面我们说一下在此基础上实现分布式锁的过程

首先,调用settp key "value"命令,如果返回成功,则说明获取锁成功;否则调用watchex key命令。由于这两步操作不是原子的,所以有可能调用watchex命令之后,返回noexist ,那么这时可以再尝试调用settp命令。如果还返回失败,说明锁已经被其他人占有,调用者可以等待或者干别的事。 当占有锁的人,用完释放之后,所有watchex这个key的client都会收到通知,这时所有client都会调用settp命令去抢锁,只会有一个人成功,其余的则继续等待,直到能抢占到锁为止。

从这个过程中,可以看出,这种实现方式会有“惊群”的问题,即通知了所有人,只有一个人能抢到锁,就会导致很多的无效操作。当然,也可以选择在key被释放时,只通知某一个client。但是由于redis的回复消息是没有确认机制的,如果这个通知消息丢失了,就可能导致其他所有的client一直等待下去。目前,还没有更好的解决方法,暂时先选择通知所有的client,如果大家有更好的方案,欢迎留言讨论。


文章中所讨论的实现,基于redis3.2.5版本,已经开源在github,地址是https://github.com/myd620/redis-dislock

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-426149-1-1.html 上篇帖子: linux下搭建redis并解决无法连接redis的问题 下篇帖子: 深入浅出Redis-Spring整合Redis
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表