之前笔者使用Redission中tryLock方法时碰到了一些问题,本来加锁之后不应该出现多次访问DB的情况,可事实上在打印出的日志中显示有多次访问数据库,因而笔者查了一些资料了解了下trylock的基本实现原理,在这里记录一下,如有不当之处,还请各位看官不吝指证
目录
一、Redission中tryLock的基本实现原理
Redission中的tryLock是基于分布式锁来实现的,其内部是使用redis中原生setnx再补齐续命逻辑达到最终效果的。通俗来讲就是一个服务部署在了多个服务器上,然后共享一把锁,当这把锁没有释放时其他线程会进行等待,直至当前持有锁的线程执行完成将锁释放,如果锁超时了但是当前持有线程还未执行完成,则会触发续命机制重置当前锁的超时时间,直至当前持有锁的线程执行完成后自行释放锁。
二、Redission中tryLock的简单使用
1、需要的依赖
<!--引入redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--引入redission用于分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.6</version>
</dependency>
2、基本实现
package com.muyichen.demo.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.muyichen.demo.dao.UserMapper;
import com.muyichen.demo.entity.User;
import com.muyichen.demo.service.IUserService;
import com.muyichen.demo.service.RedissonCommonService;
import com.muyichen.demo.util.RedisOpsUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author muyichen
* @since 2021-06-17
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisOpsUtil<User> redisOpsUtil;
@Override
public User getUserById(Long id) {
return getUseTryLock(id);
}
/**
* 使用tryLock上锁
* @param id
* @return
*/
private User getUseTryLock(Long id) {
//先从缓存中查询是否存在用户信息
User resultUser = redisOpsUtil.get("User:" + id, User.class);
if (null != resultUser) {
return resultUser;
}
// 获取当前查询所对应的锁
RLock lock = redissonClient.getLock("Lock:" + id);
try {
//判断是否能拿到锁
if(lock.tryLock(0, 5, TimeUnit.SECONDS)) {
//如果能拿到锁,则去数据库中查询数据
resultUser = userMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getId, id).last("limit 1"));
log.info("走数据库:{}", id);
//并将查询出的数据放入缓存中
redisOpsUtil.set("User:" + id, resultUser, 360L, TimeUnit.SECONDS);
} else {
Thread.sleep(50);
//如果拿不到锁则回调当前方法
getUserById(id);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return resultUser;
}
/**
* 使用lock上锁(双重检测)
* @param id
* @return
*/
private User getUseLock(Long id) {
//先从缓存中查询是否存在用户信息
User resultUser = redisOpsUtil.get("User:" + id, User.class);
if (null != resultUser) {
return resultUser;
}
// 获取当前查询所对应的锁
RLock lock = redissonClient.getLock("Lock:" + id);
try {
lock.lock();
resultUser = redisOpsUtil.get("User:" + id, User.class);
if (null != resultUser) {
return resultUser;
}
//如果缓存中没有则去数据库中查询数据
resultUser = userMapper.selectOne(Wrappers.lambdaQuery(User.class).eq(User::getId, id).last("limit 1"));
log.info("走数据库:{}", id);
//并将查询出的数据放入缓存中
redisOpsUtil.set("User:" + id, resultUser, 360L, TimeUnit.SECONDS);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return resultUser;
}
}
三、tryLock中的三个参数
wait:表示等待时间,就是之后的线程会等待当前现场释放锁的时间
leaseTime:表示锁超时时间
unit:表示前面两个时间的单位
四、使用过程中遇到的问题
1、WRONGTYPE Operation against a key holding the wrong kind of value
出现这个问题,是因为笔者之前将lock的key和User的key设置成一样的了,但是两者的类型是不一样的。所以设置key的时候要注意唯一性
2、并发量的局限
单台服务器的情况下,如果一开始没有缓存,那么一旦超过800的并发数量,那么就会出现Error