1. 程式人生 > >基於Redis RedLock的分散式同步鎖

基於Redis RedLock的分散式同步鎖

本文采用Redis官網提供的RedLock來實現分散式同步鎖,實現了單機模式和哨兵叢集模式兩種。

安全和可靠性保證

在描述我們的設計之前,我們想先提出三個屬性,這三個屬性在我們看來,是實現高效分散式鎖的基礎。

  • 安全屬性:互斥,不管任何時候,只有一個客戶端能持有同一個鎖。
  • 效率屬性A:不會死鎖,最終一定會得到鎖,就算一個持有鎖的客戶端宕掉或者發生網路分割槽。
  • 效率屬性B:容錯,只要大多數Redis節點正常工作,客戶端應該都能獲取和釋放鎖。

1、RedLock原理

  • 1.獲取當前時間(單位是毫秒)

  • 2.輪流用相同的key和隨機值在N個節點上請求鎖,在這一步裡,客戶端在每個master上請求鎖時會有一個和總的鎖釋放時間相比小的多的超時時間。比如如果鎖自動釋放時間是10秒鐘,那每個節點鎖請求的超時時間可能是5-50毫秒的範圍,這個可以防止一個客戶端在某個宕掉的master節點上阻塞過長時間,如果一個master節點不可用了,我們應該儘快嘗試下一個master節點

  • 3.客戶端計算第二步中獲取鎖所花的時間,只有當客戶端在大多數master節點上成功獲取了鎖(在這裡是3個),而且總共消耗的時間不超過鎖釋放時間,這個鎖就認為是獲取成功了

  • 4.如果鎖獲取成功了,那現在鎖自動釋放時間就是最初的鎖釋放時間減去之前獲取鎖所消耗的時間

  • 5.如果鎖獲取失敗了,不管是因為獲取成功的鎖不超過一半(N/2+1)還是因為總消耗時間超過了鎖釋放時間,客戶端都會到每個master節點上釋放鎖,即便是那些他認為沒有獲取成功的鎖。

2、maven匯入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.7.5</version>
</dependency>

主要配置檔案

@Configuration
@ConfigurationProperties(prefix = "redisson")
@ConditionalOnProperty("redisson.password")
@Data
public class RedissonProperties {
    /**
     * 連線超時時長
     */
    private int timeout = 3000;
    /**
     * ip
     */
    private String address;
    /**
     * 密碼
     */
    private String password;
    /**
     * 連線庫
     */
    private int database = 0;
    /**
     * 連線池大小
     */
    private int connectionPoolSize = 64;
    /**
     * 最小連線數
     */
    private int connectionMinimumIdleSize = 10;
    /**
     * 備用伺服器連線數
     */
    private int slaveConnectionPoolSize = 250;
    /**
     * 主伺服器連線數
     */
    private int masterConnectionPoolSize = 250;
    /**
     * 哨兵地址
     */
    private String[] sentinelAddresses;
    /**
     * 主伺服器名稱
     */
    private String masterName;
}

分散式鎖初始化類

@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redissonProperties;

    @Bean
    @ConditionalOnProperty(name="redission.master-name")
    RedissonClient redissonSentinel(){
        Config config = new Config();
        SentinelServersConfig sentinelServersConfig = config.useSentinelServers()
                .setDatabase(redissonProperties.getDatabase())
                .addSentinelAddress(redissonProperties.getSentinelAddresses())
                .setMasterName(redissonProperties.getMasterName())
                .setTimeout(redissonProperties.getTimeout())
                .setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize())
                .setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize());
        if(StringUtils.isNotBlank(redissonProperties.getPassword())){
            sentinelServersConfig.setPassword(redissonProperties.getPassword());
        }
        return Redisson.create(config);
    }

    @Bean
    @ConditionalOnProperty(name = "redisson.address")
    RedissonClient redissonClient(){
        Config config = new Config();
        SingleServerConfig singleServerConfig = config.useSingleServer()
                .setAddress("redis://" + StringUtils.trim(redissonProperties.getAddress()))
                .setDatabase(redissonProperties.getDatabase())
                .setTimeout(redissonProperties.getTimeout())
                .setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize());
        if( StringUtils.isNotEmpty(redissonProperties.getPassword()) ){
            singleServerConfig.setPassword(redissonProperties.getPassword());
        }
        return Redisson.create(config);
    }


    @Bean
    @ConditionalOnBean(RedissonClient.class)
    DistributedLock distributedLock(RedissonClient redissonClient){
        DistributedLock lock = new RedissonDistributedLocker(redissonClient);
        return lock;
    }
}

根據配置動態生成哨兵叢集模式和單機模式。主要對Redis叢集進行配置後根據Redisson初始化Redisson連線。

分散式鎖介面

/**
 * 基於Redisson的分散式鎖介面
 * @author liumeng
 */
public interface DistributedLock {

    /**
     * 獲取鎖
     * @param lockKey
     * @return
     */
    RLock lock(String lockKey);

    /**
     * 獲取鎖,設定鎖超時時長
     * @param lockKey
     * @param leaseTime
     * @return
     */
    RLock lock(String lockKey, long leaseTime);

    /**
     * 獲取鎖,設定鎖超時時長
     * @param lockKey
     * @param leaseTime
     * @param timeUnit
     * @return
     */
    RLock lock(String lockKey, long leaseTime, TimeUnit timeUnit);


    /**
     * 嘗試獲取鎖
     * @param lockKey
     * @param waitTime
     * @param leaseTime
     * @param timeUnit
     * @return
     */
    boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit);

    /**
     * 釋放鎖
     * @param lockKey
     */
    void unLock(String lockKey);

    /**
     * 釋放鎖
     * @param rLock
     */
    void unLock(RLock rLock);
}

分散式鎖介面實現類

/**
 * @author liumeng
 */
@Slf4j
public class RedissonDistributedLocker implements DistributedLock {

    private RedissonClient redissonClient;

    public RedissonDistributedLocker(RedissonClient redissonClient){
        this.redissonClient = redissonClient;
    }

    @Override
    public RLock lock(String lockKey) {
        RLock rLock = this.getRLock(lockKey);
        rLock.lock();
        return rLock;
    }

    @Override
    public RLock lock(String lockKey, long leaseTime) {
        return this.lock(lockKey, leaseTime, TimeUnit.SECONDS);
    }

    @Override
    public RLock lock(String lockKey, long leaseTime, TimeUnit timeUnit) {
        RLock rLock = this.getRLock(lockKey);
        rLock.lock(leaseTime, timeUnit);
        return rLock;
    }

    @Override
    public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit timeUnit) {
        RLock rLock = this.getRLock(lockKey);
        try {
            return rLock.tryLock(waitTime, leaseTime, timeUnit);
        } catch (InterruptedException e) {
            log.error("", e);
        }
        return false;
    }

    @Override
    public void unLock(String lockKey) {
        RLock rLock = this.getRLock(lockKey);
        rLock.unlock();
    }

    @Override
    public void unLock(RLock rLock) {
        if( null == rLock ){
            throw new NullPointerException("rLock cannot be null.");
        }
        rLock.unlock();
    }

    private RLock getRLock(String lockKey) {
        if( null == redissonClient ){
            throw new NullPointerException("redisson client is null.");
        }
        return redissonClient.getLock(lockKey);
    }
}