分布式锁一般有三种实现方式:

  • 数据库乐观锁

  • 基于ZooKeeper的分布式锁

  • 基于Redis的分布式锁

这里主要记录下基于Redis的分布式锁

Redis加锁

springboot2.1以后的版本可以直接使用redisTemplate提供的setIfAbsent方法进行加锁 相当于使用redis命令:SET key value [EX seconds] [PX millisecounds] [NX|XX]

redisTemplate.opsForValue().setIfAbsent(key,value,time,TimeUnit)
  • 为什么是set命令而不是setNx命令?

因为setNx 无法设置key过期时间 需要通过expire来为key设置过期时间,意味着加锁是两条命令,不满足原子性。

  • 锁的过期时间设置多少合适,是否可以不设置?

锁的过期时间一定是要有的,不然留着过年么?过期时间根据具体的业务逻辑来设置,但是一定要大于代码执行的时间。例如:

	//加锁  锁的过期时间为5秒
Boolean lock = redisTemplate.opsForValue().setIfAbsent("111", "11", 5, TimeUnit.SECONDS);
if(lock){
System.out.println("业务逻辑执行在0-8秒范围内");
}

这个时候肯定是不合适的。

Redis解锁

  1. 加锁之后,一定要保证锁的释放,所以通常是在finally代码块里面释放锁。

  2. 获取到锁才释放锁,没有获取到,不要去释放锁,避免释放其他客户端加的锁。

  3. 释放锁的时候可以判断锁的持有者是否是自己,是自己的才进行释放。(2和3至少要遵循一个,这样才能避免误释放锁)

解锁方式一:

redisTemplate.delete(key)

解锁方式二:使用lua脚本

if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

完整的加锁解锁代码如下:

@Autowired
RedisTemplate redisTemplate;

@Test
public void testLock() throws UnknownHostException {
String key = "lockTest";
InetAddress ia = InetAddress.getLocalHost();
String value = ia.toString();
//加锁 锁的过期时间为20秒
Boolean lock = redisTemplate.opsForValue().setIfAbsent(key, value, 20, TimeUnit.SECONDS);
if (!lock) {
//未获取到锁,直接返回
return;
}
try {
System.out.println("业务逻辑执行在小于20秒范围内");
} catch (Exception e) {
System.out.println("业务错误信息");
} finally {
// 方式一 直接使用del
redisTemplate.delete(key);
// 方式二 使用lua脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript redisScript = new DefaultRedisScript(script);
List<String> keys = new ArrayList<>();
keys.add(key);
redisTemplate.execute(redisScript, keys, value);
}

}

 

END

JAVA 基于Redis的分布式锁_java