【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案
序言
setnx、Redisson、RedLock 都可以实现分布式锁,从易到难得排序为:setnx < Redisson < RedLock。一般情况下,直接使用 Redisson 就可以啦,有很多逻辑框架的作者都已经考虑到了。
方案一:setnx
1.1、简单实现
下面的锁实现可以用在测试或者简单场景,但是它存在以下问题,使其不适合用在正式环境。
- 锁可能被误删: 在解锁操作中,如果一个线程的锁已经因为超时而被自动释放,然后又被其他线程获取到,这时原线程再来解锁就会误删其他线程的锁。
- **临界区代码不安全:**线程 A 还没有执行完临界区代码,锁就过期释放掉了。线程 B 此时又能获取到锁,进入临界区代码,导致了临界区代码非串行执行,带来了线程不安全的问题。
public class RedisLock {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 加锁
*/
private boolean tryLock(String key) {
Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return BooleanUtil.isTrue(flag);
}
/**
* 解锁
*/
private void unlock(String key) {
redisTemplate.delete(key);
}
}
1.2、使用 lua 脚本加锁、解锁
lua 脚本是原子的,不管写多少 lua 脚本代码,redis 都是通过一条命令去执行的。
下述代码使用了 lua 脚本进行加锁/解锁,保证了加锁和解锁的时候都是原子性的,是一种相对较好的 Redis 分布式锁的实现方式。
它支持获得锁的线程才能释放锁,如果线程 1 因为锁过期而丢掉了锁,然后线程 2 拿到了锁。此时线程 1 的业务代码执行完以后,也无法释放掉线程 2 的锁,解决了误删除的问题。
public class RedisLock {
private final StringRedisTemplate redisTemplate;
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean tryLock(String lockKey, String lockValue, long expireTimeInSeconds) {
try {
//加锁成功返回 true,加锁失败返回 fasle。效果等同于 redisTemplate.opsForValue().setIfAbsent
String luaScript = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end";
RedisScript redisScript = new DefaultRedisScript(luaScript, Long.class);
Long result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue, String.valueOf(expireTimeInSeconds));
return result != null & result == 1;
} catch (Exception e) {
// Handle exceptions
return false;
}
}
public void unlock(String lockKey, String lockValue) {
try {
//拿到锁的线程才可以释放锁,lockValue 可以设置为 uuid。
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript redisScript = new DefaultRedisScript(luaScript, Long.class);
redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
} catch (Exception e) {
// Handle exceptions
}
}
}
方案二:Redisson
Redisson 是一个基于 Java 的客服端,通过 Redisson 我们可以快速安全的实现分布式锁。Redisson 框架具有可重入锁的支持、分布式锁的实现、锁的自动续期、红锁支持等多种特点,给我们开发过程中带来了极大的便利。
@Component
public class RedisLock {
@Resource
private RedissonClient redissonClient;
/**
* lock(), 拿不到lock就不罢休,不然线程就一直block
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* leaseTime为加锁时间,单位为秒
*/
public RLock lock(String lockKey, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return null;
}
/**
* timeout为加锁时间,时间单位由unit确定
*/
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* @param lockKey 锁 key
* @param unit 单位
* @param waitTime 等待时间
* @param leaseTime 锁有效时间
* @return 加锁成功? true:成功 false: 失败
*/
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* unlock
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
if (lock.isLocked() & lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* unlock
* @param lock 锁
*/
public void unlock(RLock lock) {
lock.unlock();
}
}
方案三:RedLock
RedLock 又叫做红锁,是 Redis 官方提出的一种分布式锁的算法,红锁的提出是为了解决集群部署中 Redis 锁相关的问题。
比如当线程 A 请求锁成功了,这时候从节点还没有复制锁。此时主节点挂掉了,从节点成为了主节点。线程 B 请求加锁,在原来的从节点(现在是主节点)上加锁成功。这时候就会出现线程安全问题。
下图是红锁的简易思路。红锁认为 (N / 2) + 1 个节点加锁成功后,那么就认为获取到了锁,通过这种算法减少线程安全问题。简单流程为:
- 顺序向五个节点请求加锁
- 根据一定的超时时间判断是否跳过该节点
- (N / 2) + 1 个节点加锁成功并且小于锁的有效期
- 认定加锁成功
@Service
public class MyService {
private final RedissonClient redissonClient;
@Autowired
public MyService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public void doSomething() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
redLock.lock();
try {
// 业务逻辑
} finally {
redLock.unlock();
}
}
}
总结
- 自己玩或者测试的时候使用方案一的简单实现。
- 单机版 Redis 使用方案二。
- Redis 集群使用方案三。
最后
我是 xiucai,一位后端开发工程师。
如果你对我感兴趣,请移步我的个人博客,进一步了解。
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于个人博客,未经许可禁止转载💌
【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案【Java】三种方案实现 Redis 分布式锁 三种 Redis 分布式锁的实现方案