<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Redis 實(shí)現(xiàn)分布式鎖演進(jìn)原理

          共 5548字,需瀏覽 12分鐘

           ·

          2021-07-20 11:33

          點(diǎn)擊上方「Java有貨」關(guān)注我們


          技術(shù)交流群添加方式


          +



          添加小編微信:372787553,備注:進(jìn)群
          帶您進(jìn)入Java技術(shù)交流群

          引言

          分布式鎖是一個(gè)老生常談的話題,但是如何寫好鎖,避免更多的坑,讓我一起從無到有的演進(jìn)一次!

          分布式鎖

          分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。

          在分布式系統(tǒng)中,常常需要協(xié)調(diào)他們的動(dòng)作。如果不同的系統(tǒng)或是同一個(gè)系統(tǒng)的不同主機(jī)之間共享了一個(gè)或一組資源,那么訪問這些資源的時(shí)候,往往需要互斥來防止彼此干擾來保證一致性,這個(gè)時(shí)候,便需要使用到分布式鎖

          無鎖的應(yīng)用

            @GetMapping(value = "test")    public void test() {        ReentrantLock reentrantLock = new ReentrantLock();        reentrantLock.lock();        try {            order();        }finally {            reentrantLock.unlock();        }    }

          我們?cè)陂_發(fā)應(yīng)用的時(shí)候,如果需要對(duì)某一個(gè)共享變量進(jìn)行多線程同步訪問的時(shí)候,可以使用我們學(xué)到的鎖進(jìn)行處理,并且可以完美的運(yùn)行,毫無Bug!

          但是如果是分布式環(huán)境下呢?這時(shí)就必須采用分布式鎖,才能保證所有服務(wù)操作資源的一致性,分布式鎖有很多種實(shí)現(xiàn)方式,如:數(shù)據(jù)庫、Redis、zookeeper....今天我們將介紹Redis實(shí)現(xiàn)分布式鎖的演進(jìn)流程,和其中的一些坑。

          Redis實(shí)現(xiàn)分布式鎖的演進(jìn)流程

          上面的代碼在分布式情況下肯定是有問題的,那我稍加調(diào)整一下,引入Redis

          第一次演進(jìn)

           @GetMapping(value = "test2")    public void test2() {        Boolean javayh = redisTemplate.opsForValue().setIfAbsent(RedisKey.key("redis-order-lock"), "javayh");        try {            if (javayh) {                order();            }//實(shí)現(xiàn)自旋            else {                test2();            }        } finally {            redisTemplate.delete(RedisKey.key("redis-order-lock"));        }    }

          大家看這段代碼有什么問題?

          在所有的一切都是按照我們的預(yù)期去執(zhí)行的,好像沒什么問題,但是并發(fā)下往往不會(huì)按照我的預(yù)期去執(zhí)行。

          問題

          在分布式中,其中一個(gè)線程得到了鎖,進(jìn)行執(zhí)行,其他的線程進(jìn)行不斷的嘗試獲取鎖,但是如果獲取到鎖的服務(wù)器掛了,沒有釋放鎖,這就會(huì)造成死鎖...

          第二次演進(jìn)

          上面的問題似乎出現(xiàn)了加鎖后,沒有執(zhí)行釋放鎖的代碼,那么我們是不是可以給鎖設(shè)置過期時(shí)間,實(shí)現(xiàn)到期自動(dòng)刪除

          @GetMapping(value = "test3")    public void test3() {        String key = RedisKey.key("redis-order-lock");        // 上面的代碼 沒有辦法釋放鎖,那好,我們給他指定失效時(shí)間,但是這里有沒有坑呢        Boolean javayh = redisTemplate.opsForValue().setIfAbsent(key, "javayh");        try {            redisTemplate.expire(key, 30, TimeUnit.SECONDS);            if (javayh) {                order();            }//實(shí)現(xiàn)自旋            else {                test2();            }        } finally {            redisTemplate.delete(key);        }    }

          大家看這段代碼有什么問題?

          問題

          但是就像之前一下,在沒有執(zhí)行給鎖設(shè)置過期時(shí)間,服務(wù)器就掛了呢?是不是也會(huì)造成死鎖。也就是說,上下兩個(gè)操作不是原子的操作。

          第三次演進(jìn)

          知道了問題所在我們繼續(xù)改。

           @GetMapping(value = "test4")    public void test4() {        // 上面的代碼 沒有辦法釋放鎖,那我們將加鎖和設(shè)置失效時(shí)間的代碼放在一起就可以        // 這樣好像看似沒什么問題了,但是確實(shí)是這樣嗎?        String key = RedisKey.key("redis-order-lock");        Boolean javayh = redisTemplate.opsForValue().setIfAbsent(key, "javayh", 30, TimeUnit.SECONDS);        try {            if (javayh) {                order();            }//實(shí)現(xiàn)自旋            else {                test4();            }        } finally {           redisTemplate.delete(key);                   }    }

          大家看這段代碼有什么問題?

          問題

          我們?cè)賮矸治鲆幌拢杭偃?/span>這是有三個(gè)線程,其中一個(gè)線程獲取了鎖,執(zhí)行了起來,但是性能很慢,超過了我們的過期時(shí)間, 這時(shí)鎖已經(jīng)被釋放,其他線程就可以進(jìn)行獲取鎖,但是當(dāng)?shù)谝粋€(gè)線程執(zhí)行完業(yè)務(wù)邏輯,想要?jiǎng)h除這把鎖、,這時(shí)就會(huì)把其他線程鎖住的資源進(jìn)行釋放了,這也是坑根據(jù)上面的分析,我們可以將key重新設(shè)置一下,修改后的代碼

          第四次演進(jìn)

           @GetMapping(value = "test4")    public void test4() {        // 重新生成的key        String key = RedisKey.key("redis-order-lock") + UUID.randomUUID().toString();        Boolean javayh = redisTemplate.opsForValue().setIfAbsent(key, "javayh", 30, TimeUnit.SECONDS);        try {            if (javayh) {                order();            }//實(shí)現(xiàn)自旋            else {                test4();            }        } finally {            Object o = redisTemplate.opsForValue().get(key);            if (o.equals(key)) {                redisTemplate.delete(key);            }        }    }

          大家看這段代碼有什么問題?

          問題

          這樣看起來好像沒什么問題,還加了判斷鎖與redis的鎖是不是一致的,但是Redis的官方并不推薦我們這樣操作,他更希望我們可以使用腳本在進(jìn)行。

          第五次演進(jìn)

          @GetMapping(value = "test5")    public void test5() {        // 這里看似ok。我們先不看        String key = RedisKey.key("redis-order-lock") + UUID.randomUUID().toString();        Boolean javayh = redisTemplate.opsForValue().setIfAbsent(key, "javayh", 30, TimeUnit.SECONDS);        try {            if (javayh) {                order();            }//實(shí)現(xiàn)自旋            else {                test2();            }        } finally {            // 官方建議使用腳本操作            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +                    "then\n" +                    "    return redis.call(\"del\",KEYS[1])\n" +                    "else\n" +                    "    return 0\n" +                    "end";            redisTemplate.execute(new DefaultRedisScript<Long>(script), Arrays.asList(key), key);        }    }

          最終的演進(jìn)到這里就差不多了,當(dāng)然這只是demo,問題肯定還是有的。

          演進(jìn)的基礎(chǔ)理論

          這一切的演進(jìn)其實(shí)都來源于Redis官方的說明,如下:

          The command SET resource-name anystring NX EX max-lock-time is a simple way to implement a locking system with Redis.

          命令SET resource-name anystring NX EX max-lock-time是用Redis實(shí)現(xiàn)鎖定系統(tǒng)的一種簡單方法。

          A client can acquire the lock if the above command returns OK (or retry after some time if the command returns Nil), and remove the lock just using DEL.

          客戶端可以獲得鎖,如果上面的命令返回OK(或重試一段時(shí)間后,如果命令返回Nil),并使用DEL刪除鎖。

          The lock will be auto-released after the expire time is reached.

          鎖定將在到達(dá)過期時(shí)間后自動(dòng)釋放。

          It is possible to make this system more robust modifying the unlock schema as follows:

          修改解鎖模式可以使這個(gè)系統(tǒng)更健壯,如下所示:

          • Instead of setting a fixed string, set a non-guessable large random string, called token.

            與其設(shè)置固定的字符串,不如設(shè)置一個(gè)不可猜測(cè)的大型隨機(jī)字符串,稱為token。

          • Instead of releasing the lock with DEL, send a script that only removes the key if the value matches.

            發(fā)送一個(gè)只在值匹配時(shí)移除鍵的腳本,而不是使用DEL釋放鎖。

          This avoids that a client will try to release the lock after the expire time deleting the key created by another client that acquired the lock later.

          這避免了客戶端在過期時(shí)間后試圖釋放鎖,刪除另一個(gè)客戶端創(chuàng)建的密鑰,該密鑰是稍后獲得鎖的。

          An example of unlock script would be similar to the following:

          解鎖腳本的示例如下:

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

          The script should be called with EVAL ...script... 1 resource-name token-value


          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲成人中文网 | 日韩av导航 | 爱爱无码视频 | 天天干天天上天天日 | 人人操人人爱人人摸 |