<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>

          分布式鎖的實(shí)現(xiàn)之redis篇

          共 3479字,需瀏覽 7分鐘

           ·

          2021-05-11 18:00

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

            作者 |  Virtuals

          來源 |  urlify.cn/7FJ7Rr

          76套java從入門到精通實(shí)戰(zhàn)課程分享

          為什么需要分布式鎖

          引入經(jīng)典的秒殺情景,100件商品供客戶搶。如果是單機(jī)版的話,我們使用synchronized 或者 lock 都可以實(shí)現(xiàn)線程安全。但是如果多個服務(wù)器的話,synchronized 和 lock 就不管用了(廢話,怎么可能管用,都不在同一段代碼了)。

          分布式鎖就是被設(shè)計出來實(shí)現(xiàn)多個服務(wù)器的線程安全。

          很容易想到的方案是把共享變量(鎖)抽取出來放在一個公共的數(shù)據(jù)庫里(Redis、Memchhed)里,所有的服務(wù)器通過這個公共的資源實(shí)現(xiàn)數(shù)據(jù)的一致性,防止超賣。

          具體實(shí)現(xiàn)

          分布式鎖的實(shí)現(xiàn)方式有:Memchched分布式鎖、Redis分布式鎖、Zookeeper分布式鎖,這里我們以Redis分布式鎖為例,Redis分布式鎖也是現(xiàn)在使用得最多的

          1. 思路

          • setnx加鎖

            setnx是實(shí)現(xiàn)分布式的核心,意思是只有當(dāng)前key不存在才返回1,當(dāng)前key存在返回0

            這個key就是我們的“鎖”,只有線程獲得鎖才能繼續(xù)執(zhí)行,執(zhí)行完del這個key相當(dāng)于解鎖操作。這個就是redis實(shí)現(xiàn)分布式鎖的核心,怎么樣,很好理解吧

          • del解鎖

          2. 第一個問題:鎖無法被釋放

          試想一下,如果你執(zhí)行完set命令服務(wù)器宕機(jī)了,來不及del解鎖,那么這個鎖永遠(yuǎn)無法被釋放,其他線程無法執(zhí)行。

          解決方法是key必須設(shè)置一個超時時間,即使沒有被顯示釋放,也在超時后自動釋放。

          redis為我們提供了這個命令設(shè)置超時時間

          • expire key ttl 秒為單位

          • pexpire key ttl 毫秒為單位

          • expireat key timestamp

          • pexpireat key timestamp

          因此加鎖的操作變成:

          setnx lock 1
          expire lock 10

          但是這兩個操作不保證原子性(Redis單條操作保證原子性),如果加完鎖還沒設(shè)置過期時間服務(wù)器就宕機(jī)了,同樣會導(dǎo)致死鎖,因此加鎖整個操作必須保證原子性。

          redis提供了set+過期時間的原子操作

          set lock 1 EX 10 NX
          // 最終的加鎖命令

          3. 第二個問題:錯誤釋放鎖

          第二個問題,如果線程執(zhí)行時間超過TTL,當(dāng)前鎖被自動銷毀

          但是等線程執(zhí)行完了,原來的del方法還會執(zhí)行,它就會去執(zhí)行解鎖操作,把其他線程占用的鎖給del了,這會產(chǎn)生非常嚴(yán)重的問題

          String REDIS_Lock="lock";
          String value=1;
          try{
              redisUtil.setLockDistribute(REDIS_LOCK,1,10);
              ......業(yè)務(wù)邏輯
                  
          }finally{
              // 這個操作有可能會誤刪鎖
              redisUtil.del(REDIS_LOCK);
          }

          解決方案是key的value不再是默認(rèn)的了

          String REDIS_Lock="lock";
          String value=UUID.randomUUID().toString()+Thread.currentThread().getname();
          try{
              redisUtil.setLockDistribute(REDIS_LOCK,1,10);
              ......業(yè)務(wù)邏輯
                  
          }finally{
              // 先判斷后刪除
              if(redisUtil.get(REDIS_LOCK).equals(value)){
                  redisUtil.del(REDIS_LOCK);
              }
              
          }

          這樣寫其實(shí)還有個問題,判斷和刪除無法保證原子性,還是有可能誤刪。因此解鎖我們使用lua腳本來保證原子性:工具類有實(shí)現(xiàn)lua腳本的方法。

          //lua腳本刪除key原子操作
          if redis.call("get",KEYS[1]) == ARGV[1] then
              return redis.call("del",KEYS[1])
          else
              return 0
          end

          (解鎖操作也可用事務(wù)來保證原子性,應(yīng)付面試,實(shí)戰(zhàn)還是lua腳本)

          4. 第三個問題:超時解鎖導(dǎo)致并發(fā)

          加鎖和解鎖操作我們都搞定了,但是還有一個問題:如果你的線程執(zhí)行時間超過ttl過期時間,鎖還是被釋放了,其他線程可以和次線程并發(fā)執(zhí)行,這是我們并不想看到的。

          因此我們要為ttl延時

          我們可以讓獲得鎖的線程開啟一個守護(hù)線程,用來給快要過期的鎖“續(xù)航”。


          5. 集群環(huán)境下可能出現(xiàn)的問題

          redis集群環(huán)境,多個master,多個slave的情況下:

          當(dāng)主節(jié)點(diǎn)掛掉時,從節(jié)點(diǎn)會取而代之,但客戶端無明顯感知。當(dāng)客戶端 A 成功加鎖,指令還未同步,此時主節(jié)點(diǎn)掛掉,從節(jié)點(diǎn)提升為主節(jié)點(diǎn),新的主節(jié)點(diǎn)沒有鎖的數(shù)據(jù),當(dāng)客戶端 B 加鎖時就會成功。

          也就是主結(jié)點(diǎn)加了鎖就宕機(jī)了,從節(jié)點(diǎn)還沒同步,當(dāng)該從節(jié)點(diǎn)提升為主節(jié)點(diǎn)時就會出錯。


          解決方案我也不清楚....以后碰到再找資料

          開源框架Redisson

          上面的流程如果手寫的話會要人老命,開源框架Redisson幫我們擺平一切,現(xiàn)在用得十分多

          直接上代碼:

          // 注入redisson
          public Redisson redisson(){
              Config config=new Config();
              config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
              return Redisson.create(config);

          }
          @Autowired
          Redisson son;

          String REDIS_Lock="lock";
          String value=UUID.randomUUID().toString()+Thread.currentThread().getname();
          RLock lock=son.getLock();
          try{
              lock.lock();
              ......業(yè)務(wù)邏輯
                  
          }finally{
              lock.unlock();
          }

          // 這段代碼會解決上述三個問題,集群環(huán)境下redis分布式鎖的實(shí)現(xiàn)

          結(jié)語

          分布式鎖看起來難其實(shí)原理還是很簡單的,沒事多看看官方文檔,講得挺細(xì)致的








          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼 2 秒


          感謝點(diǎn)贊支持下哈 



          瀏覽 59
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  日韩高清一级无码 | 亚洲操B在线看 | 国产V综合V | 欧美后门菊门交 | 青娱乐在线免费视频 |