在平常的开发工作中,我们经常会用到锁,那么锁有什么用呢?锁主要是控制对共享资源的访问顺序,防止多个线程并发操作导致数据不一致的问题。经常可能会听到乐观锁、悲观锁、分布式锁、行锁、表锁等等,那么我们今天总结下分布式锁的实现方式之Redisson。
一:概述
提到分布式锁,我们先说说什么是锁?锁就是为了避免多个线程同时访问某个共享资源时,修改了数据,导致出现数据一致性。锁好比一个房间只能进去一个人办理事情,得等里面的人出来,其他人才能进去。那么什么是分布式锁呢?分布式锁就是在分布式项目中使用的锁。说到这儿,那就说说什么是分布式。分布式系统是指由多个节点、通过网络连接,共同完成任务的系统。如一个系统由用户服务、订单服务、支付服务、仓储服务等组成,之间通过网络通信调用,那么就是分布式系统。
二:分布式锁特点
1:互斥性:同一时刻只有一个客户端(进程或线程)能够持有锁。
2:可重入性:同一个客户端在已经持有锁的情况下,可以再次获取该锁而不会被阻塞。
3:容错性:如客户端崩溃、网络中断等。当客户端在持有锁的过程中出现异常崩溃时,锁能够自动释放,避免出现死锁的情况。
4:高可用:有良好的获取锁与释放锁的功能,避免分布式锁失效。
三:分布式锁应用场景
1:电商系统中秒杀、库存扣减,防止超卖。
2:分布式任务执行,先获取锁,再执行任务,保证数据一致性。
3:分布式系统幂等,如支付回调、消息消费等。
四:代码实现
1:引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.18.0</version>
</dependency>
2:application.yml redis 配置
data:
redis:
# 连接地址
host: 127.0.0.1
# 端口
port: 6379
# 数据库
database: 0
# 用户名
# username: redis
# 密码
password: 123456
# 连接超时
connect-timeout: 5s
# 读超时
timeout: 30s
3:RedissonConfig
RedissonConfig 主要作用是对 Redisson 客户端进行配置和初始化。
import org.redisson.Redisson;
import org.springframework.beans.factory.annotation.Value;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.database}")
private int database;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + redisPort)
.setDatabase(database)
.setPassword(password);
return Redisson.create(config);
}
4:锁实现
import cn.hutool.extra.spring.SpringUtil;
import com.ruoyi.system.service.ILockforService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* 商品实现类
*/
@Service
@Slf4j
public class SysLockforServiceImpl implements ILockforService{
@Resource
private RedissonClient redissonClient;
@Override
public void addGoods() {
// 定义锁的键
String lockKey = "addGoodsLock";
// 获取分布式锁实例
// 如果在静态方法中,无法注入,通过如下获取 bean
//RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试获取锁,等待 1 秒,锁的过期时间为 1 秒
boolean isLocked1 = lock.tryLock(1, 5, TimeUnit.SECONDS);
// 尝试获取锁,等待 0 秒,即没有获取到会立即返回,没有设置锁的过期时间,则不会自动释放锁,释放锁需要手动控制
// boolean isLocked2 = lock.tryLock(0, TimeUnit.SECONDS);
if (!isLocked1) {
log.info("未获取到锁,请稍后再试!");
}
// 模拟业务逻辑
log.info("开始处理业务");
log.info("处理业务结束");
} catch (Exception e) {
log.error("新增商品异常", e);
} finally {
// 释放锁
lock.unlock();
}
}
5:锁的过期时间
如果设置了过期时间,在到达过期时间后,锁会释放。如果未设置锁的过期时间,则需要在代码中手动释放锁。
如果持有锁的客户端在获取锁之后发生异常关闭(例如程序崩溃、服务器宕机等),由于 Redisson 底层是基于 Redis 实现的,Redis 中存储的锁键值对可能会一直存在,导致其他客户端无法获取该锁,形成死锁。不过,Redisson 为了避免这种情况,有一个看门狗(Watchdog)机制。
看门狗机制会在客户端获取锁时自动为锁设置一个默认的过期时间(默认是 30 秒),并且会在后台启动一个定时任务,不断地延长锁的过期时间,只要客户端还持有锁且没有手动释放。但如果客户端异常关闭,定时任务无法继续执行,锁会在过期时间到达后自动释放。
正常情况下使用 tryLock(0, TimeUnit.SECONDS) 需要手动释放锁,但在客户端异常关闭的情况下,看门狗机制会保证锁最终能够自动释放。
五:总结
实现 Redisson 分布式锁,主要是引入依赖,在 application.yml 中配置 redis 连接信息,配置 RedissonConfig,实现分布式锁。
可能遇到的问题,如果未配置 RedissonConfig,则会注入异常,如下:
以上为 Redisson 实现分布式锁的主要步骤。