SpringBoot 集成 Redisson 實現分佈式鎖

上篇 《SpringBoot 集成 redis 分佈式鎖優化》對死鎖的問題進行了優化,今天介紹的是 redis 官方推薦使用的 Redisson ,Redisson 架設在 redis 基礎上的 Java 駐內存數據網格(In-Memory Data Grid),基於NIO的 Netty 框架上,利用了 redis 鍵值數據庫。功能很是強大,解決了不少分佈式架構中的問題。java

Github的wiki地址:github.com/redisson/re…git

官方文檔:github.com/redisson/re…github

項目代碼結構圖:web

1570859202582

導入依賴

<dependency>
	<groupId>org.redisson</groupId>
	<artifactId>redisson</artifactId>
	<version>3.8.0</version>
</dependency>
複製代碼

屬性配置

application.properites資源文件中添加單機&哨兵相關配置redis

server.port=3000

# redisson lock 單機模式
redisson.address=redis://127.0.0.1:6379
redisson.password=

#哨兵模式
#redisson.master-name= master
#redisson.password=
#redisson.sentinel-addresses=10.47.91.83:26379,10.47.91.83:26380,10.47.91.83:26381
複製代碼

注意:spring

這裏若是不加 redis:// 前綴會報 URI 構建錯誤數據庫

Caused by: java.net.URISyntaxException: Illegal character in scheme name at index 0
複製代碼

更多的配置信息能夠去官網查看apache

定義Lock的接口定義類

package com.tuhu.thirdsample.service;

import org.redisson.api.RLock;
import java.util.concurrent.TimeUnit;
/**
 * @author chendesheng
 * @create 2019/10/12 10:48
 */
public interface DistributedLocker {

    RLock lock(String lockKey);

    RLock lock(String lockKey, int timeout);

    RLock lock(String lockKey, TimeUnit unit, int timeout);

    boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);

    void unlock(String lockKey);

    void unlock(RLock lock);
}
複製代碼

Lock接口實現類

package com.tuhu.thirdsample.service;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import java.util.concurrent.TimeUnit;

/**
 * @author chendesheng
 * @create 2019/10/12 10:49
 */
public class RedissonDistributedLocker implements DistributedLocker{

    private RedissonClient redissonClient;

    @Override
    public RLock lock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock();
        return lock;
    }

    @Override
    public RLock lock(String lockKey, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }

    @Override
    public RLock lock(String lockKey, TimeUnit unit ,int timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    @Override
    public boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            return false;
        }
    }

    @Override
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }

    @Override
    public void unlock(RLock lock) {
        lock.unlock();
    }

    public void setRedissonClient(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
}
複製代碼

redisson屬性裝配類

package com.tuhu.thirdsample.common;

import lombok.Data;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author chendesheng
 * @create 2019/10/11 20:04
 */
@Configuration
@ConfigurationProperties(prefix = "redisson")
@ConditionalOnProperty("redisson.password")
@Data
public class RedissonProperties {


    private int timeout = 3000;

    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;

}
複製代碼

SpringBoot自動裝配類

package com.tuhu.thirdsample.configuration;

import com.tuhu.thirdsample.common.RedissonProperties;
import com.tuhu.thirdsample.service.DistributedLocker;
import com.tuhu.thirdsample.service.RedissonDistributedLocker;

import com.tuhu.thirdsample.util.RedissonLockUtil;
import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


/**
 * @author chendesheng
 * @create 2019/10/12 10:50
 */
@Configuration
@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedissonProperties.class)
public class RedissonAutoConfiguration {

    @Autowired
    private RedissonProperties redissonProperties;

    /**
     * 哨兵模式自動裝配
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="redisson.master-name")
    RedissonClient redissonSentinel() {
        Config config = new Config();
        SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(redissonProperties.getSentinelAddresses())
                .setMasterName(redissonProperties.getMasterName())
                .setTimeout(redissonProperties.getTimeout())
                .setMasterConnectionPoolSize(redissonProperties.getMasterConnectionPoolSize())
                .setSlaveConnectionPoolSize(redissonProperties.getSlaveConnectionPoolSize());

        if(StringUtils.isNotBlank(redissonProperties.getPassword())) {
            serverConfig.setPassword(redissonProperties.getPassword());
        }
        return Redisson.create(config);
    }

    /**
     * 單機模式自動裝配
     * @return
     */
    @Bean
    @ConditionalOnProperty(name="redisson.address")
    RedissonClient redissonSingle() {
        Config config = new Config();
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(redissonProperties.getAddress())
                .setTimeout(redissonProperties.getTimeout())
                .setConnectionPoolSize(redissonProperties.getConnectionPoolSize())
                .setConnectionMinimumIdleSize(redissonProperties.getConnectionMinimumIdleSize());

        if(StringUtils.isNotBlank(redissonProperties.getPassword())) {
            serverConfig.setPassword(redissonProperties.getPassword());
        }

        return Redisson.create(config);
    }

    /**
     * 裝配locker類,並將實例注入到RedissLockUtil中
     * @return
     */
    @Bean
    DistributedLocker distributedLocker(RedissonClient redissonClient) {
        DistributedLocker locker = new RedissonDistributedLocker();
        ((RedissonDistributedLocker) locker).setRedissonClient(redissonClient);
        RedissonLockUtil.setLocker(locker);
        return locker;
    }
}
複製代碼

