设计分布式锁应该考虑的东⻄:
排他性
在分布式应⽤集群中,同⼀个⽅法在同⼀时间只能被⼀台机器上的⼀个线程执⾏
容错性
分布式锁⼀定能得到释放,⽐如客户端奔溃或者⽹络中断
可重⼊、⾼性能、⾼可⽤
还需要注意分布式锁的开销、锁粒度
一般的写法:
1.加锁 SETNX key value
setnx 的含义就是 SET if Not Exists,有两个参数setnx(key, value),该方法是原子性操作,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0。
2.解锁 del (key)
得到锁的线程执⾏完任务,需要释放锁,以便其他线程可以进⼊,调⽤ del(key)
3配置锁超时 expire (key,30s)
客户端奔溃或者⽹络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在⼀定时间后⾃动释放.
String uuid = CommonUtil.generateUUID();
String lockKey = "lock:sku:"+skuId;
Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));
if(nativeLock){
//加锁成功
try {
//执⾏业务 TODO
}finally {
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Integer result = redisTemplate.execute(newDefaultRedisScript<>(script, Integer.class),Arrays.asList(lockKey), uuid);
}
}else {
//加锁失败,睡眠100毫秒,⾃旋重试
try {
TimeUnit.MILLISECONDS.sleep(100L);
} catch (InterruptedException e){}
return addSku( skuId,skuCategory);
}
保证查询操作和删除操作的原⼦性
一、使⽤lua脚本
1、在resources⽬录下创建unlock.lua,编辑脚本:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
2、配置Bean加载lua脚本
@Bean
public DefaultRedisScript<List> defaultRedisScript(){
DefaultRedisScript<List> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(List.class);
defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("unlock.lua")));
return defaultRedisScript;
}
3、通过执⾏lua脚本解锁。
@AutoWired
private DefaultRedisScript defaultRedisScript;
//执⾏lua脚本
List<String> keys = new ArrayList<>();
keys.add(skuId);
List rs = stringRedisTemplate.execute(defaultRedisScript,keys,values.get(skuId));
System.out.println(rs.get(0));
二、看⻔狗机制
看⻔⼝线程:⽤于给当前 key 延⻓过期时间,保证业务线程正常执⾏的过程中,锁不会过期。