掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
作者:張佳 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è)風(fēng)格網(wǎng)站,提供周到的售前咨詢和貼心的售后服務(wù)。歡迎咨詢做網(wǎng)站需要多少錢:13518219792
Redis分布式鎖進(jìn)化史
近兩年來微服務(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)都有一些不嚴(yán)謹(jǐn)?shù)牡胤?,甚至有可能是錯誤的實現(xiàn),包括在代碼中,如果不能正確的使用分布式鎖,可能造成嚴(yán)重的生產(chǎn)環(huán)境故障,本文主要對目前遇到的各種分布式鎖以及其缺陷做了一個整理,并對如何選擇合適的Redis分布式鎖給出建議。
各個版本的Redis分布式鎖
V1.0
- tryLock(){
- SETNX Key 1
- EXPIRE Key Seconds
- }
- release(){
- DELETE Key
- }
這個版本應(yīng)該是最簡單的版本,也是出現(xiàn)頻率很高的一個版本,首先給鎖加一個過期時間操作是為了避免應(yīng)用在服務(wù)重啟或者異常導(dǎo)致鎖無法釋放后,不會出現(xiàn)鎖一直無法被釋放的情況。
這個方案的一個問題在于每次提交一個Redis請求,如果執(zhí)行完***條命令后應(yīng)用異常或者重啟,鎖將無法過期,一種改善方案就是使用Lua腳本(包含SETNX和EXPIRE兩條命令),但是如果Redis僅執(zhí)行了一條命令后crash或者發(fā)生主從切換,依然會出現(xiàn)鎖沒有過期時間,最終導(dǎo)致無法釋放。
另外一個問題在于,很多同學(xué)在釋放分布式鎖的過程中,無論鎖是否獲取成功,都在finally中釋放鎖,這樣是一個鎖的錯誤使用,這個問題將在后續(xù)的V3.0版本中解決。
針對鎖無法釋放問題的一個解決方案基于GETSET命令來實現(xiàn)
V1.1 基于GETSET
- tryLock(){
- NewExpireTime=CurrentTimestamp+ExpireSeconds
- if(SETNX Key NewExpireTime Seconds){
- oldExpireTime = GET(Key)
- if( oldExpireTime < CurrentTimestamp){
- NewExpireTime=CurrentTimestamp+ExpireSeconds
- CurrentExpireTime=GETSET(Key,NewExpireTime)
- if(CurrentExpireTime == oldExpireTime){
- return 1;
- }else{
- return 0;
- }
- }
- }
- }
- release(){
- DELETE key
- }
思路:
注意:這個版本去掉了EXPIRE命令,改為通過Value時間戳值來判斷過期
問題:
V2.0 基于SETNX
- tryLock(){
- SETNX Key 1 Seconds
- }
- release(){
- DELETE Key
- }
Redis 2.6.12版本后SETNX增加過期時間參數(shù),這樣就解決了兩條命令無法保證原子性的問題。但是設(shè)想下面一個場景:
大致的流程圖
存在問題:
V3.0
- tryLock(){
- SETNX Key UnixTimestamp Seconds
- }
- release(){
- EVAL(
- //LuaScript
- if redis.call("get",KEYS[1]) == ARGV[1] then
- return redis.call("del",KEYS[1])
- else
- return 0
- end
- )
- }
這個方案通過指定Value為時間戳,并在釋放鎖的時候檢查鎖的Value是否為獲取鎖的Value,避免了V2.0版本中提到的C1釋放了C2持有的鎖的問題;另外在釋放鎖的時候因為涉及到多個Redis操作,并且考慮到Check And Set 模型的并發(fā)問題,所以使用Lua腳本來避免并發(fā)問題。
存在問題:
V3.1
- tryLock(){
- SET Key UniqId Seconds
- }
- release(){
- EVAL(
- //LuaScript
- if redis.call("get",KEYS[1]) == ARGV[1] then
- return redis.call("del",KEYS[1])
- else
- return 0
- end
- )
- }
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的詳細(xì)說明,下面是Redlock算法的一個中文說明(引用)
假設(shè)有N個獨立的Redis節(jié)點
然而Martin Kleppmann針對這個算法提出了質(zhì)疑,提出應(yīng)該基于fencing token機(jī)制(每次對資源進(jìn)行操作都需要進(jìn)行token驗證)
針對Redlock的問題,基于Redis的分布式鎖到底安全嗎給出了詳細(xì)的中文說明,并對Redlock算法存在的問題提出了分析。
總結(jié)
不論是基于SETNX版本的Redis單實例分布式鎖,還是Redlock分布式鎖,都是為了保證下特性
安全性:在同一時間不允許多個Client同時持有鎖
活性 死鎖:鎖最終應(yīng)該能夠被釋放,即使Client端crash或者出現(xiàn)網(wǎng)絡(luò)分區(qū)(通?;诔瑫r機(jī)制) 容錯性:只要超過半數(shù)Redis節(jié)點可用,鎖都能被正確獲取和釋放 所以在開發(fā)或者使用分布式鎖的過程中要保證安全性和活性,避免出現(xiàn)不可預(yù)測的結(jié)果。
另外每個版本的分布式鎖都存在一些問題,在鎖的使用上要針對鎖的實用場景選擇合適的鎖,通常情況下鎖的使用場景包括:
Efficiency(效率):只需要一個Client來完成操作,不需要重復(fù)執(zhí)行,這是一個對寬松的分布式鎖,只需要保證鎖的活性即可;
Correctness(正確性):多個Client保證嚴(yán)格的互斥性,不允許出現(xiàn)同時持有鎖或者對同時操作同一資源,這種場景下需要在鎖的選擇和使用上更加嚴(yán)格,同時在業(yè)務(wù)代碼上盡量做到冪等
在Redis分布式鎖的實現(xiàn)上還有很多問題等待解決,我們需要認(rèn)識到這些問題并清楚如何正確實現(xiàn)一個Redis 分布式鎖,然后在工作中合理的選擇和正確的使用分布式鎖。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流