作者今天带来了一篇关于Redis锁的文章,竟然把这篇文章打码了。还有一些细节,对Redis锁还不清楚的朋友不妨看看。如果是有经验的朋友挑剔,那笔者就更感激不尽了!闲话不多说,我们马上发动汽车。说起Redis的锁,下面三个是出现频率最高的高频词:SetnxRedLockRedissonSetnx目前通常所说的setnx命令,不仅仅指的是Redis的setnxkeyvalue命令。一般指Redis中Set命令加上NX参数的使用。Set命令目前支持这么多可选参数:SETkeyvalue[EXseconds|PXmilliseconds][NX|XX][KEEPTTL]当然不会在文章里默默的写API没了,基本参数还不清楚,所以你可以跳转到官方网站。上图是作者画的Setnx的大致原理,主要是依据Key不存在Set才能成功。进程A获得锁。当锁的Key没有被删除时,进程B自然获取不到锁。那么为什么要用PX30000来设置超时呢?恐怕流程A不合理。锁还没有被释放。如果崩溃了,锁会被原地拿走,这样系统中就没人能拿到锁了。即便如此,也不能保证万无一失。如果进程A不合理,超过作者设置的超时时间操作锁中的资源,就会导致其他进程获取到锁。当进程A回来时,反击就是删除其他进程的锁,如图:或者刚才的图中,T5时间改成了锁超时,由Redis释放。进程B在T6愉快地获得了锁。没过多久,进程A完成操作,返回Del,释放锁。当进程B运行完成释放锁时(图中时间T8):如果找不到锁也不错。如果进程C在时间T7过来并成功锁定,则进程B将释放进程C释放的锁。以此类推,进程C可能会释放进程D的锁,进程D...(banningdolls),具体后果未知。所以,在使用Setnx的时候,虽然Key是主要功能,但是Value也不能闲着。您可以设置唯一的客户端ID,或使用随机数,例如UUID。解锁时先获取Value判断是否为当前进程加锁,然后删除。伪代码:Stringuuid=xxxx;//伪代码,具体实现见项目中使用的连接工具//方法有的叫set,有的叫setIfAbsentsetTestuuidNXPX3000try{//bizhandle....}finally{//unlockif(uuid.equals(redisTool.get('Test')){redisTool.del('Test');}}这次看起来是不是稳定了?相反,这次问题更明显了。在finally代码中block、get、del都不是原子操作,还是存在进程安全问题,为什么会出现这么多问题呢?原因有二:只有了解了缺点,才能更好的改进。上面最后一段代码,还是有很多公司在大小项目悖论:大公司执行标准,小公司小项目不严谨,但并发不高,出问题的概率和大公司一样低。那么其中之一删除锁的正确姿势,即可以使用lua脚本运行throughredis的eval/evalsha命令:--luadeletelock:--KEYS和ARGV是一组传入的参数,对应上面的Test和uuid。--如果对应的值等于传入的uuid.ifredis.call('get',KEYS[1])==ARGV[1]then--执行删除操作returnredis.call('del',KEYS[1])else--unsuccessful,return0return0endLua脚本能保证原子性的原因说的比较通俗一点:就算你用Lua写花,执行也是一个命令执行(eval/evalsha)。如果一个命令不执行,其他客户端会看到No。那么既然这么麻烦,有没有更好的工具呢?让我们谈谈Redisson。在介绍Redisson之前,笔者简单解释一下为什么现在的Setnx默认是带NX参数的Set命令,而不是直接说是Setnx命令。因为Redis是2.6.12之前的版本,Set不支持NX参数。如果要完成加锁,需要两条命令:1.setnxTestuuid2.expireTest30是放Key和设置有效期,是分开的理论上1会有2个步骤,刚结束程序就挂了执行,原子性无法保证。但早在2013年,也就是7年前,Redis就发布了2.6.12版本,官网(Setcommandpage)也早早表示“SETNX、SETEX、PSETEX可能在未来的版本中被弃用并永久删除””。笔者曾经看过一篇大佬的文章,里面有一个引导初学者的面试小套路。可以给Set命令加上参数,可以体现自己的见识。如果你看过这篇文章,学会了这个套路,作为本文的作者,我想补充一句:请注意你的工作年限!先回答官网提示即将废弃的命令,再介绍一下七年前的Set命令的“新特性”。如果刚毕业的人这么说,面试官会认为他穿越了。你骗面试官,面试官也会骗你。--vt·WozkiSchottRedissonRedisson是Java的Redis客户端之一,它提供了一些API方便对Redis的操作。但是Redisson客户端有点强大。作者只截取了官网的部分图片:这个功能列表可以说是太多了。是不是在JUC包下也看到了一些类名?Redisson帮助我们创建了一个分布式版本。比如AtomicLong,直接用RedissonAtomicLong就可以了,连类名都不用记,非常人性化。该锁只是冰山一角,从它的Wiki页面来看,它支持主从、哨兵、集群等多种模式。当然单节点模式肯定是支持的。本文还是以锁为主,其他的就不做过多介绍了。Redisson的常用锁实现源码主要是RedissonLock类。没有看过其源码的朋友不妨看看。源码中的加锁/解锁操作都是用Lua脚本完成的,封装的很完美,开箱即用。这是一个小细节。可以使用Setnx实现锁定。不用lua脚本吗?作者也想得很严谨:这么厉害的东西怎么会写出废代码呢?其实笔者仔细看了一下,发现加锁和解锁的Lua脚本非常全面,包括锁的重入。ReentrantLock和ReentrantLock一样流畅,RedLock是什么,因为Redisson已经实现的这么好了?RedLock中文直译为RedLock,简称红锁。Redlock不是一个工具,而是Redis官方提出的一种分布式锁算法。在刚刚介绍的Redisson中,实现了RedLock版本的锁。也就是说除了getLock方法之外,还有getRedLock方法。笔者粗略画出一个对红锁的理解:如果你对Redis高可用部署不熟悉,那也没关系。RedLock算法虽然需要多个实例,但是这些实例都是独立部署的,没有主从关系。RedLock的作者指出,之所以使用independent是为了避免Redis异步复制带来的锁丢失。比如主节点没来,把刚刚设置的数据给从节点,它就会挂掉。是不是有人觉得老板们都是胡说八道,天天想着极端的情况。其实对于高可用来说,拼写就是99.999...%中小数点后的位数。回到上面这张简单的图,红锁算法认为只要成功锁定了2N+1个节点,就认为已经获取到锁,解锁时所有实例都会被解锁。流程是:依次向5个节点请求锁,根据一定的超时时间判断是否跳过该节点。三个节点成功加锁且耗时小于锁的有效期。确定锁定成功。即假设锁在30秒后过期,三个节点加锁用了31秒,自然加锁失败。这只是一个例子。其实不应该每个节点都等那么久。正如官网所说,假设有效期为10秒,那么单次Redis实例操作的超时时间应该是5到50毫秒(注意时间单位)。还是假设我们设置有效期为30秒,图中有两个Redis节点超时了。然后总共用了3秒才成功加锁节点,所以锁的实际有效期不到27秒。即扣除3次加锁成功实例的3秒,同时扣除Redis实例等待超时的总时间。看到这里,很有可能你对这个算法有一些疑惑,那么你并不孤单。回头看Redis官网对RedLock的描述。在此描述页面的底部,您可以看到有关RedLock的著名仙女大战。即MartinKleppmann和Antirez的RedLock辩论。一位是高素质的分布式架构师,一位是Redis之父。最致命的是官方挂机。开个玩笑,如果问题能被官方发到官网,那肯定是有价值的。所以如果你想在你的项目中使用红锁,除了红锁的介绍之外,不妨多看两篇文章,分别是:MartinKleppmann的质疑贴Antirez的反击贴看了这么多,不保你发现了如何实现它100%稳定。程序就是这样,没有绝对的稳定,所以做好人工补偿也是很重要的一环。毕竟:技术不行,还需要人工!
