日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

redis分布式锁,面试官请随便问,我都会

發(fā)布時(shí)間:2025/3/16 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 redis分布式锁,面试官请随便问,我都会 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
文章有點(diǎn)長(zhǎng)并且繞,先來(lái)個(gè)圖片緩沖下!

前言

現(xiàn)在的業(yè)務(wù)場(chǎng)景越來(lái)越復(fù)雜,使用的架構(gòu)也就越來(lái)越復(fù)雜,分布式、高并發(fā)已經(jīng)是業(yè)務(wù)要求的常態(tài)。像騰訊系的不少服務(wù),還有CDN優(yōu)化、異地多備份等處理。
說到分布式,就必然涉及到分布式鎖的概念,如何保證不同機(jī)器不同線程的分布式鎖同步呢?

實(shí)現(xiàn)要點(diǎn)

  • 互斥性,同一時(shí)刻,只能有一個(gè)客戶端持有鎖。
  • 防止死鎖發(fā)生,如果持有鎖的客戶端崩潰沒有主動(dòng)釋放鎖,也要保證鎖可以正常釋放及其他客戶端可以正常加鎖。
  • 加鎖和釋放鎖必須是同一個(gè)客戶端。
  • 容錯(cuò)性,只有redis還有節(jié)點(diǎn)存活,就可以進(jìn)行正常的加鎖解鎖操作。
  • 正確的redis分布式鎖實(shí)現(xiàn)

    錯(cuò)誤加鎖方式

    錯(cuò)誤方式一

    保證互斥和防止死鎖,首先想到的使用redis的setnx命令保證互斥,為了防止死鎖,鎖需要設(shè)置一個(gè)超時(shí)時(shí)間。public?static?void?wrongLock(Jedis?jedis,?String?key,?String?uniqueId,?int?expireTime)?{
    Long?result?=?jedis.setnx(key,?uniqueId);
    if?(1?==?result)?{
    //如果該redis實(shí)例崩潰,那就無(wú)法設(shè)置過期時(shí)間了
    jedis.expire(key,?expireTime);
    }
    }
    在多線程并發(fā)環(huán)境下,任何非原子性的操作,都可能導(dǎo)致問題。這段代碼中,如果設(shè)置過期時(shí)間時(shí),redis實(shí)例崩潰,就無(wú)法設(shè)置過期時(shí)間。如果客戶端沒有正確的釋放鎖,那么該鎖(永遠(yuǎn)不會(huì)過期),就永遠(yuǎn)不會(huì)被釋放。

    錯(cuò)誤方式二

    比較容易想到的就是設(shè)置值和超時(shí)時(shí)間為原子原子操作就可以解決問題。那使用setnx命令,將value設(shè)置為過期時(shí)間不就ok了嗎?public?static?boolean?wrongLock(Jedis?jedis,?String?key,?int?expireTime)?{
    long?expireTs?=?System.currentTimeMillis()?+?expireTime;
    //?鎖不存在,當(dāng)前線程加鎖成果
    if?(jedis.setnx(key,?String.valueOf(expireTs))?==?1)?{
    return?true;
    }

    String?value?=?jedis.get(key);
    //如果當(dāng)前鎖存在,且鎖已過期
    if?(value?!=?null?&&?NumberUtils.toLong(value)?<?System.currentTimeMillis())?{
    //鎖過期,設(shè)置新的過期時(shí)間
    String?oldValue?=?jedis.getSet(key,?String.valueOf(expireTs));
    if?(oldValue?!=?null?&&?oldValue.equals(value))?{
    //?多線程并發(fā)下,只有一個(gè)線程會(huì)設(shè)置成功
    //?設(shè)置成功的這個(gè)線程,key的舊值一定和設(shè)置之前的key的值一致
    return?true;
    }
    }
    //?其他情況,加鎖失敗
    return?true;
    }
    乍看之下,沒有什么問題。但仔細(xì)分析,有如下問題:
  • value設(shè)置為過期時(shí)間,就要求各個(gè)客戶端嚴(yán)格的時(shí)鐘同步,這就需要使用到同步時(shí)鐘。即使有同步時(shí)鐘,分布式的服務(wù)器一般來(lái)說時(shí)間肯定是存在少許誤差的。
  • 鎖過期時(shí),使用 jedis.getSet雖然可以保證只有一個(gè)線程設(shè)置成功,但是不能保證加鎖和解鎖為同一個(gè)客戶端,因?yàn)闆]有標(biāo)志鎖是哪個(gè)客戶端設(shè)置的嘛。
  • 錯(cuò)誤解鎖方式

    解鎖錯(cuò)誤方式一

    直接刪除keypublic?static?void?wrongReleaseLock(Jedis?jedis,?String?key)?{
    //不是自己加鎖的key,也會(huì)被釋放
    jedis.del(key);
    }
    簡(jiǎn)單粗暴,直接解鎖,但是不是自己加鎖的,也會(huì)被刪除,這好像有點(diǎn)太隨意了吧!

    解鎖錯(cuò)誤方式二

    判斷自己是不是鎖的持有者,如果是,則只有持有者才可以釋放鎖。public?static?void?wrongReleaseLock(Jedis?jedis,?String?key,?String?uniqueId)?{
    if?(uniqueId.equals(jedis.get(key)))?{
    //?如果這時(shí)鎖過期自動(dòng)釋放,又被其他線程加鎖,該線程就會(huì)釋放不屬于自己的鎖
    jedis.del(key);
    }
    }
    看起來(lái)很完美啊,但是如果你判斷的時(shí)候鎖是自己持有的,這時(shí)鎖超時(shí)自動(dòng)釋放了。然后又被其他客戶端重新上鎖,然后當(dāng)前線程執(zhí)行到j(luò)edis.del(key),這樣這個(gè)線程不就刪除了其他線程上的鎖嘛,好像有點(diǎn)亂套了哦!

    正確加鎖釋放鎖方式

    基本上避免了以上幾種錯(cuò)誤方式之外,就是正確的方式了。要滿足以下幾個(gè)條件:
  • 命令必須保證互斥
  • 設(shè)置的key必須要有過期時(shí)間,防止崩潰時(shí)鎖無(wú)法釋放
  • value使用唯一id標(biāo)志每個(gè)客戶端,保證只有鎖的持有者才可以釋放鎖
  • 加鎖直接使用set命令同時(shí)設(shè)置唯一id和過期時(shí)間;其中解鎖稍微復(fù)雜些,加鎖之后可以返回唯一id,標(biāo)志此鎖是該客戶端鎖擁有;釋放鎖時(shí)要先判斷擁有者是否是自己,然后刪除,這個(gè)需要redis的lua腳本保證兩個(gè)命令的原子性執(zhí)行。
    下面是具體的加鎖和釋放鎖的代碼:@Slf4j
    public?class?RedisDistributedLock?{
    private?static?final?String?LOCK_SUCCESS?=?"OK";
    private?static?final?Long?RELEASE_SUCCESS?=?1L;
    private?static?final?String?SET_IF_NOT_EXIST?=?"NX";
    private?static?final?String?SET_WITH_EXPIRE_TIME?=?"PX";
    //?鎖的超時(shí)時(shí)間
    private?static?int?EXPIRE_TIME?=?5?*?1000;
    //?鎖等待時(shí)間
    private?static?int?WAIT_TIME?=?1?*?1000;

    private?Jedis?jedis;
    private?String?key;

    public?RedisDistributedLock(Jedis?jedis,?String?key)?{
    this.jedis?=?jedis;
    this.key?=?key;
    }

    //?不斷嘗試加鎖
    public?String?lock()?{
    try?{
    //?超過等待時(shí)間,加鎖失敗
    long?waitEnd?=?System.currentTimeMillis()?+?WAIT_TIME;
    String?value?=?UUID.randomUUID().toString();
    while?(System.currentTimeMillis()?<?waitEnd)?{
    String?result?=?jedis.set(key,?value,?SET_IF_NOT_EXIST,?SET_WITH_EXPIRE_TIME,?EXPIRE_TIME);
    if?(LOCK_SUCCESS.equals(result))?{
    return?value;
    }
    try?{
    Thread.sleep(10);
    }?catch?(InterruptedException?e)?{
    Thread.currentThread().interrupt();
    }
    }
    }?catch?(Exception?ex)?{
    log.error("lock?error",?ex);
    }
    return?null;
    }

    public?boolean?release(String?value)?{
    if?(value?==?null)?{
    return?false;
    }
    //?判斷key存在并且刪除key必須是一個(gè)原子操作
    //?且誰(shuí)擁有鎖,誰(shuí)釋放
    String?script?=?"if?redis.call('get',?KEYS[1])?==?ARGV[1]?then?return?redis.call('del',?KEYS[1])?else?return?0?end";
    Object?result?=?new?Object();
    try?{
    result?=?jedis.eval(script,?Collections.singletonList(key),
    Collections.singletonList(value));
    if?(RELEASE_SUCCESS.equals(result))?{
    log.info("release?lock?success,?value:{}",?value);
    return?true;
    }
    }?catch?(Exception?e)?{
    log.error("release?lock?error",?e);
    }?finally?{
    if?(jedis?!=?null)?{
    jedis.close();
    }
    }
    log.info("release?lock?failed,?value:{},?result:{}",?value,?result);
    return?false;
    }
    }
    單是一個(gè)redis的分布式鎖就有這么多道道,不知道你是否看明白了?留言討論下吧!

    有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)

    歡迎大家關(guān)注Java之道公眾號(hào)

    好文章,我在看??

    總結(jié)

    以上是生活随笔為你收集整理的redis分布式锁,面试官请随便问,我都会的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。