前言
查看了不少关于redis实现分布式锁的文章,无疑要设计一个靠谱的分布式并不太容易,总会出现各种鬼畜的问题;现在就来小述一下,在设计一个分布式锁的过程中,会遇到一些什么问题
I. 背景知识
借助redis来实现分布式锁(我们先考虑单机redis的模式),首先有必要了解下以下几点:
单线程模式
setnx : 当不存在时,设置value,并返回1; 否则返回0
getset : 设置并获取原来的值
expire : 设置失效时间
get : 获取对应的值
del : 删除
ttl : 获取key对应的剩余时间,若key没有设置过超时时间,或者压根没有这个key则返回负数(可能是-1,-2)
watch/unwatch : 事务相关
II. 方案设计
1. 设计思路
获取锁:
调用 setnx 尝试获取锁,如果设置成功,表示获取到了锁
设置失败,此时需要判断锁是否过期
未过期,则表示获取失败;循环等待,并再次尝试获取锁
已过期,getset再次设置锁,判断是否获取了锁(根据返回的值进行判断,后面给出具体的方案)
若失败,则重新进入获取锁的逻辑
释放锁:
一个原则就是确保每个业务方释放的是自己的锁
2. getset的实现方案
网上一种常见的case,主要思路如下
setnx 尝试获取锁
失败,则 get 获取锁的value (一般是 uuid_timstamp)
判断是否过期,若没有过期,则表示真的获取失败
若过期,则采用 getset设置,尝试获取锁
实现代码如下
public class DistributeLock {
private static final Long OUT_TIME = 30L;
public String tryLock(Jedis jedis, String key) {
while (true) {
String value = UUID.randomUUID().toString() + "_" + System.currentTimeMillis();
Long ans = jedis.setnx(key, value);
if (ans != null && ans == 1) { // 获取锁成功
return value;
}
// 锁获取失败, 判断是否超时
String oldLock = jedis.get(key);
if (oldLock == null) {
continue;
}
long oldTime = Long.parseLong(oldLock.substring(oldLock.lastIndexOf("_") + 1));
long now = System.currentTimeMillis();
if (now - oldTime < OUT_TIME) { // 没有超时
continue;
}
String getsetOldVal = jedis.getSet(key, value);
if (Object