》在家办公的第N周,不知道作者工作站的键盘和显示器有没有想念我,以及不知道会不会灰尘太严重,被保洁阿姨??扔掉了图片来自pexels作者今天带来了一篇关于Redis锁的文章,这篇文章我连打码都打码了,有一些细节.对Redis锁还不清楚的朋友不妨看一看如果你是有经验的挑剔的朋友,那笔者就更感激了!闲话不多说,马上上车说到Redis锁,下面三个是出现频率最高的词:SetnxRedLockRedissonSetnx是目前比较常用的提到的setnx命令不仅仅指Redis的setnxkeyvalue命令,泛指使用set命令加上Redis中的NX参数,Set命令目前支持移植了那么多可选参数:SETkeyvalue[EXseconds|PXmilliseconds][NX|XX][KEEPTTL]当然,我不会在文章中默默地写API。如果对基本参数还不清楚,可以跳转到官网。上图是笔者画的Setnx的大致原理,主要是靠它的Key。有一个功能可以设置成功。进程A获取锁。当锁的key没有被删除时,进程B自然获取不到锁。那为什么要用PX30000来设置超时呢?是因为流程A不合理,锁还没有被释放。如果崩溃了,锁会被原地拿走,这样系统中就没人能拿到锁了。如果资源超过作者设置的超时时间,其他进程将获得锁。当进程A回来时,其他进程的锁也会被删除。锁超时,被Redis释放。在T6进程B愉快的拿到锁后不到一会,进程A完成操作,返回一个Del释放锁。当进程B完成操作并释放锁时(图中时间T8):找到锁就可以了。如果T7时刻有进程C过来加锁成功,则进程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脚本通过Redis的eval/evalsha命令运行:--luadeletelock:--KEYS和ARGV是一组传入的参数,对应上面的Test和uuid。--如果对应的值等于传入的uuid.ifredis.call('get',KEYS[1])==ARGV[1]then--执行删除操作returnredis.call('del',KEYS[1])else--不成功,返回0return0endLua脚本能保证原子性的原因说的比较通俗一点:就算你用Lua写花,执行也是一个命令执行(eval/evalsha)。如果一个命令不执行,其他客户端既然这么麻烦,有没有更好的工具呢?让我们谈谈Redisson。在介绍Redisson之前,笔者简单说明一下为什么现在的Setnx默认指的是带NX参数的Set命令,而不直接是Setnx命令。因为Redis是2.6.12之前的版本,Set不支持NX参数。如果要完成加锁,需要两条命令:1.setnxTestuuid2.expireTest30是放Key和设置有效期,这是两个独立的步骤。理论上1会出现在屏幕上,程序刚执行完,程序就挂了,原子性无法保证。但早在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脚本非常全面,包括锁的重入。这可以说是考虑周全了。我也写了代码测试了一下:确实和JDK的ReentrantLock一样流畅,所以Redisson实现RedLock已经这么完美了,什么是RedLock?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%稳定。程序就是这样,没有绝对的稳定,所以做好人工补偿也是很重要的一环。毕竟:如果技术不够,还需要人工!
