Redis 分布式鎖
? ? 傳統(tǒng)setnx 方法(先 status = setnx(key ,valiue) 判斷status 是否為 1 ,如果為1 表示占用鎖成功 窄驹,占用鎖成功后 再設置失效時間? )? 存在的問題? setnx 和expire 不是原子操作,如果expire 失敗了? 就沒有失效時間涵紊,當前線程會一直占用鎖不釋放淆珊。所以需要對 setnx 和 expire 操作保證原子性 。
? ? ?從 Redis 2.6.12 版本開始装盯,SET命令的行為可以通過一系列參數(shù)來修改:
EX?second?:設置鍵的過期時間為?second?秒。?SET?key?value?EX?second?效果等同于?SETEX?key?second?value?甲馋。
PX?millisecond?:設置鍵的過期時間為?millisecond?毫秒埂奈。?SET?key?value?PX?millisecond?效果等同于?PSETEX?keymillisecond?value?。
NX?:只在鍵不存在時定躏,才對鍵進行設置操作账磺。?SET?key?value?NX?效果等同于?SETNX?key?value?。
XX?:只在鍵已經存在時痊远,才對鍵進行設置操作垮抗。
?redisCluster.set(key,"1","nx","ex",100);? //如果 key 不存在的話 往緩存中存 value =1 存100秒 ,設置成功以后 返回“OK”
? ? ?這種方法可以保證獲取鎖的原子性 不會因為某個線程獲取鎖時redis 掛掉等導致的長期占有鎖的情況碧聪。目前筆者的部門采用的就是此種方法冒版。但是此方法仍然存在一定的問題。
? ? ?假設一個線程持有鎖的有效時間是10秒鐘矾削,但是由于業(yè)務復雜 在持有鎖期間線程1 的任務沒有執(zhí)行完畢 壤玫,此時鎖已經開放,線程2 可以持有鎖哼凯,當線程1 任務執(zhí)行完畢以后會進行del(key)釋放鎖的操作欲间,但是此時釋放的鎖確實線程2 持有的鎖,此時就會有問題断部。
? ? 所以可以在釋放鎖(del)之前進行判斷一下 持有鎖的線程是不是 當前的線程猎贴。
? ? 加鎖的時候? StringthreadId =?Thread.currentThread().getId()? set(key,threaId,nx,ex,100);
? ? 釋放鎖的時候 if(threadId?.equals(redisClient.get(key))){del(key)}
? ? 但是由于 判斷的操作不是原子性操作,仍然有上述的原子性問題,所以要想實現(xiàn)驗證和刪除過程的原子性她渴,可以使用Lua腳本來實現(xiàn)达址。這樣就能保證驗證和刪除過程的正確性了。
? ? 如果擔心鎖要失效的時候 任務沒有執(zhí)行完畢趁耗,我們可以讓獲得鎖的線程開啟一個守護線程沉唠,用來給快要過期的鎖“續(xù)航”。
? ? 事實上這類瑣最大的缺點就是它加鎖時只作用在一個Redis節(jié)點上苛败,即使Redis通過sentinel保證高可用满葛,如果這個master節(jié)點由于某些原因發(fā)生了主從切換,那么就會出現(xiàn)鎖丟失的情況:
在Redis的master節(jié)點上拿到了鎖罢屈;
但是這個加鎖的key還沒有同步到slave節(jié)點嘀韧;
master故障,發(fā)生故障轉移缠捌,slave節(jié)點升級為master節(jié)點锄贷;
導致鎖丟失。
? ? 正因為如此曼月,Redis作者antirez基于分布式環(huán)境下提出了一種更高級的分布式鎖的實現(xiàn)方式:Redlock
Redlock原理后續(xù)會繼續(xù)討論谊却。