分布式面試 分布式鎖的常見問題

2021-09-11 17:50:04 字數 4157 閱讀 5761

一般實現分布式鎖都有哪些方式?使用 redis 如何設計分布式鎖?使用 zk 來設計分布式鎖可以嗎?這兩種分布式鎖的實現方式哪種效率比較高?

其實一般問問題,都是這麼問的,先問問你 zk,然後其實是要過度到 zk 關聯的一些問題裡去,比如分布式鎖。因為在分布式系統開發中,分布式鎖的使用場景還是很常見的。

官方叫做redlock演算法,是 redis 官方支援的分布式鎖演算法。

這個分布式鎖有 3 個重要的考量點:

redis 最普通的分布式鎖

第乙個最普通的實現方式,就是在 redis 裡建立乙個 key,這樣就算加鎖。

set my:lock 隨機值 nx px 30000
執行這個命令就 ok。

釋放鎖就是刪除 key ,但是一般可以用lua指令碼刪除,判斷 value 一樣才刪除:

-- 刪除鎖的時候,找到 key 對應的 value,跟自己傳過去的 value 做比較,如果是一樣的才刪除。

if redis.call("get",keys[1]) == ar**[1] then

return redis.call("del",keys[1])

else

return 0

end

為啥要用隨機值呢?因為如果某個客戶端獲取到了鎖,但是阻塞了很長時間才執行完,比如說超過了 30s,此時可能已經自動釋放鎖了,此時可能別的客戶端已經獲取到了這個鎖,要是你這個時候直接刪除 key 的話會有問題,所以得用隨機值加上面的lua指令碼來釋放鎖。

但是這樣是肯定不行的。因為如果是普通的 redis 單例項,那就是單點故障。或者是 redis 普通主從,那 redis 主從非同步複製,如果主節點掛了(key 就沒有了),key 還沒同步到從節點,此時從節點切換為主節點,別人就可以 set key,從而拿到鎖。

redlock 演算法

這個場景是假設有乙個 redis cluster,有 5 個 redis master 例項。然後執行如下步驟獲取一把鎖:

獲取當前時間戳,單位是毫秒;

跟上面類似,輪流嘗試在每個 master 節點上建立鎖,過期時間較短,一般就幾十毫秒;

嘗試在大多數節點上建立乙個鎖,比如 5 個節點就要求是 3 個節點n / 2 + 1

客戶端計算建立好鎖的時間,如果建立鎖的時間小於超時時間,就算建立成功了;

要是鎖建立失敗了,那麼就依次之前建立過的鎖刪除;

只要別人建立了一把分布式鎖,你就得不斷輪詢去嘗試獲取鎖

zk 分布式鎖,其實可以做的比較簡單,就是某個節點嘗試建立臨時 znode,此時建立成功了就獲取了這個鎖;這個時候別的客戶端來建立鎖會失敗,只能註冊個***監聽這個鎖。釋放鎖就是刪除這個 znode,一旦釋放掉就會通知客戶端,然後有乙個等待著的客戶端就可以再次重新加鎖。

/**

* zookeepersession

* * @author bingo

* @since 2018/11/29

* */

public class zookeepersession catch (interruptedexception e)

system.out.println("zookeeper session established......");

} catch (exception e)

}/**

* 獲取分布式鎖

* * @param productid

*/public boolean acquiredistributedlock(long productid) catch (exception e)

zookeeper.create(path, "".getbytes(), ids.open_acl_unsafe, createmode.ephemeral);

return true;

} catch (exception ee) }}

return true;

}/**

* 釋放掉乙個分布式鎖

* * @param productid

*/public void releasedistributedlock(long productid) catch (exception e)

}/**

* 建立zk session的watcher

* * @author bingo

* @since 2018/11/29**/

private class zookeeperwatcher implements watcher

if (this.latch != null) }}

/*** 封裝單例的靜態內部類

* * @author bingo

* @since 2018/11/29**/

private static class singleton

public static zookeepersession getinstance()

}/**

* 獲取單例

* * @return

*/public static zookeepersession getinstance()

/*** 初始化單例的便捷方法

*/public static void init()

}

也可以採用另一種方式,建立臨時順序節點:

如果有一把鎖,被多個人給競爭,此時多個人會排隊,第乙個拿到鎖的人會執行,然後釋放鎖;後面的每個人都會去監聽排在自己前面的那個人建立的 node 上,一旦某個人釋放了鎖,排在自己後面的人就會被 zookeeper 給通知,一旦被通知了之後,就 ok 了,自己就獲取到了鎖,就可以執行**了。

public class zookeeperdistributedlock implements watcher  catch (ioexception e)  catch (keeperexception e)  catch (interruptedexception e) 

}public void process(watchedevent event)

if (this.latch != null)

}public void acquiredistributedlock() else

} catch (keeperexception e) catch (interruptedexception e)

}public boolean trylock()

//如果不是最小的節點,找到比自己小1的節點

int previouslockindex = -1;

for(int i = 0; i < locks.size(); i++)

}this.waitnode = locks.get(previouslockindex);

} catch (keeperexception e) catch (interruptedexception e)

return false;

}private boolean waitforlock(string waitnode, long waittime) throws interruptedexception, keeperexception

return true;

}public void unlock() catch (interruptedexception e) catch (keeperexception e)

}public class lockexception extends runtimeexception

public lockexception(exception e) }}

另外一點就是,如果是 redis 獲取鎖的那個客戶端 出現 bug 掛了,那麼只能等待超時時間之後才能釋放鎖;而 zk 的話,因為建立的是臨時 znode,只要客戶端掛了,znode 就沒了,此時就自動釋放鎖。

redis 分布式鎖大家沒發現好麻煩嗎?遍歷上鎖,計算時間等等......zk 的分布式鎖語義清晰實現簡單。

所以先不分析太多的東西,就說這兩點,我個人實踐認為 zk 的分布式鎖比 redis 的分布式鎖牢靠、而且模型簡單易用。

分布式 分布式鎖

本質是利用redis的setnx 方法的特性來加鎖,setnx 即key不存在則設定key,否則直接返回false,要求在分布式系統中使用同乙個redis服務,以下提供兩種解決方案 1 直接使用redistemplate 這其實並不能完全保證高併發下的安全問題,因為可能在鎖過期之後該執行緒尚未執行完...

分布式專題 分布式鎖

在傳統的單體應用架構中,遇到併發安全性問題時我們可以通過同步鎖synchronized,同步 塊,reentrantlock等方式都可以解決,但隨著業務的發展,單體應用架構不能滿足龐大的使用者請求量,於是分布式系統應用而生,在分布式系統中,由於每個系統都執行在不同的伺服器上,有著不同的jvm,所以j...

分布式 常見分布式框架

分布式協調系統 日誌複製系統 paxos演算法及其變體的實現,典型的有zookeeper etcd 分布式檔案系統 hdfs hadoop 分布式nosql redis hbase 訊息佇列 rabbitmq kafka,關注訊息的at least once,at most once,only on...