本文共 4180 字,大约阅读时间需要 13 分钟。
之前笔者使用Redission中tryLock方法时碰到了一些问题,本来加锁之后不应该出现多次访问DB的情况,可事实上在打印出的日志中显示有多次访问数据库,因而笔者查了一些资料了解了下trylock的基本实现原理,在这里记录一下,如有不当之处,还请各位看官不吝指证
Redission中的tryLock是基于分布式锁来实现的,其内部是使用redis中原生setnx再补齐续命逻辑达到最终效果的。通俗来讲就是一个服务部署在了多个服务器上,然后共享一把锁,当这把锁没有释放时其他线程会进行等待,直至当前持有锁的线程执行完成将锁释放,如果锁超时了但是当前持有线程还未执行完成,则会触发续命机制重置当前锁的超时时间,直至当前持有锁的线程执行完成后自行释放锁。
org.springframework.boot spring-boot-starter-data-redis org.redisson redisson 3.15.6
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;/** ** 用户表 服务实现类 *
* * @author muyichen * @since 2021-06-17 */@Slf4j@Servicepublic class UserServiceImpl extends ServiceImplimplements IUserService { @Autowired private UserMapper userMapper; @Autowired private RedissonClient redissonClient; @Autowired private RedisOpsUtil 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; }}
wait:表示等待时间,就是之后的线程会等待当前现场释放锁的时间
leaseTime:表示锁超时时间 unit:表示前面两个时间的单位
出现这个问题,是因为笔者之前将lock的key和User的key设置成一样的了,但是两者的类型是不一样的。所以设置key的时候要注意唯一性
单台服务器的情况下,如果一开始没有缓存,那么一旦超过800的并发数量,那么就会出现Error
转载地址:http://kktgn.baihongyu.com/