日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Redis 分布式鎖進化史解讀+缺陷分析

Redis 分布式鎖進化史解讀+缺陷分析

作者:張佳 2019-01-21 11:01:52

存儲

存儲軟件

分布式

Redis 近兩年來微服務(wù)變得越來越熱門,越來越多的應(yīng)用部署在分布式環(huán)境中,在分布式環(huán)境中,數(shù)據(jù)一致性是一直以來需要關(guān)注并且去解決的問題,分布式鎖也就成為了一種廣泛使用的技術(shù),常用的分布式實現(xiàn)方式為Redis,Zookeeper,其中基于Redis的分布式鎖的使用更加廣泛。

創(chuàng)新互聯(lián)成都企業(yè)網(wǎng)站建設(shè)服務(wù),提供成都網(wǎng)站制作、網(wǎng)站建設(shè)、外貿(mào)網(wǎng)站建設(shè)網(wǎng)站開發(fā),網(wǎng)站定制,建網(wǎng)站,網(wǎng)站搭建,網(wǎng)站設(shè)計,成都響應(yīng)式網(wǎng)站建設(shè)公司,網(wǎng)頁設(shè)計師打造企業(yè)風格網(wǎng)站,提供周到的售前咨詢和貼心的售后服務(wù)。歡迎咨詢做網(wǎng)站需要多少錢:13518219792

Redis分布式鎖進化史

近兩年來微服務(wù)變得越來越熱門,越來越多的應(yīng)用部署在分布式環(huán)境中,在分布式環(huán)境中,數(shù)據(jù)一致性是一直以來需要關(guān)注并且去解決的問題,分布式鎖也就成為了一種廣泛使用的技術(shù),常用的分布式實現(xiàn)方式為Redis,Zookeeper,其中基于Redis的分布式鎖的使用更加廣泛。

但是在工作和網(wǎng)絡(luò)上看到過各個版本的Redis分布式鎖實現(xiàn),每種實現(xiàn)都有一些不嚴謹?shù)牡胤剑踔劣锌赡苁清e誤的實現(xiàn),包括在代碼中,如果不能正確的使用分布式鎖,可能造成嚴重的生產(chǎn)環(huán)境故障,本文主要對目前遇到的各種分布式鎖以及其缺陷做了一個整理,并對如何選擇合適的Redis分布式鎖給出建議。

各個版本的Redis分布式鎖

