人妖在线一区,国产日韩欧美一区二区综合在线,国产啪精品视频网站免费,欧美内射深插日本少妇

新聞動(dòng)態(tài)

帶你輕松掌握Redis分布式鎖

發(fā)布日期:2022-01-29 14:48 | 文章來(lái)源:站長(zhǎng)之家

目前很多大型網(wǎng)站及應(yīng)用都是分布式部署的,分布式場(chǎng)景中的數(shù)據(jù)一致性問(wèn)題一直是一個(gè)比較重要的話題。

基于 CAP理論,任何一個(gè)分布式系統(tǒng)都無(wú)法同時(shí)滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯(cuò)性(Partition tolerance),最多只能同時(shí)滿足兩項(xiàng)。

我們?yōu)榱吮WC數(shù)據(jù)的最終一致性,需要很多的技術(shù)方案來(lái)支持,比如分布式事務(wù)、分布式鎖等。通常大家都會(huì)采redis做分布式鎖,但這樣就可以高枕無(wú)憂了嗎?

1. 什么是分布式鎖

分布式與單機(jī)情況下最大的不同在于其不是多線程而是多進(jìn)程,而數(shù)據(jù)只有一份(或有限制),也就是說(shuō)單機(jī)的共享內(nèi)存已解決不了一致性寫問(wèn)題,此時(shí)需要利用鎖的技術(shù)控制某一時(shí)刻修改數(shù)據(jù)的進(jìn)程數(shù)。

當(dāng)在分布式模型下,分布式鎖還是可以將標(biāo)記存在內(nèi)存,只是該內(nèi)存不是某個(gè)進(jìn)程分配的內(nèi)存而是公共內(nèi)存(Redis、Memcache)。至于利用數(shù)據(jù)庫(kù)、文件等做鎖與單機(jī)的實(shí)現(xiàn)是一樣的,只要保證標(biāo)記能互斥就行。

2. 分布式鎖該具備的特性

  • 最好是可重入鎖(避免死鎖)
  • 最好是一把阻塞鎖(根據(jù)業(yè)務(wù)需求決定)
  • 最好是一把公平鎖(根據(jù)業(yè)務(wù)需求決定)
  • 有高可用、高性能的獲取鎖和釋放鎖功能

3. 基于數(shù)據(jù)庫(kù)做分布式鎖

  • 基于樂(lè)觀鎖,CAS,但如果是insert的情況采用主鍵沖突防重,在大并發(fā)情況下有可能會(huì)造成鎖表現(xiàn)象
  • 基于悲觀鎖,也就是排他鎖,會(huì)有各種各樣的問(wèn)題(操作數(shù)據(jù)庫(kù)需要一定的開(kāi)銷,使用數(shù)據(jù)庫(kù)的行級(jí)鎖并不一定靠譜,性能不靠譜)

如果按分布式該具備的特性來(lái)逐條匹配,特別是高可用(存在單點(diǎn))、高性能是硬傷

4. 基于Redis做分布式鎖

一般都使用 setnx(set if not exists) 指令,只允許被一個(gè)客戶端占有,先來(lái)先得, 用完后再通過(guò) del 指令釋放。

如果中間邏輯執(zhí)行時(shí)發(fā)生異常,可能會(huì)導(dǎo)致 del 指令沒(méi)有被執(zhí)行,這樣就會(huì)陷入死鎖,怎么破?

對(duì),給鎖加個(gè)過(guò)期時(shí)間(即使出現(xiàn)異常也可以保證幾秒之后鎖會(huì)自動(dòng)釋放)!

但setnx 和 expire 之間redis服務(wù)器突然掛掉,怎么破?

其實(shí)該問(wèn)題的根源就在于 setnx 和 expire 是兩條指令而不是原子指令。為了解決這個(gè)疑難,Redis 開(kāi)源社區(qū)涌現(xiàn)了一堆分布式鎖的 解決方案。為了治理這個(gè)亂象,Redis 2.8 版本中加入了 set 指令的擴(kuò)展參數(shù),使得 setnx 和 expire 指令可以一起執(zhí)行,徹底解決了分布式鎖的亂象。