Lock幫助類

package com.tuhu.thirdsample.util;

import com.tuhu.thirdsample.service.DistributedLocker;
import org.redisson.api.RLock;

import java.util.concurrent.TimeUnit;

/**
 * @author chendesheng
 * @create 2019/10/12 10:54
 */
public class RedissonLockUtil {

    private static DistributedLocker redissLock;

    public static void setLocker(DistributedLocker locker) {
        redissLock = locker;
    }

    /**
     * 加鎖
     * @param lockKey
     * @return
     */
    public static RLock lock(String lockKey) {
        return redissLock.lock(lockKey);
    }

    /**
     * 釋放鎖
     * @param lockKey
     */
    public static void unlock(String lockKey) {
        redissLock.unlock(lockKey);
    }

    /**
     * 釋放鎖
     * @param lock
     */
    public static void unlock(RLock lock) {
        redissLock.unlock(lock);
    }

    /**
     * 帶超時的鎖
     * @param lockKey
     * @param timeout 超時時間   單位:秒
     */
    public static RLock lock(String lockKey, int timeout) {
        return redissLock.lock(lockKey, timeout);
    }

    /**
     * 帶超時的鎖
     * @param lockKey
     * @param unit 時間單位
     * @param timeout 超時時間
     */
    public static RLock lock(String lockKey, TimeUnit unit , int timeout) {
        return redissLock.lock(lockKey, unit, timeout);
    }

    /**
     * 嘗試獲取鎖
     * @param lockKey
     * @param waitTime 最多等待時間
     * @param leaseTime 上鎖後自動釋放鎖時間
     * @return
     */
    public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        return redissLock.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
    }

    /**
     * 嘗試獲取鎖
     * @param lockKey
     * @param unit 時間單位
     * @param waitTime 最多等待時間
     * @param leaseTime 上鎖後自動釋放鎖時間
     * @return
     */
    public static boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
        return redissLock.tryLock(lockKey, unit, waitTime, leaseTime);
    }
}
複製代碼

控制層

package com.tuhu.thirdsample.task;

import com.tuhu.thirdsample.common.KeyConst;
import lombok.extern.slf4j.Slf4j;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;
/**
 * @author chendesheng
 * @create 2019/10/12 11:03
 */
@RestController
@RequestMapping("/lock")
@Slf4j
public class LockController {
    @Autowired
    RedissonClient redissonClient;
    
    @GetMapping("/task")
    public void task(){
        log.info("task start");
        RLock lock = redissonClient.getLock(KeyConst.REDIS_LOCK_KEY);
        boolean getLock = false;

        try {
            if (getLock = lock.tryLock(0,5,TimeUnit.SECONDS)){
                //執行業務邏輯
                System.out.println("拿到鎖幹活");

            }else {
                log.info("Redisson分佈式鎖沒有得到鎖:{},ThreadName:{}",KeyConst.REDIS_LOCK_KEY,Thread.currentThread().getName());
            }

        } catch (InterruptedException e) {
            log.error("Redisson 獲取分佈式鎖異常,異常信息:{}",e);
        }finally {


            if (!getLock){
                return;
            }
            //若是演示的話須要註釋該代碼;實際應該放開
            //lock.unlock();
            //log.info("Redisson分佈式鎖釋放鎖:{},ThreadName :{}", KeyConst.REDIS_LOCK_KEY, Thread.currentThread().getName());
        }
    }

}
複製代碼

RLock 繼承自 java.util.concurrent.locks.Lock,能夠將其理解爲一個重入鎖,須要手動加鎖和釋放鎖 。api

來看它其中的一個方法:tryLock(long waitTime, long leaseTime, TimeUnit unit)架構

getLock = lock.tryLock(0,5,TimeUnit.SECONDS)
複製代碼

經過 tryLock() 的參數能夠看出,在獲取該鎖時若是被其餘線程先拿到鎖就會進入等待,等待 waitTime 時間,若是還沒用機會獲取到鎖就放棄,返回 false;若得到了鎖,除非是調用 unlock 釋放,那麼會一直持有鎖,直到超過 leaseTime 指定的時間。

以上就是 Redisson 實現分佈式鎖的核心方法,有人可能要問,那怎麼肯定拿的是同一把鎖,分佈式鎖在哪?

這就是 Redisson 的強大之處,其底層仍是使用的 Redis 來做分佈式鎖,在咱們的RedissonManager中已經指定了 Redis 實例,Redisson 會進行託管,其原理與咱們手動實現 Redis 分佈式鎖相似。

測試

啓動項目,在postman中輸入url:http://localhost:3000/lock/task

第一次輸入結果,咱們能夠在控制檯看到:

1570860183525

在鎖過時時間內再次請求,咱們能夠在控制檯看到:

1570860271026

最後

ok, SpringBoot 集成 Redisson實現分佈式鎖已經寫到最後。使用redisson更加體現一切皆對象,咱們不須要知道內部如何實現,只需知道如何使用就行。 固然僅僅是知道如何使用是遠遠不夠的,熟悉理解底層原理更爲重要!

相關文章
相關標籤/搜索