成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

鎖和分布式鎖

codeGoogle / 700人閱讀

摘要:鎖的部分細(xì)節(jié)不同場(chǎng)景鎖的表現(xiàn)不同獨(dú)占共享讀寫(xiě)分布式鎖的簡(jiǎn)單實(shí)現(xiàn)分布式鎖實(shí)現(xiàn)的三個(gè)核心要素加鎖最簡(jiǎn)單的方法是使用命令。嘗試獲取分布式鎖客戶(hù)端鎖線(xiàn)程超期時(shí)間是否獲取成功釋放分布式鎖客戶(hù)端鎖請(qǐng)求標(biāo)識(shí)是否釋放成功

鎖的由來(lái)

多線(xiàn)程環(huán)境中,經(jīng)常遇到多個(gè)線(xiàn)程訪問(wèn)同一個(gè) 共享資源 ,這時(shí)候作為開(kāi)發(fā)者必須考慮如何維護(hù)數(shù)據(jù)一致性,這就需要某種機(jī)制來(lái)保證只有滿(mǎn)足某個(gè)條件(獲取鎖成功)的線(xiàn)程才能訪問(wèn)資源,而不滿(mǎn)足條件(獲取鎖失?。┑木€(xiàn)程只能等待,在下一輪競(jìng)爭(zhēng)中來(lái)獲取鎖才能訪問(wèn)資源。

兩個(gè)知識(shí)點(diǎn):

1.高級(jí)緩存Cache

CPU為了提高處理速度,不和內(nèi)存直接進(jìn)行交互,而是使用Cache。
可能引發(fā)的問(wèn)題:

如果多個(gè)處理器同時(shí)對(duì)共享變量進(jìn)行讀改寫(xiě)操作 (i++就是經(jīng)典的讀改寫(xiě)操作),那么共享變量就會(huì)被多個(gè)處理器同時(shí)進(jìn)行操作,這樣讀改寫(xiě)操作就不是原子的了,操作完之后共享變量的值會(huì)和期望的不一致。

造成此結(jié)果的原因:
多個(gè)處理器同時(shí)從各自的緩存中讀取變量i,分別進(jìn)行加1操作,然后分別寫(xiě)入 系統(tǒng)內(nèi)存中。
處理器層面的解決方案:
處理器使用總線(xiàn)鎖就是來(lái)解決這個(gè)問(wèn)題的。所謂總線(xiàn)鎖就是使用處理器提供的一個(gè) LOCK#信號(hào),當(dāng)一個(gè)處理器在總線(xiàn)上輸出此信號(hào)時(shí),其他處理器的請(qǐng)求將被阻塞住,那么該處理器可以獨(dú)占共享內(nèi)存。

2.CAS(Compare And Swap)+volatile
CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)。執(zhí)行CAS操作的時(shí)候,將內(nèi)存位置的值與預(yù)期原值比較,如果相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值。否則,處理器不做任何操作。
java的Atomic以及一些它自帶的類(lèi)中的cas操作都是通過(guò)借助cmpxchg指令完成的。他保證同一時(shí)刻只能有一個(gè)線(xiàn)程cas成功。
舉個(gè)例子
以AtomicIneger的源碼為例來(lái)看看CAS操作:


for(;;)表示循環(huán),只有當(dāng)if判斷為true才退出。而if判斷的內(nèi)容就是是否CAS成功。

volatile的作用:
1)將當(dāng)前處理器緩存行的數(shù)據(jù)寫(xiě)回到系統(tǒng)內(nèi)存。
2)這個(gè)寫(xiě)回內(nèi)存的操作會(huì)使在其他CPU里緩存了該內(nèi)存地址的數(shù)據(jù)無(wú)效。

循環(huán)CAS+volatile是實(shí)現(xiàn)鎖的關(guān)鍵。

Lock鎖的部分細(xì)節(jié)

不同場(chǎng)景鎖的表現(xiàn)不同:獨(dú)占?共享?讀寫(xiě)?

分布式鎖(redis的簡(jiǎn)單實(shí)現(xiàn))

分布式鎖實(shí)現(xiàn)的三個(gè)核心要素:

1.加鎖

