redis分布式锁实现
方案一:
1、对key设值
2、如果不存在,设值成功,则获取锁成功,业务完成,释放锁
3、如果存在,则获取锁失败,等待锁或者放弃。
问题:
1、如果在锁获取成功后,业务异常,导致释放锁失败,就会导致所有业务都被阻塞
方案二:
1、在方案一的基础上,加入超时时间,为了防止获取锁之后挂掉导致超时时间设置失败,保证原子性,使用setnx命令问题
1、业务异常,随后超时,其他业务获取到锁,然后上一个业务又释放锁,锁机制失败
方案三:
1、加入业务id,是当前业务再进行释放,并且释放锁需要保证原子性,用lua脚本保证。
方案三为最终方案
流程图如下:
@RestController
@RequestMapping("/lock")
@Slf4j
public class LockController {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private DefaultRedisScript<Long> getRedisScript;
private static final String KEY = "LOCK";
@GetMapping("/lock")
public String lock(String requestId) {
Boolean lock = redisTemplate.opsForValue().setIfAbsent(KEY, requestId, 5, TimeUnit.SECONDS);
if (lock) {
return "success";
}
return "failed";
}
@GetMapping("/unlock")
public String unlock(String requestId) {
Long lock = redisTemplate.execute(getRedisScript, Arrays.asList(KEY), new Object[]{requestId});
if (lock > 0) {
return "success";
}
return "failed";
}
@PostConstruct
public DefaultRedisScript<Long> getscript() {
getRedisScript = new DefaultRedisScript<Long>();
getRedisScript.setResultType(Long.class);
getRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/lock.lua")));
return getRedisScript;
}
}
lua脚本
local key=KEYS[1];
local reqId =redis.call("get","LOCK")
if reqId==ARGV[1]
then
return 1;
end
return 0;