Redis實現(xiàn)分布式鎖(入門)

單機版redis解決方案(redis集群還在學)
此博客解決方案適用于絕大部分業(yè)務場景在跳,不能容忍任何一點的race condition(如錢相關的業(yè)務)并不適用
原理分析參考另一篇博客

什么分布式鎖?
  • 本地鎖:在多個線程中政己,保證只有一個線程執(zhí)行(線程安全的問題)

  • 分布鎖:在分布式中艇潭,保證只有一個jvm執(zhí)行(多個jvm線程安全問題)
    如果我們服務器是集群的時候,定時任務可能會重復執(zhí)行 可以采用分布式鎖解決

分布式鎖解決方案:
  • 基于數(shù)據(jù)庫方式實現(xiàn)
  • 基于Zk方式實現(xiàn) 采用臨時節(jié)點+事件通知
  • 基于Redis方式實現(xiàn) setnx 方式
解決分布式鎖核心思路:
  • 獲取鎖
    多個不同的jvm 同時創(chuàng)建一個相同的標記(全局唯一的) 只要誰能夠創(chuàng)建成功誰就能夠獲取鎖
  • 釋放鎖
    釋放該全局唯一的標記宜岛,其他的jvm重新進入到獲取鎖資源。
  • 超時鎖(沒有獲取鎖、已經(jīng)獲取鎖)
    等待獲取鎖的超時時間
    已經(jīng)獲取到鎖 鎖的有效期 5s
分析:基于Redis實現(xiàn)分布式鎖思路
  • 獲取鎖
    多個不同的jvm 同時創(chuàng)建一個相同的標記使用Setnx命令界阁,因為Rediskey必須保證是唯一的,只要誰能夠創(chuàng)建成功誰就能夠獲取鎖
    Set命令的時候:如果key不存在則創(chuàng)建胖喳,如果key已經(jīng)存在則修改原值泡躯;
    SetNx命令: 如果key不存在則創(chuàng)建 返回1,如果已經(jīng)存在則不執(zhí)行任何操作返回0
    1 不存在創(chuàng)建成功 0 已經(jīng)存在 不執(zhí)行任何操作。

  • 釋放鎖
    對我們的redis的key設置一個有效期(或者是主動刪除該key)可以靈活的自動的釋放該全局唯一的標記,其他的jvm重新進入到獲取鎖資源较剃。

  • 超時鎖(沒有獲取鎖咕别、已經(jīng)獲取鎖)
    等待獲取鎖的超時時間
    已經(jīng)獲取到鎖 鎖的有效期 5s

分析基于Zk實現(xiàn)分布式鎖思路
  • 獲取鎖
    多個不同的jvm在zk集群上創(chuàng)建一個相同的全局唯一的臨時路徑,只要誰能夠創(chuàng)建成功誰就能夠獲取到鎖写穴。
    分析:臨時節(jié)點對我們節(jié)點設置有效期

  • 釋放鎖
    人為主動刪除該節(jié)點或者使用Session有效期

  • 超時鎖(沒有獲取鎖惰拱、已經(jīng)獲取鎖)
    等待獲取鎖的超時時間
    已經(jīng)獲取到鎖 鎖的有效期 5s

代碼實現(xiàn)
  • maven依賴
   <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>2.9.0</version>
    </dependency>
    <!--用于判斷字符串是否為空,測試類用到了-->
    <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.9</version>
    </dependency>
  • redis工具類配置redis
public class RedisUtil {
    //protected static Logger logger = Logger.getLogger(RedisUtil.class);
    
    private static String IP = "你自己的redis IP";

    //Redis的端口號
    private static int PORT = 6379;

    //可用連接實例的最大數(shù)目啊送,默認值為8偿短;
    //如果賦值為-1,則表示不限制馋没;如果pool已經(jīng)分配了maxActive個jedis實例昔逗,則此時pool的狀態(tài)為exhausted(耗盡)。
    private static int MAX_ACTIVE = 100;

    //控制一個pool最多有多少個狀態(tài)為idle(空閑的)的jedis實例篷朵,默認值也是8勾怒。
    private static int MAX_IDLE = 20;

    //等待可用連接的最大時間,單位毫秒款票,默認值為-1控硼,表示永不超時。如果超過等待時間艾少,則直接拋出JedisConnectionException卡乾;
    private static int MAX_WAIT = 3000;

    private static int TIMEOUT = 3000;

    //在borrow一個jedis實例時,是否提前進行validate操作缚够;如果為true幔妨,則得到的jedis實例均是可用的;
    private static boolean TEST_ON_BORROW = true;

    //在return給pool時谍椅,是否提前進行validate操作误堡;
    private static boolean TEST_ON_RETURN = true;

    private static JedisPool jedisPool = null;

    /**
     * redis過期時間,以秒為單位
     */
    public final static int EXRP_HOUR = 60 * 60; //一小時
    public final static int EXRP_DAY = 60 * 60 * 24; //一天
    public final static int EXRP_MONTH = 60 * 60 * 24 * 30; //一個月

