说到redis锁,下面三个是出现频率最高的高频词:setnxredLockredissonsetnx其实目前普遍所说的setnx命令并不仅仅是指setnxredis订单的键值。一般是指redis中set命令加上nx参数的使用。set命令目前支持这么多可选参数:SETkeyvalue[EXseconds|PXmilliseconds][NX|XX][KEEPTTL]当然,只是Api没写在文章里,基本的参数还是不清楚,所以你可以跳到官方网站。上图是作者画的setnx的大致原理,主要是靠key不存在set才能成功的特性。进程A获得锁。当锁的key没有被删除时,进程B自然无法获得锁。那么为什么要用PX30000来设置超时呢?怕是进程A不通,一直没有释放锁。如果崩溃了,java训练会把锁拿走,系统内谁也拿不到锁。即便如此,也不能保证万无一失。如果进程A不合理,超过作者设置的超时时间操作锁中的资源,就会导致其他进程获取到锁。当进程A回来,反击就是删除其他进程的锁,如图:还是刚才的图,把T5时间改成了锁超时,由redis释放。进程B在T6愉快地获得了锁。没过多久,进程A完成了操作,返回了一个del,释放了锁。当进程B运行完成释放锁时(图中时间T8):如果找不到锁也不错。如果进程C在时间T7过来并成功锁定,则进程B释放进程C释放的锁。以此类推,进程C可能会释放进程D的锁,进程D....(娃娃禁止),具体后果未知。所以在使用setnx的时候,虽然key是main函数,但是value不能闲置。您可以设置唯一的客户端ID,或使用随机数,例如UUID。解锁时先获取值判断是否是当前进程加的锁,然后删除。伪代码:Stringuuid=xxxx;//伪代码,具体实现取决于项目中使用的连接工具//有的方法叫set,有的叫setIfAbsentsetTestuuidNXPX3000try{//bizhandle。...}finally{//unlockif(uuid.equals(redisTool.get('Test')){redisTool.del('Test');}}这次看起来是不是稳定了?反倒是这次的问题比较明显的是,finally代码块中,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脚本能保证原子性的原因很简单:即使你用luaFlower写,执行也是一个命令(eval/evalsha)来执行。如果一个命令没有被执行,其他客户端就看不到它。那么既然这么麻烦,有没有更好的工具呢?再说说redisson。在介绍redisson之前,笔者先简单说明一下为什么现在的setnx默认使用带nx参数的set命令,而不是直接使用setnx命令。因为redis是2.6.12之前的版本,所以set不支持nx参数。如果要完成加锁,需要两条命令:setnxTestuuidexpireTest30就是放入Key和设置有效期,这是两个独立的步骤。理论上,1执行完程序就会挂掉,原子性无法保证。但早在2013年,也就是7年前,Redis就发布了2.6.12版本,官网(setcommandpage)也早早表示“SETNX、SETEX、PSETEX可能在未来的版本中被弃用并永久删除”.笔者曾看过一篇大佬的文章,里面有一个引导初学者的面试小套路,具体文字忘记了,大意如下:说到redis锁,可以从setnx入手,以及finallylead可以给set命令加上参数,可以体现自己的见识。如果你看过这篇文章,学会了这个套路,作为本文的作者,我想补充一句:请注意你的工作年限!先回答官网提示即将废弃的命令,再引出七年前set命令的“新特性”。如果刚毕业的人这么说,面试官会认为他穿越了。你骗面试官,面试官也会骗你。--vt·WozkiSchoderedissonRedisson是java的redis客户端之一,它提供了一些API方便对redis的操作。但是redisson客户端有点强大。作者只截取了官网上的一部分图:这个功能列表可以说是太多了。是不是在JUC包下也看到了一些类名?Redisson帮我们创建了一个分布式版本的AtomicLong,比如AtomicLong,直接使用RedissonAtomicLong就可以了,连类名都不用记,非常人性化。该锁只是冰山一角,从它的wiki页面来看,它支持主从、哨兵、集群等多种模式。当然单节点模式肯定是支持的。本文还是以锁为主,其他的就不做过多介绍了。Redisson普通锁实现的源码主要是RedissonLock类。没有看过其源码的朋友不妨看看。源码中的加锁/释放加锁操作都是用lua脚本完成的,封装的很完美,开箱即用。这里有个小细节,加锁可以用setnx实现,难道不用lua脚本吗?作者也想得很严谨:这么厉害的东西怎么会写出废代码呢?其实笔者仔细看了一下,发现加锁和解锁的lua脚本非常全面,包括了锁的重入。ReentrantLock和ReentrantLock一样流畅,所以redisson的实现如此完美,什么是redLock?RedLock中文直译为redLock,简称红锁。红锁不是工具,而是redis官方提出的一种分布式锁算法。在刚刚介绍的redisson中,实现了redLock版本的锁。也就是说,除了getLock方法之外,还有getRedLock方法。笔者粗略画出一个对红锁的理解:如果你对redis高可用部署不熟悉,那也没关系。redLock算法虽然需要多个实例,但是这些实例都是独立部署的,没有主从关系。RedLock的作者指出,之所以使用independent是为了避免redis异步复制带来的锁丢失。比如主节点没来,把刚刚设置的数据给从节点,它就会挂掉。是不是有人觉得老板们都是胡说八道,天天想着极端的情况。其实对于高可用来说,拼写就是99.999...%中小数点后的位数。回到上面这张简单的图,红锁算法认为只要有(N/2)+1个节点成功加锁,就认为已经获取到锁,解锁时所有实例都会被解锁。流程是:依次向5个节点申请锁,根据一定的超时时间判断是否跳过该节点。三个节点成功加锁且耗时小于锁的有效期。确定锁定成功。即假设锁在30秒后过期,三个节点加锁用了31秒,自然加锁失败。这只是一个例子。其实不应该每个节点都等那么久。官网说的,假设有效期为10秒,那么单次redis实例操作的超时时间应该是5到50毫秒(注意时间单位)还是假设我们设置有效期为30秒,两个图中redis节点超时。然后总共用了3秒才成功加锁节点,所以锁的实际有效期不到27秒。即扣除3次加锁成功实例的3秒,同时扣除redis实例等待超时的总时间。看到这里,很有可能你对这个算法有一些疑惑,那么你并不孤单。回头看Redis官网对RedLock的描述。在此描述页面的底部,您可以看到有关RedLock的著名仙女大战。也就是MartinKleppmann和antirez之间的redLock之争。一个是合格的分布式架构师,一个是redis之父。
