一、前言
分布式锁是一种用于协调分布式系统中多个节点之间对共享资源进行访问控制的机制。它可以确保在分布式环境下,同一时间只有一个节点能够获取到锁,并且其他节点需要等待释放锁后才能获取。
以下是使用分布式锁的几个常见场景和原因:
避免资源冲突:当多个节点需要同时对共享资源进行读写操作时,使用分布式锁可以确保同一时间只有一个节点能够执行写操作,避免数据冲突和一致性问题。
防止重复处理:在某些业务场景中,可能会出现重复处理的问题,例如订单支付、秒杀等。使用分布式锁可以确保同一时间只有一个节点能够处理该任务,避免重复处理和产生脏数据。
控制资源并发:某些资源的并发操作会导致性能问题,如数据库的并发写操作。使用分布式锁可以限制对资源的并发访问,提高系统的稳定性和性能。
避免死锁:在分布式环境下,由于网络延迟等原因,可能会发生死锁的情况。使用分布式锁可以避免死锁问题的发生,确保资源的正确释放。
二、使用redisTemplate.opsForValue().setIfAbsent
1获取锁:
客户端通过在Redis中设置一个特定的键,作为锁的标识,并设置过期时间以避免死锁情况。这个操作可以通过Redis的SETNX命令来实现。如果SETNX命令返回1,表示锁获取成功,客户端可以继续执行相应的业务逻辑;如果返回0,表示锁已经被其他客户端持有,客户端需要等待或进行重试操作。
可以通过Redis的SET命令设置锁的过期时间,以防止锁一直被持有而导致死锁。设置过期时间可以保证即使持有锁的客户端发生异常退出,锁也会在过期时间后自动释放。
2释放锁:
客户端完成业务操作后,通过DEL命令删除锁的键,即可释放锁。只有持有锁的客户端才能删除锁,以避免误删其他客户端的锁。
下面是一个示例:
@Component
public class DistributedLock {
private static final String LOCK_KEY = "my_lock";
private static final long EXPIRE_TIME = 30000; // 锁的过期时间,单位为毫秒
private static final long WAIT_TIME = 1000; // 获取锁时的等待时间,单位为毫秒
@Autowired
private StringRedisTemplate redisTemplate;
public boolean acquireLock() throws InterruptedException {
long start = System.currentTimeMillis();
while (true) {
Boolean success = redisTemplate.opsForValue().setIfAbsent(LOCK_KEY, "locked", EXPIRE_TIME, TimeUnit.MILLISECONDS);
if (success != null && success) {
return true;
}
long current = System.currentTimeMillis();
if (current - start > WAIT_TIME) {
return false;
}
Thread.sleep(100); // 等待一段时间后进行重试
}
}
public void releaseLock() {
redisTemplate.delete(LOCK_KEY);
}
}
在上述代码中,acquireLock方法尝试获取分布式锁,如果成功获取,则返回true;如果超过等待时间仍未获取到锁,则返回false。releaseLock方法用于释放锁。
使用setIfAbsent的缺点
使用redisTemplate.opsForValue().setIfAbsent()方法实现分布式锁存在以下缺点:
可靠性问题:使用redisTemplate.opsForValue().setIfAbsent()方法实现分布式锁时,需要手动编写代码来处理锁的获取和释放,容易出现人为的错误,如忘记释放锁、锁的过期时间设置不正确等。而Redisson框架提供了更加可靠的分布式锁实现,内部封装了各种功能的锁,并提供了易于使用的API,能够确保锁的可靠性和正确性。
功能限制:redisTemplate.opsForValue().setIfAbsent()方法只能实现简单的锁功能,无法支持更复杂的功能,如可重入锁、公平锁、红锁和读写锁等。而Redisson框架提供了丰富的分布式锁实现方式,可以根据实际需求选择适用的锁类型。
性能问题:redisTemplate.opsForValue().setIfAbsent()方法实现分布式锁时,每次都需要与Redis服务器进行通信,可能会造成较高的网络开销和延迟。而Redisson框架通过内部的优化和封装,能够提供更高效的分布式锁实现,减少与Redis服务器的通信次数和网络开销。
可拓展性问题:使用redisTemplate.opsForValue().setIfAbsent()方法实现分布式锁时,随着业务的发展和变化,可能需要添加更多的功能和特性,而手动编写的代码可能无法满足新的需求。而Redisson框架提供了丰富的锁实现,同时也支持自定义锁的扩展,能够更好地适应业务的变化和拓展。
三、使用redisson实现分布式锁
通过Redisson框架可以方便地实现分布式锁。Redisson是一个基于Redis的分布式Java对象和服务框架,提供了丰富的分布式锁的实现方式。
要使用Redisson实现分布式锁,需要完成以下步骤:
1 引入Redisson依赖:在项目的pom.xml文件中添加Redisson的依赖。
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.14.0</version>
</dependency>
2 创建RedissonClient对象:在Spring Boot中,可以通过Redisson的Spring支持来创建RedissonClient对象。
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
return Redisson.create(config);
}
}
在上述代码中,创建了一个RedissonClient对象,并配置了连接Redis的地址。
3 实现分布式锁:
使用RedissonClient对象获取RLock对象,RLock是Redisson提供的分布式锁接口。
通过RLock对象的lock方法来获取锁,并在获取锁成功后执行业务逻辑。
通过RLock对象的unlock方法来释放锁。
下面是一个示例:
@Service
public class DistributedLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithLock() {
RLock lock = redissonClient.getLock("my_lock");
try {
lock.lock();
// 执行业务逻辑...博客原文:https://blog.csdn.net/qq_27471405/article/details/134109185
} finally {
lock.unlock();
}
}
}
在上述代码中,executeWithLock方法通过redissonClient获取了一个名为"my_lock"的锁,并通过lock方法获取锁。在获取锁成功后,可以执行业务逻辑。最后,通过unlock方法释放锁。
Redisson还提供了其他一些功能强大的分布式锁实现方式,如可重入锁、公平锁、红锁、读写锁等。这些锁的实现方式更加灵活和强大,可以根据实际需求进行选择和使用。
使用Redisson实现分布式锁时,需要确保Redis服务器的可用性和稳定性,以避免单点故障导致的锁失效或锁的不稳定情况。此外,还需要根据具体的应用场景和需求,合理设置锁的过期时间,避免锁的长时间占用。
- 可重入锁(Reentrant Lock):
可重入锁是指同一个线程可以多次获得同一个锁,而不会发生死锁。Redisson的可重入锁实现是基于Redis的分布式锁的一种特例。
@Service
public class ReentrantLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithReentrantLock() {
RLock lock = redissonClient.getLock("my_lock");
try {
lock.lock();
// 执行业务逻辑...小小鱼儿小小林的博客测试
executeWithReentrantLock();
} finally {
lock.unlock();
}
}
}
在上述代码中,使用redissonClient获取了一个名为"my_lock"的可重入锁,并通过lock方法获取锁。在获取锁成功后,可以执行业务逻辑,包括递归调用executeWithReentrantLock方法。最后,通过unlock方法释放锁。
- 公平锁(Fair Lock):
公平锁是指按照线程请求锁的顺序来分配锁。Redisson的公平锁实现可以保证多个线程按照先后顺序获取锁。
@Service
public class FairLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithFairLock() {
RLock lock = redissonClient.getFairLock("my_lock");
try {
lock.lock();
// 执行业务逻辑...小小鱼儿小小林的博客测试
} finally {
lock.unlock();
}
}
}
在上述代码中,使用redissonClient获取了一个名为"my_lock"的公平锁,并通过lock方法获取锁。在获取锁成功后,可以执行业务逻辑。最后,通过unlock方法释放锁。
- 红锁(Red Lock):
红锁是指在多个Redis节点上获取锁,以提高分布式系统的可靠性和容错性。Redisson的红锁实现是基于Redis的分布式锁的一种优化方式。
@Service
public class RedLockService {
@Autowired
private RedissonClient redissonClient;
public void executeWithRedLock() {
RLock lock1 = redissonClient.getLock("lock1");
RLock lock2 = redissonClient.getLock("lock2");
RLock lock3 = redissonClient.getLock("lock3");
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
redLock.lock();
// 执行业务逻辑...
} finally {
redLock.unlock();
}
}
}
在上述代码中,使用redissonClient分别获取了名为"lock1"、"lock2"和"lock3"的锁,并通过RedissonRedLock将这些锁组合成红锁。在获取红锁成功后,可以执行业务逻辑。最后,通过unlock方法释放红锁。
- 读写锁(ReadWrite Lock):
读写锁是指在多线程环境下,对于读操作可以并行进行,对于写操作必须互斥进行。Redisson的读写锁实现提供了读锁和写锁两种操作。
@Service
public class ReadWriteLockService {
@Autowired
private RedissonClient redissonClient;
public void readWithReadWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
RLock readLock = rwLock.readLock();
try {
readLock.lock();
// 执行读操作...
} finally {
readLock.unlock();
}
}
public void writeWithReadWriteLock() {
RReadWriteLock rwLock = redissonClient.getReadWriteLock("my_lock");
RLock writeLock = rwLock.writeLock();
try {
writeLock.lock();
// 执行写操作...
} finally {
writeLock.unlock();
}
}
}
在上述代码中,使用redissonClient获取了一个名为"my_lock"的读写锁,并通过readLock方法获取读锁,通过writeLock方法获取写锁。在获取锁成功后,可以执行相应的读操作或写操作。最后,通过unlock方法释放锁。
四、遇到redis单点故障怎么办
当使用Redisson实现分布式锁时,如果遇到Redis服务器的单点故障,可以采取以下解决方案:
1 Redis Sentinel(哨兵模式):Redis Sentinel是Redis官方提供的高可用性解决方案,它通过监控Redis主节点和从节点的状态,实现自动故障转移和故障恢复。在使用Redisson时,可以配置Redis Sentinel来实现高可用性的Redis集群,在主节点故障时,Redis Sentinel会自动将从节点切换为主节点,从而保证分布式锁的可用性。
2 Redis Cluster(集群模式):Redis Cluster是Redis官方提供的分布式解决方案,通过将数据分散到多个节点上进行存储和访问,实现高可用性和横向扩展。使用Redisson时,可以配置Redis Cluster来搭建分布式锁的集群,当某个节点出现故障时,其他节点仍然可以正常工作,确保分布式锁的可用性。
3 使用RedLock算法:RedLock算法是由Redis官方提出的一种多实例锁机制,通过在多个独立的Redis实例之间获取锁,确保锁的可靠性。在使用Redisson时,可以使用RedLock算法来实现分布式锁,通过协调多个Redis实例之间的锁获取和释放,即使部分实例发生故障,仍然可以保证锁的可用性。
4 引入其他高可用的中间件:除了Redis本身的高可用性解决方案,也可以考虑引入其他高可用的中间件,如ZooKeeper、etcd等。在使用Redisson时,可以将这些中间件作为分布式锁的协调中心,用于进行锁的获取和释放操作,以保证分布式锁的可用性。
以上解决方案都需要在配置和部署时做相应的工作,如正确配置Redis Sentinel或Redis Cluster、合理设计Redis实例的数量和分布、选择合适的RedLock算法实现等。同时,还需要在代码实现中考虑异常处理和重试机制,以应对可能出现的故障和异常情况。