SpringBoot:Redis分佈式鎖

概述

目前幾乎不少大型網站及應用都是分佈式部署的,分佈式場景中的數據一致性問題一直是一個比較重要的話題。分佈式的CAP理論告訴咱們「任何一個分佈式系統都沒法同時知足一致性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance),最多隻能同時知足兩項。」因此,不少系統在設計之初就要對這三者作出取捨。在互聯網領域的絕大多數的場景中,都須要犧牲強一致性來換取系統的高可用性,系統每每只須要保證「最終一致性」,只要這個最終時間是在用戶能夠接受的範圍內便可。java

在不少場景中,咱們爲了保證數據的最終一致性,須要不少的技術方案來支持,好比分佈式事務、分佈式鎖等。有的時候,咱們須要保證一個方法在同一時間內只能被同一個線程執行。redis

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

儘管有這三種方案,可是不一樣的業務也要根據本身的狀況進行選型,他們之間沒有最好只有更適合!但因爲操做數據庫須要必定的開銷,會照成必定的性能問題。而且使用數據庫行級鎖並不必定靠譜,尤爲是當咱們鎖表並不大的時候,因此實際開發過程當中使用Redis或Zookeeper的比較多。數據庫

分佈式鎖特性

當咱們在設計分佈式鎖的時候,咱們應該考慮分佈式鎖至少要知足的一些條件,同時考慮如何高效的設計分佈式鎖,這裏我認爲如下幾點是必需要考慮的。api

一、互斥

在分佈式高併發的條件下,咱們最須要保證,同一時刻只能有一個線程得到鎖,這是最基本的一點。緩存

二、防死鎖

在分佈式高併發的條件下,好比有個線程得到鎖的同時,尚未來得及去釋放鎖,就由於系統故障或者其它緣由使它沒法執行釋放鎖的命令,致使其它線程都沒法得到鎖,形成死鎖。因此分佈式很是有必要設置鎖的有效時間,確保系統出現故障後,在必定時間內可以主動去釋放鎖,避免形成死鎖的狀況。服務器

三、性能

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

四、重入

咱們知道ReentrantLock是可重入鎖,那它的特色就是:同一個線程能夠重複拿到同一個資源的鎖。重入鎖很是有利於資源的高效利用。關於這點以後會作演示。針對以上Redisson都能很好的知足,下面就來分析下它。app

基於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

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息