利用redis实现分布式锁
// 分布式锁实现使用的版本
// springBoot的版本:org.springframework.boot:spring-boot:1.5.8.RELEASE
// redis的版本:org.springframework.data:spring-data-redis:1.8.8.RELEASE
// lombok的版本:org.projectlombok:lombok:1.18.6
// 标识springBoot启动时会扫描该类并创建实例放入spring容器
@Component
// 引入lombokjar包,才能使用的注解
@Slf4j
public class DistributedLock {
private static final String LOCK_PREFIX = "REDIS_LOCK_";
//加锁失效时间,单位:秒
public static final int LOCK_EXPIRE = 3;
// 此处注入的RedisTemplate实例信息,可以查看Redis与SpringBoot融合这篇工具类博客
// 文章地址:https://carefulhuo.github.io/posts/c51e64d0/
@Autowired
RedisTemplate redisTemplate;
/**
* 尝试获取分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId) {
log.info("{}开始加锁,requestId:{}", lockKey, requestId);
String lock = LOCK_PREFIX + lockKey;
// 利用lambda表达式
// setNX英文全称:SET if Not Exists
// setNX含义:如果key存在,设置失败返回0;如果key不存在,设置成功返回1
// setNX方法:原子性的,但是该命令不能设置超时时间
// expire方法:设置超时时间,防止死锁
// 注意其中的RedisCallback,当redis有多个实例时,数据进行主从同步时,但主redis挂掉,随机选择一个从redis作为主redis时,不会发生多个请求获取到锁。
// 原理如下:加锁时,会向多半的节点发送setNX命令,如果多半节点成功,则算加锁成功,那么释放锁的时候,需要想所有节点发送del命令
return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
if (connection.setNX(lock.getBytes(), requestId.getBytes())) {
connection.expire(lock.getBytes(), LOCK_EXPIRE);
return true;
}
return false;
});
}
/**
* 释放分布式锁
*
* @param lockKey 锁
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String requestId) {
log.info("{}开始释放锁,requestId:{}", lockKey, requestId);
String lock = LOCK_PREFIX + lockKey;
final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end ";
// eval命令的参数(lua脚本,返回类型,脚本的参数个数,redis的key,redis的value)
// redis执行lua脚本是原子性的
return (Boolean)redisTemplate.execute((RedisCallback) connection -> {
return connection.eval(script.getBytes(), ReturnType.BOOLEAN ,1, lock.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
});
}
}
-- 上述释放redis锁需要redis执行的lua脚本
-- 代码含义:
-- if 中的比较如果是true , 那么 执行 del 并返回del结果;如果 if 结果为false 直接返回 0 。
if redis.call('get', KEYS[1]) == ARGV[1]
then
return redis.call('del', KEYS[1])
else
return 0
end