    /**
     * 初始化Redis連接池
     */
    private static void initialPool() {
        try {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);

            jedisPool = new JedisPool(config, IP, PORT, TIMEOUT);
          //有密碼用下面這種構造方法
          // jedisPool = new JedisPool(config, IP, PORT, TIMEOUT, "123456");
        } catch (Exception e) {
            //logger.error("First create JedisPool error : "+e);
            e.getMessage();
        }
    }

    /**
     * 在多線程環(huán)境同步初始化
     */
    private static synchronized void poolInit() {
        if (jedisPool == null) {
            initialPool();
        }
    }

    /**
     * 同步獲取Jedis實例
     *
     * @return Jedis
     */
    public synchronized static Jedis getJedis() {
        if (jedisPool == null) {
            poolInit();
        }
        Jedis jedis = null;
        try {
            if (jedisPool != null) {
                jedis = jedisPool.getResource();
            }
        } catch (Exception e) {
            e.getMessage();
            // logger.error("Get jedis error : "+e);
        }
        return jedis;
    }

    /**
     * 釋放jedis資源
     *
     * @param jedis
     */
    public static void returnResource(final Jedis jedis) {
        if (jedis != null && jedisPool != null) {
            jedisPool.returnResource(jedis);
        }
    }

    public static Long sadd(String key, String... members) {
        Jedis jedis = null;
        Long res = null;
        try {
            jedis = getJedis();
            res = jedis.sadd(key, members);
        } catch (Exception e) {
            //logger.error("sadd  error : "+e);
            e.getMessage();
        }
        return res;
    }
}
  • 分布式鎖實現(xiàn)工具類
public class RedisLock {

    private static int lockSuccss = 1;

    /**
     * @param lockKey      在Redis中創(chuàng)建的key值
     * @param notLockTime  嘗試獲取鎖超時時間
     * @return 返回lock成功值
     */
    public String getLock(String lockKey,int notLockTime, int timeOut){
        //獲取Redis連接
        Jedis jedis=RedisUtil.getJedis();
        //計算超時時間
        long endTime = System.currentTimeMillis() + notLockTime;
        try {
            //當前系統(tǒng)時間小于endTime說明獲取鎖沒有超時
            while (System.currentTimeMillis()<endTime){
                String lockValue = UUID.randomUUID().toString();
                // 當多個不同的jvm同時創(chuàng)建一個相同的rediskey 只要誰能夠創(chuàng)建成功誰就能夠獲取鎖
                if(jedis.setnx(lockKey,lockValue)==1){
                    jedis.expire(lockKey,timeOut/1000);
                    return lockValue;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if(jedis!=null){
                jedis.close();
            }
        }
        return null;
    }

    /**
     * 釋放鎖
     * @return
     */
    public boolean unLock(String lockKey,String lockValue){
        //獲取Redis連接
        Jedis jedis=RedisUtil.getJedis();
        try {
            // 判斷獲取鎖的時候保證自己刪除自己(防止空指針將lockValue寫前面,因為redis可能獲取到空值)
            if(lockValue.equals(jedis.get(lockKey))){
                return jedis.del(lockKey)>0 ? true:false;
            }
        }
        catch (Exception e){
            e.printStackTrace();
        }finally {
            if (jedis != null) {
                jedis.close();
            }
        }
        return false;
    }
}

  • 測試類
  public class TestService {

    private static final String LOCKKEY = "lock";

    public static void service() {
        // 1.獲取鎖
        RedisLock mayiktRedisLock = new RedisLock();
        String lockValue = mayiktRedisLock.getLock(LOCKKEY, 5000, 5000);
        if (StringUtils.isEmpty(lockValue)) {
            System.out.println(Thread.currentThread().getName() + "雏吭,獲取鎖失敗了");
            return;
        }
        // 執(zhí)行我們的業(yè)務邏輯
        System.out.println(Thread.currentThread().getName() + "锁施,獲取鎖成功:lockValue:" + lockValue);

        // 3.釋放鎖(設置了失效時間,不釋放也不會出現(xiàn)死鎖)
        mayiktRedisLock.unLock(LOCKKEY, lockValue);
    }

    public static void main(String[] args) {
        service();
    }

    /***
     *
     * 嘗試獲取鎖為什么次數(shù)限制杖们?
     * 如果我們業(yè)務邏輯5s 內(nèi)沒有執(zhí)行完畢呢悉抵?
     *
     * 分場景:
     * 1.鎖的超時時間根據(jù)業(yè)務場景來預估
     * 2.可以自己延遲鎖的時間
     * 3.在提交事務的時候檢查鎖是否已經(jīng)超時 如果已經(jīng)超時則回滾(手動回滾)否則提交。
     *
     * 僅限于單機版本
     */
}

From 螞蟻課堂

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末摘完,一起剝皮案震驚了整個濱河市姥饰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌孝治,老刑警劉巖列粪,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件审磁,死亡現(xiàn)場離奇詭異,居然都是意外死亡岂座,警方通過查閱死者的電腦和手機态蒂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掺逼,“玉大人吃媒,你說我怎么就攤上這事÷来” “怎么了赘那?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長氯质。 經(jīng)常有香客問我募舟,道長,這世上最難降的妖魔是什么闻察? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任拱礁,我火速辦了婚禮,結果婚禮上辕漂,老公的妹妹穿的比我還像新娘呢灶。我一直安慰自己,他們只是感情好钉嘹,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布鸯乃。 她就那樣靜靜地躺著,像睡著了一般跋涣。 火紅的嫁衣襯著肌膚如雪缨睡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天陈辱,我揣著相機與錄音奖年,去河邊找鬼。 笑死沛贪,一個胖子當著我的面吹牛陋守,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播利赋,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼水评,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了隐砸?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蝙眶,失蹤者是張志新(化名)和其女友劉穎季希,沒想到半個月后褪那,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡式塌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年博敬,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片峰尝。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡偏窝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出武学,到底是詐尸還是另有隱情祭往,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布火窒,位于F島的核電站硼补,受9級特大地震影響,放射性物質發(fā)生泄漏熏矿。R本人自食惡果不足惜已骇,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望票编。 院中可真熱鬧褪储,春花似錦、人聲如沸慧域。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吊趾。三九已至宛裕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間论泛,已是汗流浹背揩尸。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屁奏,地道東北人岩榆。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像坟瓢,于是被迫代替她去往敵國和親勇边。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容