String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
return false;
其中:
· lockKey:锁的标识
· requestId:请求id
· NX:只在键不存在时,才对键进行设置操作。
· PX:设置键的过期时间为 millisecond 毫秒。
· expireTime:过期时间
由于该命令只有一步,所以它是原子操作。
7.3 释放锁接下来,有些朋友可能会问:在加锁时,既然已经有了lockKey锁标识,为什么要需要记录requestId呢?
答:requestId是在释放锁的时候用的。
if (jedis.get(lockKey).equals(requestId)) {
jedis.del(lockKey);
return true;
}
return false;
在释放锁的时候,只能释放自己加的锁,不允许释放别人加的锁。
这里为什么要用requestId,用userId不行吗?
答:如果用userId的话,假设本次请求流程走完了,准备删除锁。此时,巧合锁到了过期时间失效了。而另外一个请求,巧合使用的相同userId加锁,会成功。而本次请求删除锁的时候,删除的其实是别人的锁了。
当然使用lua脚本也能避免该问题:
if redis.call(\'get\', KEYS[1]) == ARGV[1] then
return redis.call(\'del\', KEYS[1])
else
return 0
end
它能保证查询锁是否存在和删除锁是原子操作。
7.4 自旋锁上面的加锁方法看起来好像没有问题,但如果你仔细想想,如果有1万的请求同时去竞争那把锁,可能只有一个请求是成功的,其余的9999个请求都会失败。
在秒杀场景下,会有什么问题?
答:每1万个请求,有1个成功。再1万个请求,有1个成功。如此下去,直到库存不足。这就变成均匀分布的秒杀了,跟我们想象中的不一样。
如何解决这个问题呢?
答:使用自旋锁。
try {
Long start = System.currentTimeMillis();
while(true) {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
long time = System.currentTimeMillis() - start;
if (time>=timeout) {
return false;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally{
unlock(lockKey,requestId);
}
return false;