Redisson实现分布式锁

HanGR 于 2024-09-17 发布


Redisson实现分布式锁


使用Redisson

  1. 添加Redisson依赖
    • spring boot starter 引入(不推荐,版本迭代太快,容易冲突)
    • 直接引入redisson依赖(推荐)
         <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
         <dependency>
             <groupId>org.redisson</groupId>
             <artifactId>redisson</artifactId>
             <version>3.35.0</version>
         </dependency>
      
  2. 配置Redisson
    • 新建RedissonConfig类,配置Redis连接信息
       @Configuration
       @ConfigurationProperties(prefix = "spring.data.redis") // 读取配置文件中的redis配置
       @Data    // lombok注解,自动生成getter、setter方法
       public class RedissonConfig {
          
         private String host;
          
         private String port;
          
         private String password;
          
         @Bean
         public RedissonClient redissonClient() {
             // 1. 创建配置
             Config config = new Config();
             String redisAddress = String.format("redis://%s:%s", host, port);
          
             // 使用单个Redis,没有开集群 useClusterServers()  设置地址和使用库
             config.useSingleServer().setAddress(redisAddress).setDatabase(3).setPassword(password);
          
             // 2. 创建实例
             RedissonClient redisson = Redisson.create(config);
             return redisson;
         }
       }
      
  3. 使用Redisson
    • 在xxxService类中注入RedissonClient,并使用Redisson提供的分布式锁, 以定时任务为例:
       @Component
       @Slf4j
       public class PreCacheJob {
          
         @Resource
         private UserService userService;
          
         @Resource
         private RedisTemplate<String, Object> redisTemplate;
          
         @Resource
         private RedissonClient redissonClient;
          
         // 重点用户
         private List<Long> mainUserList = Arrays.asList(1L);
          
         // 每天执行,预热推荐用户
         @Scheduled(cron = "0 59 23 * * *")   //自己设置时间测试
         public void doCacheRecommendUser() {
             RLock rlock = redissonClient.getLock("hjx:precachejob:docache:lock");
             try {
                 if (rlock.tryLock(0, 30000L, TimeUnit.MILLISECONDS)) {
                     //查数据库
                     for (Long userId : mainUserList) {
                         QueryWrapper<User> queryWrapper = new QueryWrapper<>();
                         Page<User> userPage = userService.page(new Page<>(1, 20), queryWrapper);
                         String redisKey = String.format("hjx:user:recommend:%s", userId);
                         ValueOperations valueOperations = redisTemplate.opsForValue();
                         //写缓存,30s过期
                         try {
                             valueOperations.set(redisKey, userPage, 30000, TimeUnit.MILLISECONDS);
                         } catch (Exception e) {
                             log.error("redis set key error", e);
                         }
                     }
                 }
             } catch (InterruptedException e) {
                 log.error("doCacheRecommendUser, redis lock error", e);
             } finally {
                 // 只能释放自己的锁
                 if (rlock.isHeldByCurrentThread()) {
                     rlock.unlock();
                 }
             }
         }
       } 
      
  4. 续期 (看门狗)
    • Redisson提供了Watchdog来监控Redis的运行状态,如果Redis出现故障,Watchdog会自动释放锁,避免死锁。
    • 原理: 监听当前线程, 默认过期时间是 30 秒, 每 10 秒续期一次 (补到 30 秒)
    • 注意: 如果线程挂掉(注意 debug 模式也会被它当成服务器宕机),则不会续期
    • 代码测试:
       @Test
       void testWatchDog() {
           RLock lock = redissonClient.getLock("hjx:precachejob:docache:lock");
           try {
               // 只有一个线程能获取到锁
               if (lock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                   Thread.sleep(300000);//todo 实际要执行的代码
                   System.out.println("getLock: " + Thread.currentThread().getId());
               }
           } catch (InterruptedException e) {
               System.out.println(e.getMessage());
           } finally {
               // 只能释放自己的锁
               if (lock.isHeldByCurrentThread()) {
                   System.out.println("unLock: " + Thread.currentThread().getId());
                   lock.unlock();
               }
           }
       }