最近在做一个抽奖活动的后台,代码是自动生成的,只需要做些修改。之前的公共代码用了Redis锁来防止并发情况,但是了解代码的过程中,发现代码有点问题。代码如下:
大家可以看到,在并发情况下,第一个获取锁的进程会被动延长锁的过期时间,后续的并发进程获取锁失败,但是却会重新设置锁的过期时间,直到超时,然后进入业务逻辑。也就是说,这个方法不能有效地阻止并发情况的发生。


再来看看网上常见的Redis锁方案,该方案使用setnx命令,setnx命令是原子操作,所以可以防止并发情况的发生,但是该方案有个弊端,如果获取锁的进程奔溃,那么该锁永远不会释放。同理,如果在获取到锁后,再加上有效时间,那么在获取锁之后,加上过期时间之前,进程奔溃,后果也是一样。一般解决方法是在setnx的时候将值设置为过期时间,则可以解决线程奔溃锁无法释放的问题。但是在这种情况下,假如A进程获取锁,并且超时,此时B进程与C进程并发,都判断A进程锁过期,B删除锁,并重新获取锁,C删除锁,并重新获取锁,可以看到,这个时候C把B的锁给删除了。所以这个方案也不能有效防止并发。

总的看来,如果获取锁并设置过期时间的操作不是原子操作,就不能防止各种并发情况的发生。好在,Redis已经想到这一点,在Redis官网,set命令有下面这些参数可以使用。NX就跟setnx一样,EX可以在set的时候加上过期时间。

最后再来看看如何使用set命令。在set的时候设置NX与EX,并且设置值为随机数。当A进程获取锁后,后续进程都无法获取锁。A进程业务逻辑完成后,删除锁,后续进程才能获取到锁。加上随机数,主要是为了防止A进程超时后,锁被后续进程获取,这个时候如果A进程删除锁,就会把后面的锁给删了。

删除锁也需要注意,先对比随机数是否一致,如果一致,再删除。加上watch命令,如果在删除过程中,发现key被修改,则删除失败。避免在A进程获取key值之后,删除key之前锁过期,而这个时候B进程拿到锁(修改key值),则A删除失败。

网友评论