最簡(jiǎn)單的方法是使用setnx命令。key是鎖的唯一標(biāo)識(shí),按業(yè)務(wù)來(lái)決定命名。比如想要給一種商品的秒殺活動(dòng)加鎖,可以給key命名為 “l(fā)ock_sale_商品ID” 。而value設(shè)置成什么呢?我們可以姑且設(shè)置成1。加鎖的偽代碼如下:
setnx(key,1)
SETNX key value

將 key 的值設(shè)為 value ,當(dāng)且僅當(dāng) key 不存在。

若給定的 key 已經(jīng)存在,則 SETNX 不做任何動(dòng)作。

SETNX 是『SET if Not eXists』(如果不存在,則 SET)的簡(jiǎn)寫(xiě)。
時(shí)間復(fù)雜度:
O(1)
返回值:
設(shè)置成功,返回 1 。
設(shè)置失敗,返回 0 。

當(dāng)一個(gè)線(xiàn)程執(zhí)行setnx返回1,說(shuō)明key原本不存在,該線(xiàn)程成功得到了鎖;當(dāng)一個(gè)線(xiàn)程執(zhí)行setnx返回0,說(shuō)明key已經(jīng)存在,該線(xiàn)程搶鎖失敗。

2.解鎖

有加鎖就得有解鎖。當(dāng)?shù)玫芥i的線(xiàn)程執(zhí)行完任務(wù),需要釋放鎖,以便其他線(xiàn)程可以進(jìn)入。釋放鎖的最簡(jiǎn)單方式是執(zhí)行del指令,偽代碼如下:

del(key)

釋放鎖之后,其他線(xiàn)程就可以繼續(xù)執(zhí)行setnx命令來(lái)獲得鎖。

3.設(shè)置超時(shí)時(shí)間

如果一個(gè)得到鎖的線(xiàn)程在執(zhí)行任務(wù)的過(guò)程中掛掉,來(lái)不及顯式地釋放鎖,這塊資源將會(huì)永遠(yuǎn)被鎖住,別的線(xiàn)程再也別想進(jìn)來(lái)。

所以,setnx的key必須設(shè)置一個(gè)超時(shí)時(shí)間,以保證即使沒(méi)有被顯式釋放,這把鎖也要在一定時(shí)間后自動(dòng)釋放。setnx不支持超時(shí)參數(shù),所以需要額外的指令,偽代碼如下:

expire(key, 30)

綜合起來(lái),我們分布式鎖實(shí)現(xiàn)的第一版?zhèn)未a如下:

if(setnx(key,1) == 1){
    expire(key,30)
    do something ......
    del(key)
    }

上述代碼的問(wèn)題:

1 setnx和expire的非原子性


setnx剛執(zhí)行成功,還未來(lái)得及執(zhí)行expire指令,節(jié)點(diǎn)1 Duang的一聲掛掉了。

這樣一來(lái),這個(gè)鎖就長(zhǎng)生不死了。
解決方案:
Redis 2.6.12以上版本為set指令增加了可選參數(shù),偽代碼如下:

set(key,1,30,NX)

2 del 導(dǎo)致誤刪




可以在del釋放鎖之前做一個(gè)判斷,驗(yàn)證當(dāng)前的鎖是不是自己加的鎖
至于具體的實(shí)現(xiàn),可以在加鎖的時(shí)候把當(dāng)前的線(xiàn)程ID當(dāng)做value,并在刪除之前驗(yàn)證key對(duì)應(yīng)的value是不是自己線(xiàn)程的ID。
加鎖:

String threadId = Thread.currentThread().getId()
set(key,threadId ,30,NX)

解鎖:

if(threadId .equals(redisClient.get(key))){
    del(key)
}

這樣做又隱含了一個(gè)新的問(wèn)題,判斷和釋放鎖是兩個(gè)獨(dú)立操作,不是原子性。
這一塊要用Lua腳本來(lái)實(shí)現(xiàn):

String luaScript = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end";

redisClient.eval(luaScript , Collections.singletonList(key), Collections.singletonList(threadId));

redis官方說(shuō):eval命令在執(zhí)行l(wèi)ua腳本時(shí)會(huì)當(dāng)作一個(gè)命令去執(zhí)行,并且直到命令執(zhí)行完成redis才會(huì)去執(zhí)行其他命令,所以就變成了一個(gè)原子操作。

3出現(xiàn)并發(fā)的可能性

