關於如何正確實現Redis分布式鎖的探索

2021-09-20 13:03:01 字數 4554 閱讀 4436

首先介紹下什麼是分布式鎖,分布式鎖是針對不同系統多程序之間資料同步的一種解決方案。在不同程序需要互斥地訪問共享資源時,分布式鎖是一種非常有用的技術手段。

先提出三個屬性,這三個屬性,是實現高效分布式鎖的基礎。

當前目標是在多個tomcat上實現關單操作。所謂關單,就是把那些超期未支付的訂單給關閉掉,這裡用的是spring schedule實現,關單部分不在這裡詳述,僅就分布式鎖問題進行探索。

這裡是多個tomcat共享乙個redis集群。分布式鎖用redis實現。同一時間只有乙個tomcat可以獲取鎖,進行關單。

原理這個初始版本理論上不是乙個真正的分布式鎖,但放在這裡是可以實現分布式鎖的功能的。所以我也放在這裡做乙個例子吧。為什麼這個不好呢,之後再說。

幾個命令:

首先設定乙個鎖名+鎖過期時間的鍵值對到redis中,用setnx來實現鎖功能。setnx,即"set if not exists",就是只有在不存在這個key的時候才進行set,契合鎖的定義。

如果set成功,也就是沒有這個key,那麼就獲取鎖成功。還要再把鎖expire一下,防止set之後當前系統掛了之後一直沒解鎖,發生死鎖。在這段時間裡該程序可以對共享資源進行操作,進行關單,其他程序無法插手。

如果set失敗,說明redis中已經有這個key了,但是並不代表這個鎖還有效,此時仍需判斷,如果鎖過期了,獲取鎖。

下面是此過程的的流程圖:

要使用redis,需要引入redis相關元件,這裡採用jedis,在pom.xml中新增:

>

>

redis.clientsgroupid

>

>

jedisartifactid

>

>

2.6.0version

>

dependency

>

具體實現**如下:

@scheduled

(cron =

"0 */1 * * * ?"

)//每1分鐘執行一次(每個1分鐘的整數倍)

public

void

closeordertask()

else

else

", const.redis_lock.close_order_task_lock);}

}else

", const.redis_lock.close_order_task_lock);}

} log.

info

("關閉訂單定時任務結束");

}/**

* 關閉訂單,解鎖

* @param lockname :redis中儲存的鎖

*/private

void

closeorder

(string lockname)

, threadname:{}"

, const.redis_lock.close_order_task_lock, thread.

currentthread()

.getname()

);int hour = integer.

parseint

(propertiesutil.

getproperty

("close.order.task.time.hour"))

; iorderservice.

closeorder

(hour)

;//解鎖

redisshardedpoolutil.

del(const.redis_lock.close_order_task_lock)

; log.

info

("獲取{}, threadname:{}"

, const.redis_lock.close_order_task_lock, thread.

currentthread()

.getname()

);log.

info

("*************************===");

}

這是無業務**的簡化理解版:

public

static

boolean

getlock

(jedis jedis, string lockkey,

int timeout)

// 如果鎖存在,獲取鎖的過期時間

string currentvaluestr = jedis.

get(lockkey);if

(currentvaluestr != null && system.

currenttimemillis()

> long.

parselong

(currentvaluestr))}

// 其他情況,一律返回加鎖失敗

return

false

;}

問題分析

這裡會出現什麼問題呢?

首先可以很清楚地看出,這種實現方式麻煩,比較難懂,出錯概率不小;

如果有多個程序同時進行getset操作,可能會出現a設定的value值覆蓋b的value值的情況,此時a和b同時進行了getset,儘管最終獲得鎖的只有乙個程序,假設為b,但是如果a的getset是在b之後的話,那麼就會覆蓋掉b的value值。即:鎖是b的,業務是b在處理,但是過期時間是a的;

但是放在這裡還是ok的,因為a、b的過期時間相差幾乎沒有,既然都是基於當前時間+timeout,兩條指令先後處理,相差時間是微秒級的;

可以看到**中是有給鎖設定有效時間的,而且既在redis中設定了ttl,還將過期時間設為value值。但是還有一種情況:如果鎖已過期失效,但程序任務仍然沒有完成,此時有另乙個程序獲取鎖,那麼就有兩個程序在同時執行此任務,違背了分布式鎖的規則;

要設定乙個合理的時間要考慮到執行時間問題、阻塞的問題,同時要兼顧效率。在我這個情境下執行時間是可以**,因此這個問題可以不用考慮;

對時間同步性的要求高。鎖過期時間是由程序自己生成的,那麼多機部署的時候就要求時間是同步的,否則會出現某個鎖時間過長/過短的情況。

setnx和expire是兩步操作,不具有原子性。在併發中原子性是是十分重要的乙個屬性。

對初始版本的分析就完了,總結起來就是僅僅可以實現當前這種情景,功能簡易實現麻煩…是否可以有一種完美的,適合所有情況的分布式鎖呢?

原子性命令:

set key value [ex seconds] [px milliseconds] [nx|xx]

其實前面邏輯判斷那麼多就為了無衝突地set,jedis自帶了乙個多引數set:jedis.set(string key, string value, string n***, string expx, long time)。還是原子性的。

這幾個引數的意思是:

redisson框架也可以十分簡單的實現分布式鎖。**如下:

@scheduled

(cron =

"0 */1 * * * ?"

)//每1分鐘執行一次(每個1分鐘的整數倍)

public

void

closeordertask()

, threadname:{}"

, const.redis_lock.close_order_task_lock, thread.

currentthread()

.getname()

);int hour = integer.

parseint

(propertiesutil.

getproperty

("close.order.task.time.hour"))

;//執行關單業務

// iorderservice.closeorder(hour);

}else

, threadname:{}"

, const.redis_lock.close_order_task_lock, thread.

currentthread()

.getname()

);}}

catch

(interruptedexception e)

finally

//如果獲得了鎖,將其釋放

lock.

unlock()

;//釋放鎖

log.

info

("redisson分布式鎖釋放");

}}

有幾個需要注意的點:

這裡redis中存入的鎖是hash型別;

注意要在finally中釋放鎖。

目前來說最好的redis分布式方案是redis作者antirez 的redlock,這種方案也只是做到了更好,遠遠沒有到完美的程度。

Redis分布式鎖的正確實現方式

分布式鎖一般有三種實現方式 1.資料庫樂觀鎖 2.基於redis的分布式鎖 3.基於zookeeper的分布式鎖。為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件 1.互斥性。在任意時刻,只有乙個客戶端能持有鎖。2.不會發生死鎖。即使有乙個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能...

Redis分布式鎖的正確實現方式

首先,set 加入了nx引數,可以保證如果已有key存在,則函式不會呼叫成功,也就是只有乙個客戶端能持有鎖,滿足互斥性。其次,由於我們對鎖設定了過期時間,即使鎖的持有者後續發生崩潰而沒有解鎖,鎖也會因為到了過期時間而自動解鎖 即key被刪除 不會發生死鎖。最後,因為我們將value賦值為reques...

c 中正確實現單例

方法一 class mysingleton private static object s lock private static mysingleton s single null public static mysingleton singleton monitor.enter s lock i...