使用redis實現分布式鎖

背景

在類似秒殺這樣的并發(fā)場景下脚草,為了確保同一時刻只能允許一個用戶訪問資源弄砍,需要利用加鎖的機制控制資源的訪問權造锅。如果服務只在單臺機器上運行撼唾,可以簡單地用一個內存變量進行控制。而在多臺機器的系統(tǒng)上哥蔚,則需要用分布式鎖的機制進行并發(fā)控制倒谷。基于redis的一些特性糙箍,利用redis可以既方便又高效地模擬鎖的實現渤愁。

一個簡單方案

讓我們先從一個簡單的實現說起,這里用到了redis的兩個命令深夯,SETNXEXPIRE抖格。如果lock_key不存在,那么就設置lock_key的值為1咕晋,并且設置過期時間雹拄;如果lock_key存在,說明已經有人在使用這把鎖捡需,訪問失敗办桨。

def acquire_lock(lock_key, expire_timeout=60):
    if redis.setnx(lock_key, 1):
        redis.expire(lock_key, expire_timeout)
        return True
    return False

邏輯上看似乎沒有問題,但是考慮一下異常情況:如果setnx設置成功站辉,但expire由于某些原因(比如超時)操作失敗呢撞,那么這把鎖就永遠存在了,也就是所謂的死鎖饰剥,后面的人永遠無法訪問這個資源殊霞。

利用時間戳取值的方案

為了解決死鎖,我們可以利用setnx的value來做文章汰蓉。上例中的我們設的value是1绷蹲,其實并沒有派上用場。因此可以考慮將value設為當前時間加上expire_timeout,當setnx設置失敗后祝钢,我們去讀lock_key的value比规,并且和當前時間作比對,如果當前時間大于value拦英,那么資源理當被釋放蜒什。代碼示例如下:

def acquire_lock(lock_key, expire_timeout=60):
    expire_time = int(time.time()) + expire_timeout
    if redis.setnx(lock_key, expire_time):
        redis.expire(lock_key, expire_timeout)
        return True
    redis_value = redis.get(lock_key)
    if redis_value and int(time.time()) > int(redis_value):
        redis.delete(lock_key)
    return False

然而仔細推敲下這段代碼仍然能發(fā)現一些問題。第一疤估,這個方案依賴時間灾常,如果在分布式系統(tǒng)中的時間沒有同步,則會對方案產生一定偏差铃拇。第二钞瀑,假設C1和C2都沒拿到鎖,它們都去讀value并對比時間慷荔,在競態(tài)條件(race condition)下可能產生如下的時序:C1刪除lock_key雕什,C1獲得鎖,C2刪除lock_key拧廊,C2獲得鎖监徘。這樣C1和C2同時拿到了鎖,顯然是不對的吧碾。

改進后的方案

幸運的是凰盔,redis里還有一個指令可以幫助我們解決這個問題。GETSET指令在set新值的同時會返回老的值倦春,這樣的話我們可以檢查返回的值户敬,如果該值和之前讀出來的值相同,那么這次操作有效睁本,反之則無效尿庐。代碼示例如下:

def acquire_lock(lock_key, expire_timeout=60):
    expire_time = int(time.time()) + expire_timeout
    if redis.setnx(lock_key, expire_time):
        redis.expire(lock_key, expire_timeout)
        return True
    redis_value = redis.get(lock_key)
    if redis_value and int(time.time()) > int(redis_value):
        expire_time = int(time.time()) + expire_timeout
        old_value = redis.getset(lock_key, expire_time)
        if int(old_value) == int(redis_value):
            return True
    return False

這個方案基本可以滿足要求,除了有一個小瑕疵呢堰,由于getset會去修改value抄瑟,在競態(tài)條件下可能會被修改多次導致timeout有細微的誤差,但這個對結果影響不大枉疼。

最終方案

以上方案實現起來略顯繁瑣皮假,但從redis 2.6.12版本開始有一個更為簡便的方法。我們可以使用SET指令的擴展 ** SET key value [EX seconds] [PX milliseconds] [NX|XX] **骂维,這個指令相當于對SETNX和EXPIRES進行了合并惹资,因而我們的算法可以簡化為如下一行:

def acquire_lock(lock_key, expire_timeout=60):
    ret = redis.set(lock_key, int(time.time()), nx=True, ex=expire_timeout):
    return ret

總結

在redis 2.6.12版本之后我們可以用一個簡單的SET命令實現分布式鎖,而在此版本之前則需要將SETNX和GETSET配合使用一個較為繁瑣的方案航闺。簡化后的方案對于開發(fā)者來說當然是好事褪测,但通過學習這一演變過程我們會對問題有更深刻的印象猴誊。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市侮措,隨后出現的幾起案子懈叹,更是在濱河造成了極大的恐慌,老刑警劉巖萝毛,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件项阴,死亡現場離奇詭異,居然都是意外死亡笆包,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進店門略荡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庵佣,“玉大人,你說我怎么就攤上這事汛兜“头啵” “怎么了?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵粥谬,是天一觀的道長肛根。 經常有香客問我,道長漏策,這世上最難降的妖魔是什么派哲? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮掺喻,結果婚禮上芭届,老公的妹妹穿的比我還像新娘。我一直安慰自己感耙,他們只是感情好褂乍,可當我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著即硼,像睡著了一般逃片。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上只酥,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天褥实,我揣著相機與錄音,去河邊找鬼层皱。 笑死性锭,一個胖子當著我的面吹牛,可吹牛的內容都是我干的叫胖。 我是一名探鬼主播爱致,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼渊抽,長吁一口氣:“原來是場噩夢啊……” “哼逃贝!你這毒婦竟也來了忧侧?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤震放,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體凡资,經...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年谬运,在試婚紗的時候發(fā)現自己被綠了隙赁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡梆暖,死狀恐怖伞访,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情轰驳,我是刑警寧澤厚掷,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站级解,受9級特大地震影響冒黑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜勤哗,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一抡爹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俺陋,春花似錦豁延、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缴挖,卻和暖如春袋狞,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背映屋。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工苟鸯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棚点。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓早处,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瘫析。 傳聞我的和親對象是個殘疾皇子砌梆,可洞房花燭夜當晚...
    茶點故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內容