SpringBoot:Redis分布式鎖


概述

目前幾乎很多大型網站及應用都是分布式部署的,分布式場景中的數據一致性問題一直是一個比較重要的話題。分布式的CAP理論告訴我們“任何一個分布式系統都無法同時滿足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多只能同時滿足兩項。”所以,很多系統在設計之初就要對這三者做出取舍。在互聯網領域的絕大多數的場景中,都需要犧牲強一致性來換取系統的高可用性,系統往往只需要保證“最終一致性”,只要這個最終時間是在用戶可以接受的范圍內即可。

在很多場景中,我們為了保證數據的最終一致性,需要很多的技術方案來支持,比如分布式事務、分布式鎖等。有的時候,我們需要保證一個方法在同一時間內只能被同一個線程執行。

基於數據庫實現分布式鎖; 
基於緩存(Redis等)實現分布式鎖; 
基於Zookeeper實現分布式鎖;

盡管有這三種方案,但是不同的業務也要根據自己的情況進行選型,他們之間沒有最好只有更適合!但由於操作數據庫需要一定的開銷,會照成一定的性能問題。並且使用數據庫行級鎖並不一定靠譜,尤其是當我們鎖表並不大的時候,所以實際開發過程中使用Redis或Zookeeper的比較多。

分布式鎖特性

當我們在設計分布式鎖的時候,我們應該考慮分布式鎖至少要滿足的一些條件,同時考慮如何高效的設計分布式鎖,這里我認為以下幾點是必須要考慮的。

1、互斥

在分布式高並發的條件下,我們最需要保證,同一時刻只能有一個線程獲得鎖,這是最基本的一點。

2、防死鎖

在分布式高並發的條件下,比如有個線程獲得鎖的同時,還沒有來得及去釋放鎖,就因為系統故障或者其它原因使它無法執行釋放鎖的命令,導致其它線程都無法獲得鎖,造成死鎖。所以分布式非常有必要設置鎖的有效時間,確保系統出現故障后,在一定時間內能夠主動去釋放鎖,避免造成死鎖的情況。

3、性能

高並發分布式系統中,線程互斥等待會成為性能瓶頸,需要好的中間件和實現來保證性能。

4、重入

我們知道ReentrantLock是可重入鎖,那它的特點就是:同一個線程可以重復拿到同一個資源的鎖。重入鎖非常有利於資源的高效利用。關於這點之后會做演示。針對以上Redisson都能很好的滿足,下面就來分析下它。

基於Redisson的實現

為了更好的理解分布式鎖的原理,我這邊自己畫張圖通過這張圖來分析。

加鎖機制:

線程去獲取鎖,獲取成功: 執行lua腳本,保存數據到redis數據庫。

線程去獲取鎖,獲取失敗: 一直通過while循環嘗試獲取鎖,獲取成功后,執行lua腳本,保存數據到redis數據庫。

watch dog看門狗:

在一個分布式環境下,假如一個線程獲得鎖后,突然服務器宕機了,那么這個時候在一定時間后這個鎖會自動釋放,你也可以設置鎖的有效時間(不設置默認30秒),這樣的目的主要是防止死鎖的發生。但在實際開發中會有下面一種情況:

    //設置鎖1秒過去
        redissonLock.lock("redisson", 1);
        /**
         * 業務邏輯需要咨詢2秒
         */
        redissonLock.release("redisson");

      /**
       * 線程1 進來獲得鎖后,線程一切正常並沒有宕機,但它的業務邏輯需要執行2秒,這就會有個問題,在 線程1 執行1秒后,這個鎖就自動過期了,
       * 那么這個時候 線程2 進來了。那么就存在 線程1和線程2 同時在這段業務邏輯里執行代碼,這當然是不合理的。
       * 而且如果是這種情況,那么在解鎖時系統會拋異常,因為解鎖和加鎖已經不是同一線程了,具體后面代碼演示。
       */