總之,setnx 和 expire 組合就是分布式鎖的奧義所在。

4.1 超時(shí)問(wèn)題

如果在加鎖和釋放鎖之間的邏輯執(zhí)行的太長(zhǎng),超出了超時(shí)限制,怎么破?

也就是說(shuō)第一個(gè)線程持有的鎖過(guò)期了但臨界區(qū)的邏輯還沒(méi)有執(zhí)行完,這個(gè)時(shí)候第二個(gè)線程就提前重新持有了這把鎖,導(dǎo)致每個(gè)請(qǐng)求執(zhí)行臨界區(qū)代碼時(shí)不能嚴(yán)格的串行執(zhí)行。

Redis 的分布式鎖不能解決超時(shí)問(wèn)題,建議分布式鎖不要用于較長(zhǎng)時(shí)間的任務(wù)。

稍微安全一點(diǎn)的方案是為 set 指令的 value 參數(shù)設(shè)置為一個(gè)隨機(jī)數(shù),釋放鎖時(shí)先匹配隨機(jī)數(shù)是否一致,一致的話再刪除 key,這是可以確保當(dāng)前線程占有的鎖不會(huì)被其它線程釋放,但是并不能解決鎖被redis服務(wù)器自動(dòng)釋放的。

int tag = random.nextint()//隨機(jī)數(shù)
boolean nx=true;
int ex=5;
if(redis.set(key, tag, nx, ex)){
    do_something()
    redis.delifequals(key, tag)//不存在這樣的命令
}

但是匹配 value 和刪除 key 不是一個(gè)原子操作,怎么破?

需要使用 Lua 腳本來(lái)處理了,因?yàn)?Lua 腳本可以保證連續(xù)多個(gè)指令的原子性執(zhí)行。

#delifequals.lua文件,下面的是社區(qū)熱門代碼
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end
//java調(diào)用
public void delifequals(){
    String script = readScript("delifequals.lua");
    int tag = 5;
    String key = "key";
    Object eval = jedis.eval(script, Lists.newArrayList(key), Lists.newArrayList(tag));
    System.out.println(eval);
}

4.2 可重入鎖

redis有類似Java 語(yǔ)言里有個(gè) ReentrantLock 就是可重入鎖嗎?

要支持可重入,需要對(duì)jedis 的 set 方法進(jìn)行包裝,思路是:使用 Threadlocal 存儲(chǔ)當(dāng)前持有鎖的計(jì)數(shù)??芍厝腈i加重了客戶端的復(fù)雜性,精確一點(diǎn)還需要考慮內(nèi)存鎖計(jì)數(shù)的過(guò)期時(shí)間,代碼復(fù)雜度將會(huì)繼續(xù)升高。

public class JedisWithReentrantLock {
    private Jedis jedis;
    /**
     * 當(dāng)前線程的鎖及計(jì)數(shù)
     */
    private ThreadLocal<Map<String, Integer>> lockers = new ThreadLocal<>();
    public JedisWithReentrantLock(Jedis jedis) {
        this.jedis = jedis;
    }
    private boolean set(String key) {
        return jedis.set(key, "", "nx", "ex", 5L) != null;
    }
    private void del(String key) {
        jedis.del(key);
    }
    private Map<String, Integer> getLockers() {
        Map<String, Integer> refs = lockers.get();
        if (refs != null) {
            return refs;
        }
        lockers.set(Maps.newHashMap());
        return lockers.get();
    }
 
    public boolean lock(String key) {
        Map<String, Integer> refs = getLockers();
        Integer refCount = refs.get(key);
        if (refCount != null) {
            refs.put(key, refCount + 1);
            return true;
        }
        if (!this.set(key)) {
            return false;
        }
        refs.put(key, 1);
        return true;
    }
 
