关于分布式锁的概念,具体实现方式,直接参阅下面两个帖子,这里就不多介绍了。

分布式锁的多种实现方式

分布式锁总结

对于分布式锁的几种实现方式的优劣,这里再列举下

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

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

可以看到在删除或者添加某个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

使用redis构建可靠分布式锁的更多相关文章

  1. [翻译]利用REDIS来搭建可靠分布式锁的提议

    本系列都是翻译REDIS作者的博文  另外加上我自己的一点点理解  希望有问题大家一起讨论 http://antirez.com/news/77 原文地址 在利用REDIS做分布式锁时基本持有2种观点 ...

  2. Redis构建全局并发锁

    Redis构建全局并发锁 https://www.cnblogs.com/FG123/p/9990336.html 谈起Redis的用途,小伙伴们都会说使用它作为缓存,目前很多公司都用Redis作为缓 ...

  3. Redis系列(二)--分布式锁、分布式ID简单实现及思路

    分布式锁: Redis可以实现分布式锁,只是讨论Redis的实现思路,而真的实现分布式锁,Zookeeper更加可靠 为什么使用分布式锁: 单机环境下只存在多线程,通过同步操作就可以实现对并发环境的安 ...

  4. redis系列:分布式锁

    redis系列:分布式锁 1 介绍 这篇博文讲介绍如何一步步构建一个基于Redis的分布式锁.会从最原始的版本开始,然后根据问题进行调整,最后完成一个较为合理的分布式锁. 本篇文章会将分布式锁的实现分 ...

  5. Redis高并发分布式锁详解

    为什么需要分布式锁 1.为了解决Java共享内存模型带来的线程安全问题,我们可以通过加锁来保证资源访问的单一,如JVM内置锁synchronized,类级别的锁ReentrantLock. 2.但是随 ...

  6. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  7. 一个Redis实现的分布式锁

    import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.redis.conne ...

  8. 基于Redis的简单分布式锁的原理

    参考资料:https://redis.io/commands/setnx 加锁是为了解决多线程的资源共享问题.Java中,单机环境的锁可以用synchronized和Lock,其他语言也都应该有自己的 ...

  9. 在redis上实现分布式锁

    /** *在redis上实现分布式锁 */ class RedisLock { private $redisString; private $lockedNames = []; public func ...

随机推荐

  1. CMS模板应用调研问卷

    截止目前,已经有数十家网站与我们合作,进行了MIP化改造,在搜索结果页也能看到"闪电标"的出现.除了改造方面的问题,MIP项目组被问到最多的就是:我用了wordpress,我用了织 ...

  2. 戏说HTML5

    如果有非技术人员问你,HTML5是什么,你会怎么回答? 新的HTML规范... 给浏览器提供了牛逼能力,干以前不能干的事...(确切地说应该是给浏览器规定了许多新的接口标准,要求浏览器实现牛逼的功能. ...

  3. java EE设计模式简介

    1.何为设计模式 设计模式提供了对常见应用设计问题的解决方案.在面向对象的编程中,设计模式通常在解决与对象创建和交互相关的问题,而非整体软件架构所面对的大规模问题,它们以样板代码的形式提供了通用的解决 ...

  4. 初步了解nodejs

    什么是Node.js? 很多初学者并没有真正地理解Node.js到底是什么.nodejs.org网站中的描述也没有多大帮助. 首先要清楚Node不是一个Web服务器,这十分重要.它本身并不能做任何事情 ...

  5. 奇葩问题-TextView无法获取值

    问题场景 前几天写一个界面的时候,遇到一个非常奇葩的问题.app第一次安装的时候,这里针对用户第一次安装的时候,后来是不会出现这个问题了.我明明是对某个界面的一个textview赋值了,而且服务端也返 ...

  6. 你所能用到的BMP格式介绍

    原理篇: 一.编码的意义. 让我们从一个简单的问题开始,-2&-255(中间的操作符表示and的意思)的结果是多少,这个很简单的问题,但是能够写出解答过程的人并不 多.这个看起来和图片格式没有 ...

  7. 游走 bzoj 3143

    游走(2s 128MB)walk [问题描述] [输入格式] [输出格式] [样例输入] 3 3 2 3 1 2 1 3 [样例输出] 3.333 [样例说明] 题解: 主要算法:贪心:高斯消元: 题 ...

  8. Sass之坑Compass编译报错

    前段时间在使用Compass时遇到了其为难处理的一个坑,现记录到博客希望能帮助到各位. 一.问题: 利用Koala或者是gulp编译提示如下,截图为koala编译提示错误: 二.解决办法 从问题截图上 ...

  9. 在开源中国(oschina)git中新建标签(tags)

    我今天提交代码到主干上面,本来想打个标签(tags)的. 因为我以前新建过标签(tags),但是我现在新建的时候不知道入库在哪了.怎么找也找不到了. 从网上找资料也没有,找客服没有人理我,看到一个交流 ...

  10. Centos 7 vsftpd ldap 配置

    #ldap 安裝配置 环境Centos7#安装 yum install -y openldap openldap-clients openldap-servers migrationtools pam ...