所以這個時候看門狗就出現了,它的作用就是 線程1 業務還沒有執行完,時間就過了,線程1 還想持有鎖的話,就會啟動一個watch dog后台線程,不斷的延長鎖key的生存時間。注意 正常這個看門狗線程是不啟動的,還有就是這個看門狗啟動后對整體性能也會有一定影響,所以不建議開啟看門狗。

 代碼實現

首先在pom里依賴Redisson

            <!-- redisson -->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson</artifactId>
                <version>${org-redisson.version}</version>
            </dependency>    

   在公共模塊配置Redisson

import org.redisson.Redisson;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ResourceBundle;

/**
 * <p>實例Redisson類</p>
 *
 * @author lft
 * @version 1.0
 * @date 2019/6/28 0028
 * @since jdk1.8
 */
@Component
public class RedissonManager {

    private static Logger logger = LoggerFactory.getLogger(RedissonManager.class);
    private static String REDIS_IP;
    private static String REDIS_PORT;
    private static String REDIS_PASSWD;
    private static String PROJECT_ENV;

    @Value("${spring.profiles.active}")
    public void setProjectEnv(String projectEnv) {
        PROJECT_ENV = projectEnv;
    }
    private static Config config = new Config();
    private static Redisson redisson = null;

    private static void init() {
        ResourceBundle properties = ResourceBundle.getBundle("application-"+PROJECT_ENV);
        REDIS_IP = properties.getString("spring.redis.host").trim();
        REDIS_PORT = properties.getString("spring.redis.port").trim();
        if(properties.containsKey("spring.redis.password")){
            REDIS_PASSWD = properties.getString("spring.redis.password");
        }
        SingleServerConfig singleServerConfig = config.useSingleServer();
        singleServerConfig.setAddress("redis://"+REDIS_IP+":"+REDIS_PORT);
        if(null != REDIS_PASSWD && !"".equals(REDIS_PASSWD)){
            singleServerConfig.setPassword(REDIS_PASSWD);
        }
        //得到redisson對象
        redisson = (Redisson) Redisson.create(config);
    }
    //實例化redisson
    public static Redisson getInstance(){
        init();
        return redisson;
    }
}

  加鎖和釋放鎖的方法

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.TimeUnit;

/**
 * <p>Redisson的使用</p>
 *
 * @author lft
 * @version 1.0
 * @date 2019/6/28 0028
 * @since jdk1.8
 */
public class DistributedRedisLock {

    private static Logger logger = LoggerFactory.getLogger(DistributedRedisLock.class);
    private static Redisson redisson = RedissonManager.getInstance();
    private static final String LOCK_TITLE = "redis_lock_";
    //加鎖
    public static boolean lockup(String lockName){
        //聲明key對象
        String key = LOCK_TITLE + lockName;
        //獲取鎖對象
        RLock mylock = redisson.getLock(key);
        //設置過期時間,防止死鎖
        mylock.lock(60, TimeUnit.SECONDS);
        logger.info("======lock======"+Thread.currentThread().getName());
        return true;
    }
    //鎖的釋放
    public static void release(String lockName){
        String key = LOCK_TITLE + lockName;
        RLock mylock = redisson.getLock(key);
        //釋放鎖(解鎖)
        mylock.unlock();
        logger.info("======unlock======"+Thread.currentThread().getName());
    }
}

  業務邏輯使用鎖

/**
     * 測試Redis鎖
     * @param request
     * @return
     * @throws Exception
     */
    @PostMapping("/lockTest")
    public Object lockTest(HttpServletRequest request) throws Exception{
        String key = "test";
        //加鎖
        DistributedRedisLock.lockup(key);
        //TODO 業務代碼
        LOGGER.info("=========================執行測試任務");
        //釋放鎖
        DistributedRedisLock.release(key);
        return ResultUtils.success("鎖測試結束!");
    }

  

 

zookeeper分布式鎖參考:https://cloud.tencent.com/developer/article/1462302


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM