如何优雅地用Redis实现分布式锁?
什么是分布式锁
在学习Java多线程编程的时候,锁是一个很重要也很基础的概念,锁可以看成是多线程情况下访问共享资源的一种线程同步机制。这是对于单进程应用而言的,即所有线程都在同一个JVM进程里的时候,使用Java语言提供的锁机制可以起到对共享资源进行同步的作用。如果分布式环境下多个不同线程需要对共享资源进行同步,那么用Java的锁机制就无法实现了,这个时候就必须借助分布式锁来解决分布式环境下共享资源的同步问题。分布式锁有很多种解决方案,今天我们要讲的是怎么使用缓存数据库Redis来实现分布式锁。
实现思路
将一个key作为锁,获取锁的时候,检查该key是否有值存在,如果存在说明锁已经被持有,如果不存在,则获取锁,并给该key设置一个随机值,业务代码执行完毕之后,将该key从缓存中淘汰,即锁释放。
实现需要注意的点:
1.锁的获取需要是原子性的
原因:当一个客户端检查到某个锁不存在,并在执行setKey方法之前,别的客户端可能也会检查到该锁不存在,并也会执行setKey方法,这样一来,同一把锁就有可能被不同的客户端获取到了。该命令为:
SET my_key my_value NX PX milliseconds
jedisCluster.set(key, value, "NX", "EX", expireSeconds); // SET IF NOT EXIST,而且还是原子的操作成功,返回“OK”,否则返回null。
2.释放锁操作需要保证原子性
原因:一般我们认为释放锁操作就是直接delete key即可,但实际并非这样,假如我们的业务代码时间执行较长,该key到了过期时间自动被删除,而此时有其它客户端获取了锁,然后业务代码执行完毕,进行锁的删除,那么就有可能删除了其它客户端的锁,因此删除操作应该先判断该key对应的值是不是自己之前设置的,如果是,再进行删除,如果不是,不删除。然而此时的删除操作依然不是原子性的,我们还需要保证判断key值是否相同与删除key这两布操作为原子性的。
实现:Lua脚本实现。Lua脚本的原子性,在Redis执行该脚本的过程中,其他客户端的命令都需要等待该Lua脚本执行完才能执行。因此使用Lua脚本实现上述代码即可。
参考链接:如何优雅地用Redis实现分布式锁?