使用Redis的 SETNX 命令能夠實現分佈式鎖,下文介紹其實現方法。html
SETNX key value
將 key 的值設爲 value,當且僅當 key 不存在。
若給定的 key 已經存在,則 SETNX 不作任何動做。
SETNX 是SET if Not eXists的簡寫。python
返回整數,具體爲
- 1,當 key 的值被設置
- 0,當 key 的值沒被設置redis
redis> SETNX mykey 「hello」 (integer) 1 redis> SETNX mykey 「hello」 (integer) 0 redis> GET mykey 「hello」 redis>
多個進程執行如下Redis命令:算法
SETNX lock.foo <current Unix time + lock timeout + 1>
若是 SETNX 返回1,說明該進程得到鎖,SETNX將鍵 lock.foo 的值設置爲鎖的超時時間(當前時間 + 鎖的有效時間)。
若是 SETNX 返回0,說明其餘進程已經得到了鎖,進程不能進入臨界區。進程能夠在一個循環中不斷地嘗試 SETNX 操做,以得到鎖。網絡
考慮一種狀況,若是進程得到鎖後,斷開了與 Redis 的鏈接(多是進程掛掉,或者網絡中斷),若是沒有有效的釋放鎖的機制,那麼其餘進程都會處於一直等待的狀態,即出現「死鎖」。數據結構
上面在使用 SETNX 得到鎖時,咱們將鍵 lock.foo 的值設置爲鎖的有效時間,進程得到鎖後,其餘進程還會不斷的檢測鎖是否已超時,若是超時,那麼等待的進程也將有機會得到鎖。分佈式
然而,鎖超時時,咱們不能簡單地使用 DEL 命令刪除鍵 lock.foo 以釋放鎖。考慮如下狀況,進程P1已經首先得到了鎖 lock.foo,而後進程P1掛掉了。進程P2,P3正在不斷地檢測鎖是否已釋放或者已超時,執行流程以下:spa
從上面的狀況能夠得知,在檢測到鎖超時後,進程不能直接簡單地執行 DEL 刪除鍵的操做以得到鎖。.net
爲了解決上述算法可能出現的多個進程同時得到鎖的問題,咱們再來看如下的算法。
咱們一樣假設進程P1已經首先得到了鎖 lock.foo,而後進程P1掛掉了。接下來的狀況:code
GETSET lock.foo <current Unix timestamp + lock timeout + 1>
另外,值得注意的是,在進程釋放鎖,即執行 DEL lock.foo 操做前,須要先判斷鎖是否已超時。若是鎖已超時,那麼鎖可能已由其餘進程得到,這時直接執行 DEL lock.foo 操做會致使把其餘進程已得到的鎖釋放掉。
用如下Python代碼來實現上述的使用 SETNX 命令做分佈式鎖的算法。
LOCK_TIMEOUT = 3 lock = 0 lock_timeout = 0 lock_key = 'lock.foo'# 獲取鎖while lock != 1: now = int(time.time()) lock_timeout = now + LOCK_TIMEOUT + 1 lock = redis_client.setnx(lock_key, lock_timeout) if lock == 1 or (now > int(redis_client.get(lock_key))) and now > int(redis_client.getset(lock_key, lock_timeout)): breakelse: time.sleep(0.001) # 已得到鎖 do_job() # 釋放鎖 now = int(time.time()) if now < lock_timeout: redis_client.delete(lock_key)