V1.0

  
 
 
 
  1. tryLock(){   
  2.    SETNX Key 1 
  3.    EXPIRE Key Seconds 
  4. release(){   
  5.  DELETE Key 

這個版本應(yīng)該是最簡單的版本,也是出現(xiàn)頻率很高的一個版本,首先給鎖加一個過期時間操作是為了避免應(yīng)用在服務(wù)重啟或者異常導致鎖無法釋放后,不會出現(xiàn)鎖一直無法被釋放的情況。

這個方案的一個問題在于每次提交一個Redis請求,如果執(zhí)行完***條命令后應(yīng)用異常或者重啟,鎖將無法過期,一種改善方案就是使用Lua腳本(包含SETNX和EXPIRE兩條命令),但是如果Redis僅執(zhí)行了一條命令后crash或者發(fā)生主從切換,依然會出現(xiàn)鎖沒有過期時間,最終導致無法釋放。

另外一個問題在于,很多同學在釋放分布式鎖的過程中,無論鎖是否獲取成功,都在finally中釋放鎖,這樣是一個鎖的錯誤使用,這個問題將在后續(xù)的V3.0版本中解決。

針對鎖無法釋放問題的一個解決方案基于GETSET命令來實現(xiàn)

V1.1 基于GETSET

  
 
 
 
  1. tryLock(){   
  2.    NewExpireTime=CurrentTimestamp+ExpireSeconds 
  3.    if(SETNX Key NewExpireTime Seconds){ 
  4.         oldExpireTime = GET(Key) 
  5.          if( oldExpireTime < CurrentTimestamp){ 
  6.               NewExpireTime=CurrentTimestamp+ExpireSeconds 
  7.              CurrentExpireTime=GETSET(Key,NewExpireTime) 
  8.              if(CurrentExpireTime == oldExpireTime){ 
  9.                return 1; 
  10.              }else{ 
  11.                return 0; 
  12.              } 
  13.         } 
  14.    } 
  15. release(){   
  16.        DELETE key 
  17.    } 

思路:

  1. SETNX(Key,ExpireTime)獲取鎖
  2. 如果獲取鎖失敗,通過GET(Key)返回的時間戳檢查鎖是否已經(jīng)過期
  3. GETSET(Key,ExpireTime)修改Value為NewExpireTime
  4. 檢查GETSET返回的舊值,如果等于GET返回的值,則認為獲取鎖成功

注意:這個版本去掉了EXPIRE命令,改為通過Value時間戳值來判斷過期

問題:

  1. 在鎖競爭較高的情況下,會出現(xiàn)Value不斷被覆蓋,但是沒有一個Client獲取到鎖
  2. 在獲取鎖的過程中不斷的修改原有鎖的數(shù)據(jù),設(shè)想一種場景C1,C2競爭鎖,C1獲取到了鎖,C2鎖執(zhí)行了GETSET操作修改了C1鎖的過期時間,如果C1沒有正確釋放鎖,鎖的過期時間被延長,其它Client需要等待更久的時間

V2.0 基于SETNX

  
 
 
 
  1. tryLock(){    
  2.    SETNX Key 1 Seconds  
  3. }  
  4. release(){    
  5.  DELETE Key  

Redis 2.6.12版本后SETNX增加過期時間參數(shù),這樣就解決了兩條命令無法保證原子性的問題。但是設(shè)想下面一個場景:

  1. C1成功獲取到了鎖,之后C1因為GC進入等待或者未知原因?qū)е氯蝿?wù)執(zhí)行過長,***在鎖失效前C1沒有主動釋放鎖
  2. C2在C1的鎖超時后獲取到鎖,并且開始執(zhí)行,這個時候C1和C2都同時在執(zhí)行,會因重復執(zhí)行造成數(shù)據(jù)不一致等未知情況
  3. C1如果先執(zhí)行完畢,則會釋放C2的鎖,此時可能導致另外一個C3進程獲取到了鎖

大致的流程圖

存在問題:

  1. 由于C1的停頓導致C1 和C2同都獲得了鎖并且同時在執(zhí)行,在業(yè)務(wù)實現(xiàn)間接要求必須保證冪等性
  2. C1釋放了不屬于C1的鎖

V3.0

  
 
 
 
  1. tryLock(){    
  2.    SETNX Key UnixTimestamp Seconds  
  3. }  
  4. release(){    
  5.    EVAL( 
  6.      //LuaScript 
  7.      if redis.call("get",KEYS[1]) == ARGV[1] then 
  8.          return redis.call("del",KEYS[1]) 
  9.      else 
  10.          return 0 
  11.      end 
  12.    ) 

這個方案通過指定Value為時間戳,并在釋放鎖的時候檢查鎖的Value是否為獲取鎖的Value,避免了V2.0版本中提到的C1釋放了C2持有的鎖的問題;另外在釋放鎖的時候因為涉及到多個Redis操作,并且考慮到Check And Set 模型的并發(fā)問題,所以使用Lua腳本來避免并發(fā)問題。

存在問題:

  • 如果在并發(fā)極高的場景下,比如搶紅包場景,可能存在UnixTimestamp重復問題,另外由于不能保證分布式環(huán)境下的物理時鐘一致性,也可能存在UnixTimestamp重復問題,只不過極少情況下會遇到。

V3.1

  
 
 
 
  1. tryLock(){    
  2.    SET Key UniqId Seconds  
  3. }  
  4. release(){    
  5.    EVAL(  
  6.      //LuaScript 
  7.      if redis.call("get",KEYS[1]) == ARGV[1] then 
  8.          return redis.call("del",KEYS[1]) 
  9.      else 
  10.          return 0 
  11.      end 
  12.    )  

Redis 2.6.12后SET同樣提供了一個NX參數(shù),等同于SETNX命令,官方文檔上提醒后面的版本有可能去掉SETNX, SETEX, PSETEX,并用SET命令代替,另外一個優(yōu)化是使用一個自增的唯一UniqId代替時間戳來規(guī)避V3.0提到的時鐘問題。

這個方案是目前***的分布式鎖方案,但是如果在Redis集群環(huán)境下依然存在問題:

由于Redis集群數(shù)據(jù)同步為異步,假設(shè)在Master節(jié)點獲取到鎖后未完成數(shù)據(jù)同步情況下Master節(jié)點crash,此時在新的Master節(jié)點依然可以獲取鎖,所以多個Client同時獲取到了鎖

分布式Redis鎖:Redlock

V3.1的版本僅在單實例的場景下是安全的,針對如何實現(xiàn)分布式Redis的鎖,國外的分布式專家有過激烈的討論, antirez提出了分布式鎖算法Redlock,在distlock話題下可以看到對Redlock的詳細說明,下面是Redlock算法的一個中文說明(引用)

假設(shè)有N個獨立的Redis節(jié)點

  • 獲取當前時間(毫秒數(shù))。
  • 按順序依次向N個Redis節(jié)點執(zhí)行獲取鎖的操作。這個獲取操作跟前面基于單Redis節(jié)點的獲取鎖的過程相同,包含隨機字符串myrandomvalue,也包含過期時間(比如PX 30000,即鎖的有效時間)。為了保證在某個Redis節(jié)點不可用的時候算法能夠繼續(xù)運行,這個獲取鎖的操作還有一個超時時間(time out),它要遠小于鎖的有效時間(幾十毫秒量級)??蛻舳嗽谙蚰硞€Redis節(jié)點獲取鎖失敗以后,應(yīng)該立即嘗試下一個Redis節(jié)點。這里的失敗,應(yīng)該包含任何類型的失敗,比如該Redis節(jié)點不可用,或者該Redis節(jié)點上的鎖已經(jīng)被其它客戶端持有(注:Redlock原文中這里只提到了Redis節(jié)點不可用的情況,但也應(yīng)該包含其它的失敗情況)。
  • 計算整個獲取鎖的過程總共消耗了多長時間,計算方法是用當前時間減去第1步記錄的時間。如果客戶端從大多數(shù)Redis節(jié)點(>= N/2+1)成功獲取到了鎖,并且獲取鎖總共消耗的時間沒有超過鎖的有效時間(lock validity time),那么這時客戶端才認為最終獲取鎖成功;否則,認為最終獲取鎖失敗。
  • 如果最終獲取鎖成功了,那么這個鎖的有效時間應(yīng)該重新計算,它等于最初的鎖的有效時間減去第3步計算出來的獲取鎖消耗的時間。
  • 如果最終獲取鎖失敗了(可能由于獲取到鎖的Redis節(jié)點個數(shù)少于N/2+1,或者整個獲取鎖的過程消耗的時間超過了鎖的最初有效時間),那么客戶端應(yīng)該立即向所有Redis節(jié)點發(fā)起釋放鎖的操作(即前面介紹的Redis Lua腳本)。
  • 釋放鎖:對所有的Redis節(jié)點發(fā)起釋放鎖操作

然而Martin Kleppmann針對這個算法提出了質(zhì)疑,提出應(yīng)該基于fencing token機制(每次對資源進行操作都需要進行token驗證)

  1. Redlock在系統(tǒng)模型上尤其是在分布式時鐘一致性問題上提出了假設(shè),實際場景下存在時鐘不一致和時鐘跳躍問題,而Redlock恰恰是基于timing的分布式鎖
  2. 另外Redlock由于是基于自動過期機制,依然沒有解決長時間的gc pause等問題帶來的鎖自動失效,從而帶來的安全性問題。 接著antirez又回復了Martin Kleppmann的質(zhì)疑,給出了過期機制的合理性,以及實際場景中如果出現(xiàn)停頓問題導致多個Client同時訪問資源的情況下如何處理。

針對Redlock的問題,基于Redis的分布式鎖到底安全嗎給出了詳細的中文說明,并對Redlock算法存在的問題提出了分析。

總結(jié)

不論是基于SETNX版本的Redis單實例分布式鎖,還是Redlock分布式鎖,都是為了保證下特性

安全性:在同一時間不允許多個Client同時持有鎖

活性 死鎖:鎖最終應(yīng)該能夠被釋放,即使Client端crash或者出現(xiàn)網(wǎng)絡(luò)分區(qū)(通?;诔瑫r機制) 容錯性:只要超過半數(shù)Redis節(jié)點可用,鎖都能被正確獲取和釋放 所以在開發(fā)或者使用分布式鎖的過程中要保證安全性和活性,避免出現(xiàn)不可預測的結(jié)果。

另外每個版本的分布式鎖都存在一些問題,在鎖的使用上要針對鎖的實用場景選擇合適的鎖,通常情況下鎖的使用場景包括:

Efficiency(效率):只需要一個Client來完成操作,不需要重復執(zhí)行,這是一個對寬松的分布式鎖,只需要保證鎖的活性即可;

Correctness(正確性):多個Client保證嚴格的互斥性,不允許出現(xiàn)同時持有鎖或者對同時操作同一資源,這種場景下需要在鎖的選擇和使用上更加嚴格,同時在業(yè)務(wù)代碼上盡量做到冪等

在Redis分布式鎖的實現(xiàn)上還有很多問題等待解決,我們需要認識到這些問題并清楚如何正確實現(xiàn)一個Redis 分布式鎖,然后在工作中合理的選擇和正確的使用分布式鎖。


當前標題:Redis 分布式鎖進化史解讀+缺陷分析
文章來源:http://www.5511xx.com/article/cohehih.html