    public boolean unlock(String key) {
        Map<String, Integer> refs = getLockers();
        Integer refCount = refs.get(key);
        if (refCount == null) {
            return false;
        }
        refCount -= 1;
        if (refCount > 0) {
            refs.put(key, refCount);
        } else {
            refs.remove(key);
            this.del(key);
        }
        return true;
    }
}
    @Test
    public void runJedisWithReentrantLock() {
        JedisWithReentrantLock redis = new JedisWithReentrantLock(jedis);
        System.out.println(redis.lock("alex"));
        System.out.println(redis.lock("alex"));
        System.out.println(redis.unlock("alex"));
        System.out.println(redis.unlock("alex"));
    }

4.3 集群環(huán)境的缺陷

在集群環(huán)境下,這種方式是有缺陷的(數(shù)據(jù)不一致的情況)。比如在 Sentinel 集群中,主節(jié)點(diǎn)掛掉時(shí)(原先第一個(gè)客戶端在主節(jié)點(diǎn)中申請(qǐng)成功了一把鎖),從節(jié)點(diǎn)A 會(huì)取而代之并晉升為主(但是這把鎖還沒(méi)有來(lái)得及同步),雖然客戶端上卻并沒(méi)有明顯感知,但是這時(shí)另一個(gè)客戶端過(guò)來(lái)請(qǐng)求 從節(jié)點(diǎn)A 可以成功加鎖,這樣就會(huì)導(dǎo)致系統(tǒng)中同樣一把鎖被兩個(gè)客戶端同時(shí)持有。

主從發(fā)生故障轉(zhuǎn)移,一般持續(xù)時(shí)間極短,數(shù)據(jù)不一致的情況基本上都是小概率事件。

4.4 Redlock

上面的集群同步問(wèn)題導(dǎo)致的缺陷,難道就沒(méi)有解決方案嗎?

為此Antirez 發(fā)明了 Redlock 算法,它的流程比較復(fù)雜,不過(guò)已經(jīng)有了很多開(kāi)源的實(shí)現(xiàn)。

原理

使用 Redlock,需要提供多個(gè) Redis 實(shí)例,這些實(shí)例之前相互獨(dú)立沒(méi)有主從關(guān)系。同很多分布式算法一樣,redlock 也使用少數(shù)服從多數(shù)。

加鎖時(shí),它會(huì)向過(guò)半節(jié)點(diǎn)發(fā)送 set(key, value, nx, ex) 指令,只要過(guò)半節(jié)點(diǎn) set 成功,那就認(rèn)為加鎖成功。釋放鎖時(shí),需要向所有節(jié)點(diǎn)發(fā)送 del 指令。缺陷:因?yàn)?Redlock 需要向多個(gè)節(jié)點(diǎn)進(jìn)行讀寫,意味著相比單實(shí)例 Redis 性能會(huì)下降一些。

注:Redlock算法還需要考慮出錯(cuò)重試、時(shí)鐘漂移等很多細(xì)節(jié)問(wèn)題

使用場(chǎng)景

如果你很在乎高可用性,希望掛了一臺(tái) redis 完全不受影響,那就應(yīng)該考慮 redlock。

引用資料

How to do distributed locking

Redlock的實(shí)現(xiàn)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持本站。

香港服務(wù)器租用

版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。

相關(guān)文章

實(shí)時(shí)開(kāi)通

自選配置、實(shí)時(shí)開(kāi)通

免備案

全球線路精選!

全天候客戶服務(wù)

7x24全年不間斷在線

專屬顧問(wèn)服務(wù)

1對(duì)1客戶咨詢顧問(wèn)

在線
客服

在線客服:7*24小時(shí)在線

客服
熱線

400-630-3752
7*24小時(shí)客服服務(wù)熱線

關(guān)注
微信

關(guān)注官方微信
頂部