《SpringBoot2.0 實戰》系列-集成redisson實現分佈式鎖

前言

最近面試總是會被問到有沒有用過分佈式鎖、redis鎖,由於平時很少接觸到,所以只能很無奈的回答“沒有”。回來之後就惡補了一下,本文主要做下記錄,通過SpringBoot整合redisson來實現分佈式鎖,並結合demo測試結果。

首先看下大佬總結的圖

來源:https://www.cnblogs.com/qdhxhz/p/11046905.html

正文

增加依賴

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.10.6</version>
</dependency>

配置 信息

spring:
  # redis
  redis:
    host: 47.103.5.190
    port: 6379
    jedis:
      pool:
        # 連接池最大連接數(使用負值表示沒有限制)
        max-active: 100
        # 連接池中的最小空閒連接
        max-idle: 10
        # 連接池最大阻塞等待時間(使用負值表示沒有限制)
        max-wait: -1
      # 連接超時時間(毫秒)
      timeout: 5000
      #默認是索引爲0的數據庫
      database: 0

配置類

/**
 * redisson 配置,下面是單節點配置:
 *
 * @author gourd
 */
@Configuration
public class RedissonConfig {
    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private String port;
    @Value("${spring.redis.password:}")
    private String password;

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        //單節點
        config.useSingleServer().setAddress("redis://" + host + ":" + port);
        if (StringUtils.isEmpty(password)) {
            config.useSingleServer().setPassword(null);
        } else {
            config.useSingleServer().setPassword(password);
        }
        //添加主從配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
        // 集羣模式配置 setScanInterval()掃描間隔時間,單位是毫秒, //可以用"rediss://"來啓用SSL連接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
        return Redisson.create(config);
    }
}

Redisson工具類

/**
 * redis分佈式鎖幫助類
 *
 * @author gourd
 *
 */
public class RedisLockUtil {

    private static DistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker",DistributedLocker.class);

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

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

    /**
     * 帶超時的鎖
     * @param lockKey
     * @param timeout 超時時間   單位:秒
     */
    public static RLock lock(String lockKey, int timeout) {
        return distributedLocker.lock(lockKey, timeout);
    }
    
    /**
     * 帶超時的鎖
     * @param lockKey
     * @param unit 時間單位
     * @param timeout 超時時間
     */
    public static RLock lock(String lockKey, int timeout,TimeUnit unit ) {
        return distributedLocker.lock(lockKey, unit, timeout);
    }
    
    /**
     * 嘗試獲取鎖
     * @param lockKey
     * @param waitTime 最多等待時間
     * @param leaseTime 上鎖後自動釋放鎖時間
     * @return
     */
    public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
        return distributedLocker.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 distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
    }

    /**
     * 獲取計數器
     *
     * @param name
     * @return
     */
    public static RCountDownLatch getCountDownLatch(String name){
        return distributedLocker.getCountDownLatch(name);
    }

    /**
     * 獲取信號量
     *
     * @param name
     * @return
     */
    public static RSemaphore getSemaphore(String name){
        return distributedLocker.getSemaphore(name);
    }
}

底層封裝

/**
 * @author gourd
 */
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);
}

/**
 * @author gourd
 */
@Component
public class RedisDistributedLocker implements DistributedLocker {

    @Autowired
    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();
    }
}

測試

模擬併發測試

/**
 * redis分佈式鎖控制器
 * @author gourd
 * @since 2019-07-30
 */
@RestController
@Api(tags = "redisson", description = "redis分佈式鎖控制器" )
@RequestMapping("/redisson" )
@Slf4j
public class RedissonLockController {

    /**
     * 鎖測試共享變量
     */
    private Integer lockCount = 10;

    /**
     * 無鎖測試共享變量
     */
    private Integer count = 10;

    /**
     * 模擬線程數
     */
    private static int threadNum = 10;

    /**
     * 模擬併發測試加鎖和不加鎖
     * @return
     */
    @GetMapping("/test")
    @ApiOperation(value = "模擬併發測試加鎖和不加鎖")
    public void lock(){
        // 計數器
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < threadNum; i ++) {
            MyRunnable myRunnable = new MyRunnable(countDownLatch);
            Thread myThread = new Thread(myRunnable);
            myThread.start();
        }
        // 釋放所有線程
        countDownLatch.countDown();
    }

    /**
     * 加鎖測試
     */
    private void testLockCount() {
        String lockKey = "lock-test";
        try {
            // 加鎖,設置超時時間2s
            RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
            lockCount--;
            log.info("lockCount值:"+lockCount);
        }catch (Exception e){
            log.error(e.getMessage(),e);
        }finally {
            // 釋放鎖
            RedisLockUtil.unlock(lockKey);
        }
    }

    /**
     * 無鎖測試
     */
    private void testCount() {
        count--;
        log.info("count值:"+count);
    }


    public class MyRunnable implements Runnable {
        /**
         * 計數器
         */
        final CountDownLatch countDownLatch;

        public MyRunnable(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                // 阻塞當前線程,直到計時器的值爲0
                countDownLatch.await();
            } catch (InterruptedException e) {
                log.error(e.getMessage(),e);
            }
            // 無鎖操作
            testCount();
            // 加鎖操作
            testLockCount();
        }

    }

}

調用接口後打印值:

測試結果

根據打印結果可以明顯看到,未加鎖的count--後值是亂序的,而加鎖後的結果和我們預期的一樣。

由於條件問題沒辦法測試分佈式的併發。只能模擬單服務的這種併發,但是原理是一樣,希望對大家有幫助。如有錯誤之處,歡迎指正。

====================================================

代碼均已上傳至本人的開源項目

spring-cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章