MENU

Redlock算法和底层源码分析

Redlock算法和底层源码分析

当前代码用v8.0的

Redis分布式锁-Redlock红锁算法Distributed locks with Redis

产生原因----- 单点故障:

image-20230617223921458

redis之父提出了Redlock算法解决这个问题

Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。
锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。

image-20230617224715826

设计理念

image-20230617225025063

该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用master节点,同时由于舍弃了slave,为了保证可用性,引入了N个节点,官方建议是5。

演示用3台实例来做说明。

客户端只有在满足下面的这两个条件时,才能认为是加锁成功。
条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;
条件2:客户端获取锁的总耗时没有超过锁的有效时间。

image-20230617225416664

redisson实现

pom
<!--redisson-->
  <dependency>
      <groupId>org.redisson</groupId>
      <artifactId>redisson</artifactId>
      <version>3.13.4</version>
  </dependency>
RedisConfig
package com.zzrg.redislock.config;

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
* @Author ZzRG
* @Date 2023/6/14 0:39
* @Version 1.0
*/
@Configuration
public class RedisConfig {
  @Bean
  public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
      RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
      // 配置具体的序列化方式
      redisTemplate.setConnectionFactory(lettuceConnectionFactory);
      // 设置key的序列话方式String
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      // 设置value的序列化方式JSON
      redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

      redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

      redisTemplate.afterPropertiesSet();

      return redisTemplate;
  }

  @Bean
  public Redisson redisson(){
      Config config = new Config();
      config.useSingleServer().setAddress("redis://192.168.126.130:6379").setDatabase(0).setPassword("");

      return (Redisson) Redisson.create(config);
  }
}
InventoryServiceImpl
package com.zzrg.redislock.service.Impl;

import cn.hutool.core.util.IdUtil;
import com.zzrg.redislock.config.DistributedLockFactory;
import com.zzrg.redislock.myLock.RedisDistributedLock;
import com.zzrg.redislock.service.InventoryService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.sql.Time;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
* @Author ZzRG
* @Date 2023/6/14 1:09
* @Version 1.0
*/
@Service
@Slf4j
public class InventoryServiceImpl implements InventoryService {

  @Autowired
  private StringRedisTemplate stringRedisTemplate;
  @Value("${service.port}")
  private String port;
  @Autowired
  private Redisson redisson;

  @Override
  public String saleByRedisson() {
      String retMessage = "";

      RLock redisRedissonLock=redisson.getLock("redisRedissonLock");

      redisRedissonLock.lock();
      try {
          // 1 查询库存信息
          String result = stringRedisTemplate.opsForValue().get("inventory001");
          // 2 判断库存是否足够
          Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
          // 3 扣减库存 每次减少一个
          if (inventoryNumber > 0) {
              stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
              retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
              System.out.println(retMessage + "\t" + "服务端口号:" + port);
          } else {
              retMessage = "商品卖完了,o(╥﹏╥)o";
          }
      } finally {
          redisRedissonLock.unlock();
      }
      return retMessage + "\t" + "服务端口号:" + port;

  }
}
InventoryService
package com.zzrg.redislock.service;

/**
* @Author ZzRG
* @Date 2023/6/14 1:09
* @Version 1.0
*/
public interface InventoryService {
  String sale();

  String saleByRedisson();

}
InventoryController
package com.zzrg.redislock.controller;

import com.zzrg.redislock.service.InventoryService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @Author ZzRG
* @Date 2023/6/14 1:14
* @Version 1.0
*/

@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController {

  @Autowired
  private InventoryService inventoryService;

  @ApiOperation("扣减库存sale,一次卖一个")
  @GetMapping(value = "/inventory/sale")
  public String sale()
  {
      return inventoryService.sale();
  }


  @ApiOperation("扣减库存saleByRedisson,一次卖一个")
  @GetMapping(value = "/inventory/saleByRedisson")
  public String saleByRedisson()
  {
      return inventoryService.saleByRedisson();
  }
}

但是还是会出现BUG

image-20230617232159935

修改

package com.zzrg.redislock.service.Impl;

import cn.hutool.core.util.IdUtil;
import com.zzrg.redislock.config.DistributedLockFactory;
import com.zzrg.redislock.myLock.RedisDistributedLock;
import com.zzrg.redislock.service.InventoryService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.sql.Time;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**
* @Author ZzRG
* @Date 2023/6/14 1:09
* @Version 1.0
*/
@Service
@Slf4j
public class InventoryServiceImpl implements InventoryService {

  @Autowired
  private StringRedisTemplate stringRedisTemplate;
  @Value("${service.port}")
  private String port;
  @Autowired
  private Redisson redisson;

  @Override
  public String saleByRedisson() {
      String retMessage = "";

      RLock redisRedissonLock=redisson.getLock("redisRedissonLock");

      redisRedissonLock.lock();
      try {
          // 1 查询库存信息
          String result = stringRedisTemplate.opsForValue().get("inventory001");
          // 2 判断库存是否足够
          Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
          // 3 扣减库存 每次减少一个
          if (inventoryNumber > 0) {
              stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
              retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
              System.out.println(retMessage + "\t" + "服务端口号:" + port);
          } else {
              retMessage = "商品卖完了,o(╥﹏╥)o";
          }
      } finally {
          //改进点,只能删除属于自己的Key,不能删除别人的
          if (redisRedissonLock.isLocked() && redisRedissonLock.isHeldByCurrentThread()) {
              redisRedissonLock.unlock();
          }
      }
      return retMessage + "\t" + "服务端口号:" + port;

  }
}

Redisson源码解析

Redis分布式锁过期了,但是业务逻辑还没处理完怎么办

缓存续命

守护线程"续命"

额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。
Redisson里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;

在获取锁成功后,给锁加一个watchdog, watchdo 会起一个定时任务,在锁没有被释放且快要过期的时候会续期

image-20230618133750186

image-20230618133800379

源码分析1

通过redisson新建出来的锁key,默认是30秒钟

image-20230618134356398

lock 还是juc里面的

image-20230618134439644

image-20230618134617347

image-20230618134725730

image-20230618134853580

image-20230618134948026

源码分析2

image-20230618135128844

image-20230618135350231

image-20230618135453572

image-20230618135925827

lock() --- tryACquire() ---tryAcquireAsync()

源码分析3

image-20230618140600268

image-20230618140658802

流程解释

  1. 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
  2. 通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
  3. 如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间(代表了锁key的剩余生存时间),加锁失败
源码分析4

image-20230618141345692

image-20230618141425744

image-20230618141603931

解锁

image-20230618141837186

image-20230618141910718

image-20230618141933545

image-20230618141942064