【江南一点雨】的教程~ 分布式锁可以限制程序的并发执行:执行思路: 进来一个线程先占位,当别的线程进来操作时,发现已经有人占位了,就会放弃或者稍后再试。(不会抢占)
在Redis中,占位一般用setnx(没有才存,有就不存),先进来的线程先占位,线程的操作执行完成后,再调用del指令释放位置。
package org.kk.distributed_lock;
import org.kk.jedis.Redis;
public class LockTest {
public static void main(String[] args) {
Redis redis=new Redis();
redis.execute(jedis -> {
Long setnx=jedis.setnx("k1","v1");
if(setnx==1){
jedis.expire("k1",5);
jedis.set("name","value");
String name=jedis.get("name");
System.out.println(name);
jedis.del("k1");
}else{
}
});
}
}
改造后还有问题:如果在获取锁和设置过期时间之间服务器突然挂掉,锁还是被占用,无法及时得到释放,也会造成死锁,因为获取锁和设置过期时间是两个操作,不具备原子性。 Redis 2.8开始,setnx和expire可以通过一个命令来一起执行了,我们对上述代码进行改进:
package org.kk.distributed_lock;
import org.kk.jedis.Redis;
import redis.clients.jedis.params.SetParams;
public class LockTest {
public static void main(String[] args) {
Redis redis=new Redis();
redis.execute(jedis -> {
jedis.auth("123456");
String set = jedis.set("k1", "v1", new SetParams().nx().ex(5));
if(set!=null&&"OK".equals(set)){
jedis.set("name","value");
String name=jedis.get("name");
System.out.println(name);
jedis.del("k1");
}else{
}
});
}
}
解决超时问题
为了防止业务代码在执行时抛出异常,我们给每个锁添加了超时时间,超时以后,锁会被自动释放,但也带来了新问题:如果要执行的业务非常耗时,可能会出现紊乱。
第一个线程的任务还没执行成功锁就被释放,第二个线程会获取到锁开始执行…
- 尽量避免在获取锁之后执行耗时操作
- 将锁的value设置为随机字符串,每次释放锁的时候,都去比较随机字符串是否一致,如果一致再去释放。否则不释放。
对于第二种方案,释放锁的时候,先去查看锁的value,第二个比较value的值是否正确,第三步释放锁。有三个步骤,不具备原子性,所以得引入Lua脚本。
Lua脚本的优势
- 使用方便,Redis中内置了对Lua脚本的支持
- Lua脚本可以在Redis服务端原子的执行多个Redis命令
- 由于网络在很大程度上会影响到Redis性能,而使用Lua脚本可以让多个命令一次执行,可以有效解决网络给Redis带来的性能问题。
在Redis中,使用Lua脚本,大致两种思路: 4. 提前在Redis服务端写好脚本,然后Java客户端去调用脚本 5. 可以直接在Java段写Lua脚本,写好之后,需要执行时,将脚本发送到Redis中去 第一步:
if redis.call("get",KEYS[1])==ARGV[1] then
return redis.call("del",KEY[1])
else
return 0
end
逻辑:得到锁的值和传进来的一样,那么释放锁。
第二步 可以给Lua脚本求一个SHA1和,命令如下:
将lua脚本加载到缓存
cat releasewhereValueequal.lua |redis-cli -a 123456 script load --pipe
script load这个命令会在Redis服务器中缓存Lua脚本,并返回脚本内容的 SHA1校验和,然后在Java端调用时,传入SHA1校验和作为参数,这样redis服务端就知道执行哪个脚本了。
public class LuaTest {
public static void main(String[] args) {
Redis redis=new Redis();
redis.execute(jedis -> {
jedis.auth("123456");
String value = UUID.randomUUID().toString();
String k1 = jedis.set("k1", value, new SetParams().nx().ex(5));
if(k1!=null&&"OK".equals(k1)){
jedis.set("site","www.kk.com");
String site = jedis.get("site");
System.out.println(site);
jedis.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("k1"),Arrays.asList(value));
}else{
System.out.println("没拿到锁");
}
});
}
}
|