Redis分布式鎖 SETNX實現

2021-09-10 05:45:44 字數 4011 閱讀 2793

redis有一系列以nx結尾的命令,nx是not exists的縮寫,如setnx命令就應該理解為:set if not exists。

1. 用setnx實現分布式鎖

利用setnx非常簡單的實現分布式鎖。例如:某客戶端要獲得乙個名字foo的鎖,客戶端使用下面的命令進行獲取:

setnx lock.foo

1)如返回1,則該程序獲得鎖,把鍵lock.foo的值設定為鎖的超時時間(當前時間 + 鎖的有效時間),表示該鍵已被鎖定,該客戶端最後可以通過del lock.foo來釋放該鎖。

2)如返回0,表明該鎖已被其他程序取的,這時我們可以先返回或進行重試等對方完成或等待鎖超時。

2. 解決死鎖

考慮一種情況,如果程序獲得鎖後,斷開了與 redis 的連線(可能是程序掛掉,或者網路中斷),如果沒有有效的釋放鎖的機制,那麼其他程序都會處於一直等待的狀態,即出現「死鎖」。

上面在使用 setnx 獲得鎖時,我們將鍵 lock.foo 的值設定為鎖的有效時間,程序獲得鎖後,其他程序還會不斷的檢測鎖是否已超時,如果超時,那麼等待的程序也將有機會獲得鎖。

然而,鎖超時時,我們不能簡單地使用 del 命令刪除鍵 lock.foo 以釋放鎖。考慮以下情況,程序p1已經首先獲得了鎖 lock.foo,然後程序p1掛掉了。程序p2,p3正在不斷地檢測鎖是否已釋放或者已超時,執行流程如下:

從上面的情況可以得知,在檢測到鎖超時後,程序不能直接簡單地執行 del 刪除鍵的操作以獲得鎖。

為了解決上述演算法可能出現的多個程序同時獲得鎖的問題,我們再來看以下的演算法。 

我們同樣假設程序p1已經首先獲得了鎖 lock.foo,然後程序p1掛掉了。接下來的情況:

另外,值得注意的是,在程序釋放鎖,即執行 del lock.foo 操作前,需要先判斷鎖是否已超時。如果鎖已超時,那麼鎖可能已由其他程序獲得,這時直接執行 del lock.foo 操作會導致把其他程序已獲得的鎖釋放掉

3. 偽**如下:

1)/**

* 快取操作介面

*/public inte***ce cache extends jediscommands

2)public inte***ce lock

} catch (interruptedexception e)

}@override

public void lockinterruptibly() throws interruptedexception

@override

public boolean trylock(long time, timeunit unit) catch (interruptedexception e)

return false;

}@override

public boolean trylock() catch (interruptedexception e)

return false;

}public boolean trylockinterruptibly(long time, timeunit unit) throws interruptedexception

@override

public void unlock()

}protected void setexclusiveownerthread(thread thread)

protected final thread getexclusiveownerthread()

protected abstract void unlock0();

/*** 阻塞式獲取鎖的實現

** @param timeout

* @param unit

* @param interrupt 是否響應中斷

* @return

* @throws interruptedexception

*/protected abstract boolean lock(long timeout, timeunit unit, boolean interrupt)

throws interruptedexception;

4)public classredislockextends abstractlock

@override

protected booleanlock(long timeout, timeunit unit, boolean interrupt) throws interruptedexception

long timeoutmillis = unit == null ? 0 : unit.tomillis(timeout);

while (timeoutmillis >= 0)

long lockexpiretime = system.currenttimemillis() + lockexpires + 1;

string stringoflockexpiretime = string.valueof(lockexpiretime);

if (setnx(lockkey, stringoflockexpiretime))

string value = this.get(lockkey);

if (value != null && istimeexpired(value))

}long delaymillis = randomdelay();

long sleepmillis = timeoutmillis < delaymillis ? timeoutmillis : delaymillis;

thread.sleep(sleepmillis);

timeoutmillis = timeoutmillis - sleepmillis == 0 ? -1 : timeoutmillis - sleepmillis;

}return false;

}private long randomdelay()

public boolean islocked() else

}@override

protected void unlock0()

}private void checkinterruption() throws interruptedexception

}private boolean istimeexpired(string value)

private void dounlock()

public string getlockkey()

private string get(final string key)

private boolean setnx(final string key, final string value)

private string getset(final string key, final string value)

}5)使用場景

@service

public class testserviceimplelse

} finally

4.總結

1) setnx:先通過jediscommands的setnx(key,value)方法,設定指定key鎖的值即失效時間,如果redis中不存在該值,則返回值為1,設定成功獲得該鎖;如果返回0,繼續走第二步->

2) get:通過jediscommands的get(key)方法,獲取指定key鎖的值即失效時間,與當前時間做比較,如果大於當前時間表示還未失效,當前執行緒不能獲得鎖;如果小於當前時間即為失效,繼續走第三步->

3) getset:通過jediscommands的getset(key,value)方法,設定指定key鎖的值即新的失效時間,同時返回鍵的舊值,兩種方法判斷是否能獲得鎖:a) 將第二步get方法獲取到的健值與當前getset方法獲取的健值比較,如果相等說明中途沒有程序搶占該鎖,該程序可以獲得鎖 ;b) 將當前getset 方法獲得的最新失效時間與當前時間做比較,如果小於當前時間,說明中途沒有程序搶占該鎖也沒有通過getset設定新的失效時間,則程序可以獲得鎖。

Redis redis分布式鎖 SETNX

因業務需要使用了redis的setnx來實現分布式鎖。描述 redis有一系列的命令,特點是以nx結尾,nx是not exists的縮寫,如setnx命令就應該理解為 set if not exists。這系列的命令非常有用,這裡講使用setnx來實現分布式鎖。直接上重點 set nx 命令是快速失...

分布式鎖redis和zookeeper實現區別

一.分布式鎖解決方案 1.採用資料庫 不建議 效能不好 jdbc 2.基於redis實現分布式鎖 setnx setnx也可以存入key,如果存入key成功返回1,如果存入的key已經存在了,返回0.3.基於zookeeper實現分布式鎖 zookeeper是乙個分布式協調工具,在分布式解決方案中。...

redis分布式鎖

redis分布式鎖 直接上 我寫了四個redis分布式鎖的方法,大家可以提個意見 第一種方法 redis分布式鎖 param timeout public void lock long timeout thread.sleep 100 catch exception e override publi...