场景:项目要从单实例变为多实例部署了,从而引入了一个问题,就是定时任务会多次执行(没一个实例都会执行一次)。为了解决这个问题,就要使用到分布式锁。
使用哪一种方式实现分布式锁
分布式锁的实现有多种:
- 第一种:使用
数据库
,当要上锁时想数据库中添加一条数据,释放锁时删除数据即可。这种方式要维护一张存放锁状态的表。而且性能不好,不推荐使用。 - 第二种:使用
zookeeper
来实现,zookeeper 是分布式协调服务,它的数据是以节点的方式存储,节点操作是原子性的。当我们要上锁时,添加一个临时节点,释放锁时删除这个节点。 - 第三种:使用
redis
来实现。redis 中的 SETNX 命令是原子性的,命令可以尝试将一个指定的键设置为某个值,只有当该键不存在时才能设置成功。
因为我在项目中没有使用到 zookeeper ,所以不用为了实现分布式锁而去部署一套 zookeeper。 而数据库方式有性能问题,最终选择了使用 redis 方式实现。
创建 RedisDistributedLock 工具类
SpringBoot项目使用的 redisTemplate 来操作 redis,具体代码如下:
java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class RedisDistributedLock {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public boolean lock(String lockKey, String lockValue, long expireTime) {
return redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue,expireTime, TimeUnit.MILLISECONDS);
}
public void unLock(String lockKey, String lockValue) {
String currentValue = redisTemplate.opsForValue().get(lockKey);
if (currentValue != null && currentValue.equals(lockValue)) {
String luaScript = "if redis.call('get', KEYS[1]) then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(lockKey));
}
}
}
lock 方法中的 lockKey 为锁的对象,lockValue 是为释放锁时会用到,防止释放了别人的锁,expireTime 是为了防止死锁。
unlock 方法中使用了 lua 脚本,确保正确的释放锁。
END
PS:欢迎大家关注我的公众号 小城边AI
,直接搜索即可添加,可以体验AI问答,持续为大家推送相关优质技术文,共同进步,一起加油~
个人导航网站:小城边-个人导航 (gitee.io)