官网:https://redisson.org/
官方文档:https://redisson.org/docs/getting-started/
官方中文文档:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
1、引入依赖
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
如果用下面这个依赖,后面则省去config这一步配置过程,如果用上面后面config则要配置
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.27.2</version>
</dependency>
2、配置Redisson客户端
(1)程序化配置方法
参考官网:
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置
Config config = new Config();
// 如果是集群使用config.useClusterServers().setAddress()
config.useSingleServer().setAddress("redis://192.168.75.101:6379").setPassword("123321");
// 创建RedissonClient对象
return Redisson.create(config);
}
}
(2)文件方式配置
当然也可以如果yml的方式进行配置,参考官方文档如下:https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95#22-%E6%96%87%E4%BB%B6%E6%96%B9%E5%BC%8F%E9%85%8D%E7%BD%AE
还有一些配置详细信息:
这里我们使用程序化配置方式 !
3、简单使用Redisson的分布式锁
这里讲可重入锁:
参考:8. 分布式锁和同步器 · redisson/redisson Wiki · GitHub
@Resource
private RedissionClient redissonClient;
@Test
void testRedisson() throws Exception{
//获取锁(可重入),指定锁的名称
RLock lock = redissonClient.getLock("myLock");
//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);
//判断获取锁成功
if(isLock){
try{
System.out.println("执行业务");
}finally{
//释放锁
lock.unlock();
}
}
}
Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟
剩余其余锁可以参考官方文档。
4、分布式锁原理
Redisson分布式锁原理:
•可重入:利用hash结构记录线程id和重入次数
•可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
•超时续约:利用watchDog机制,开启一个定时任务,每隔一段时间(releaseTime / 3),重置超时时间
5、实战
在微信登录操作的时候加上分布式锁
首先需要:
@Resource
private RedissonClient redissonClient;
/**
* 微信登录
*/
@Override
public User wxLogin(UserLoginDTO userLoginDTO) {
//调用微信接口服务,获得当前微信用户的openid
String openid = getOpenid(userLoginDTO.getCode());
System.out.println("当前微信用户的openid"+openid);
//判断openid是否为空,如果为空表示登录失败,抛出业务异常
if (openid==null){
throw new LoginFailedException(MessageConstant.LOGIN_FAILED);
}
//判断当前用户是否为新用户
// User user = userMapper.getByOpenid(openid);
QueryWrapper<User>wrapper=new QueryWrapper<>();
wrapper.lambda()
.eq(User::getOpenid,openid);
User user = userMapper.selectOne(wrapper);
//如果为新用户,自动完成注册
if (user==null){
RLock lock = redissonClient.getLock(RedissonConstant.LOCK_USER_REGISTER_KEY + openid);
if (!lock.tryLock()){
throw new RegisterFailedException(MessageConstant.REGISTER_FAILED_USER_EXIST);
}
try {
//获得当前用户表数据数量
Long allUserNums = userMapper.selectCount(null);
//构造用户编号id
/*"%0" + 8 + "d":这是一个格式字符串,用于指定输出格式。
"%0" 表示输出时,如果数字的长度不足指定的宽度,则在前面补0;
8 是紧接着 %0 后面的数字,表示输出的数字至少要有8位;
"d" 表示输出的是十进制整数。
因此,整个格式字符串意味着输出一个至少8位的十进制整数,如果不足8位,则在前面补0*/
String substring = String.valueOf(System.currentTimeMillis()).substring(5);
String quickUserId = "QUICK" +substring+"_"+ String.format("%0" + 6 + "d", allUserNums + 1);
log.info("注册新用户");
user=User.builder()
.openid(openid)
.quickUserId(quickUserId)
.wallet(0L)
.follow(0L)
.fan(0L)
.briefIntroduction("")
.collectNumber(0L)
.markNumber(0L)
.useTime(0L)
.name("微信用户")
.phone("")
.sex("")
.avatar("https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0")
.createTime(LocalDateTime.now())
.build();
userMapper.insert(user);
//发送mq异步消息修改es表 发送这两个数据后续获取用户信息 openid
try {
rabbitTemplate.convertAndSend(
EsUserDocListener.ADD_USER_DOC_EXCHANGE_NAME,
EsUserDocListener.ADD_USER_DOC_ROUTING_KEY,
user.getQuickUserId());
} catch (AmqpException e) {
log.error("发送新增userDoc消息失败", e);
}
}catch (DuplicateKeyException e){
throw new RegisterFailedException(MessageConstant.REGISTER_FAILED_USER_EXIST);
}finally {
// 解锁
lock.unlock();
}
}
//返回这个用户的对象
return user;
}