進(jìn)程1在超時(shí)時(shí)間內(nèi)未執(zhí)行完代碼,此時(shí)進(jìn)程2是可以獲取鎖的,會(huì)出現(xiàn)兩個(gè)進(jìn)程同時(shí)訪問(wèn)一個(gè)資源的情況。
解決方案:可以在進(jìn)程1所在的jvm環(huán)境中開(kāi)一個(gè)線(xiàn)程專(zhuān)門(mén)用來(lái)“續(xù)命”,當(dāng)需要解鎖的時(shí)候,通知這個(gè)續(xù)命線(xiàn)程結(jié)束執(zhí)行。

private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * 嘗試獲取分布式鎖
     * @param jedis Redis客戶(hù)端
     * @param lockKey 鎖
     * @param requestId 線(xiàn)程Id
     * @param expireTime 超期時(shí)間
     * @return 是否獲取成功
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }
 private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 釋放分布式鎖
     * @param jedis Redis客戶(hù)端
     * @param lockKey 鎖
     * @param requestId 請(qǐng)求標(biāo)識(shí)
     * @return 是否釋放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        String script = "if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72228.html

相關(guān)文章

  • 布式spring事務(wù)管理

    摘要:否則數(shù)據(jù)會(huì)出現(xiàn)不同步問(wèn)題我使用的做分布式鎖管理,用注解事務(wù)管理。但是出現(xiàn)另外一個(gè)問(wèn)題,鎖超時(shí)但是事務(wù)仍未提交。 最近開(kāi)發(fā)一個(gè)小程序遇到一個(gè)需求需要實(shí)現(xiàn)分布式事務(wù)管理 業(yè)務(wù)需求 用戶(hù)在使用小程序的過(guò)程中可以查看景點(diǎn),對(duì)景點(diǎn)地區(qū)或者城市標(biāo)記是否想去,那么需要統(tǒng)計(jì)一個(gè)地點(diǎn)被標(biāo)記的人數(shù),以及記錄某個(gè)用戶(hù)對(duì)某個(gè)地點(diǎn)是否標(biāo)記為想去,用兩個(gè)表存儲(chǔ)數(shù)據(jù),一個(gè)地點(diǎn)表記錄改地點(diǎn)被標(biāo)記的次數(shù),一個(gè)用戶(hù)意向表...

    shinezejian 評(píng)論0 收藏0
  • 基于redis實(shí)現(xiàn)布式思考

    摘要:分布式鎖基于實(shí)現(xiàn)分布式鎖思考幾個(gè)問(wèn)題鎖為什么不能應(yīng)用于分布式鎖雖然能夠解決同步問(wèn)題,但是每次只有一個(gè)線(xiàn)程訪問(wèn),并且鎖屬于鎖,僅適用于單點(diǎn)部署然而分布式需要部署多臺(tái)實(shí)例,屬于不同的線(xiàn)程對(duì)象使用中實(shí)現(xiàn)分布式鎖。分布式鎖基于redis實(shí)現(xiàn)分布式鎖思考幾個(gè)問(wèn)題?synchronized鎖為什么不能應(yīng)用于分布式鎖?synchronized雖然能夠解決同步問(wèn)題,但是每次只有一個(gè)線(xiàn)程訪問(wèn),并且synchr...

    Tecode 評(píng)論0 收藏0
  • 基于 Zookeeper 的布式實(shí)現(xiàn)

    摘要:不過(guò)比較膚淺,為了進(jìn)一步加深對(duì)的認(rèn)識(shí),我利用空閑時(shí)間編寫(xiě)了本篇文章對(duì)應(yīng)的基于的分布式鎖實(shí)現(xiàn)。不過(guò)我所編寫(xiě)的分布式鎖還是比較簡(jiǎn)陋的,實(shí)現(xiàn)的也不夠優(yōu)美,僅僅是個(gè)練習(xí),僅供參考使用。好了,題外話(huà)就說(shuō)到這里,接下來(lái)我們就來(lái)聊聊基于的分布式鎖實(shí)現(xiàn)。 1. 背景 最近在學(xué)習(xí) Zookeeper,在剛開(kāi)始接觸 Zookeeper 的時(shí)候,完全不知道 Zookeeper 有什么用。且很多資料都是將 Z...

    邱勇 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<