10. 分布式鎖有哪些場(chǎng)景

如何理解分布式鎖

為了保證在多線程下處理共享數(shù)據(jù)的安全性,需要保證同一時(shí)刻只有一個(gè)線程能處理共享數(shù)據(jù)
Java 語(yǔ)言提供了線程鎖,開(kāi)放了處理鎖機(jī)制的 API,比如 Synchronized,Lock 等
當(dāng)一個(gè)鎖被某個(gè)線程持有時(shí),另一個(gè)線程嘗試去獲取這個(gè)鎖會(huì)失敗或者阻塞
直到持有鎖的線程釋放了該鎖
單臺(tái)服務(wù)器內(nèi)存,可以通過(guò)線程加鎖的方式來(lái)同步,避免并發(fā)問(wèn)題


20230802102622.jpg

分布式鎖的常用實(shí)現(xiàn)

20230802102720.jpg
基于關(guān)系型數(shù)據(jù)庫(kù)

基于關(guān)系型數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖,是依賴數(shù)據(jù)庫(kù)的唯一性來(lái)實(shí)現(xiàn)資源鎖定,比如主鍵和唯一索引等
以唯一索引為例,創(chuàng)建一張鎖表,定義方法或資源名,失效時(shí)間等字段
同時(shí)針對(duì)鎖的信息添加唯一索引,比如方法名
當(dāng)要鎖住某個(gè)方法或資源時(shí),就在該表中插入對(duì)應(yīng)方法的一條記錄
插入成功表示獲取了鎖,想要釋放鎖的時(shí)候就刪除這條記錄

  • 創(chuàng)建一張基于數(shù)據(jù)庫(kù)的分布式鎖表
CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法或資源',
  PRIMARY KEY (`id`),
  UNIQYE KEY `uidx_method_name` (`method_name`) USIGN BTREE
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='對(duì)方法加鎖';

當(dāng)希望對(duì)某個(gè)方法加鎖時(shí),執(zhí)行以下SQL語(yǔ)句

insert into methodLock(method_name) values ('method_name'); 

如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫(kù)的話,數(shù)據(jù)庫(kù)會(huì)保證只有一個(gè)操作可以成功
可以認(rèn)為操作成功的那個(gè)線程獲得了該方法的鎖,可以執(zhí)行后面的業(yè)務(wù)邏輯
當(dāng)方法執(zhí)行完畢后,想要釋放鎖,在數(shù)據(jù)庫(kù)中刪除對(duì)應(yīng)的記錄即可

優(yōu)化
  • 存在單點(diǎn)故障風(fēng)險(xiǎn)
    數(shù)據(jù)實(shí)現(xiàn)方式強(qiáng)依賴數(shù)據(jù)庫(kù)的可用性,一旦數(shù)據(jù)庫(kù)掛掉,則會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)不可用
    解決方法:配置數(shù)據(jù)庫(kù)主從機(jī)器,防止單點(diǎn)故障
  • 超時(shí)無(wú)法失效
    一旦解鎖操作失敗,會(huì)導(dǎo)致鎖記錄一直在數(shù)據(jù)庫(kù)中,其他線程無(wú)法再獲得鎖
    解決方法:可以添加獨(dú)立的定時(shí)任務(wù),通過(guò)時(shí)間戳對(duì)比等方式,刪除超時(shí)數(shù)據(jù)
  • 不可重入
    以Java語(yǔ)言為例,常見(jiàn)的Synchronize,Lock 等都支持可重入
    在數(shù)據(jù)庫(kù)實(shí)現(xiàn)方式中,同一個(gè)線程在沒(méi)有釋放鎖
    實(shí)現(xiàn)可重入,需要改造加鎖方法,額外存儲(chǔ)在判斷線程信息,不阻塞獲得鎖的線程再次請(qǐng)求加鎖
  • 對(duì)阻塞操作不友好
    其他線程在請(qǐng)求對(duì)應(yīng)方法時(shí),插入數(shù)據(jù)失敗會(huì)直接返回,不會(huì)阻塞線程
    如果需要阻塞其他線程,需要不斷的重試 insert 操作,直到數(shù)據(jù)插入成功

應(yīng)用 Redis 緩存

緩存的性能更好,各種緩存組件也提供了多種集群方案,可以解決單點(diǎn)問(wèn)題
常見(jiàn)的開(kāi)源緩存組件都支持分布式鎖,包括 Redis,Memcached 即 Tair
應(yīng)用 Redis 實(shí)現(xiàn)分布式鎖,最直接的想法是利用 setnx 和 expire 命令實(shí)現(xiàn)加鎖
在 Redis 中,setnx 是【set if not exists】如果不存在,則 SET 的意思

  • 當(dāng)一個(gè)線程執(zhí)行 setnx 返回 1 ,說(shuō)明 key 不存在,該線程獲得鎖
  • 當(dāng)一個(gè)線程執(zhí)行 setnx 返回 0 ,說(shuō)明key 已存在,獲取鎖失敗
if (setnx(key, value) == 1) {
  expire(key, expireTime)
  try {
    // 業(yè)務(wù)處理
  } finally {
    // 釋放鎖
    del(key)
  }
}

在 Redis 版本更新中,添加了 SETEX 命令
SETEX 支持 setnx 和 expire 指令組合的原子操作
解決了加鎖過(guò)程中失敗的問(wèn)題

基于 ZooKeeper 實(shí)現(xiàn)

ZooKeeper 有四種節(jié)點(diǎn)類型:

  • 持久節(jié)點(diǎn)
  • 持久順序節(jié)點(diǎn)
  • 臨時(shí)節(jié)點(diǎn)
  • 臨時(shí)順序節(jié)點(diǎn)
利用 ZooKeeper 支持臨時(shí)順序節(jié)點(diǎn)的特性,可以實(shí)現(xiàn)分布式鎖
20230802111918.jpg

當(dāng)客戶端對(duì)某個(gè)方法加鎖時(shí),在 ZooKeeper中該方法對(duì)指定節(jié)點(diǎn)目錄下生成唯一有序節(jié)點(diǎn)。
判斷是否獲取鎖,只需要判斷持有的節(jié)點(diǎn)是否是有序節(jié)點(diǎn)中的序號(hào)最小的一個(gè)
當(dāng)釋放鎖的時(shí)候,將這個(gè)臨時(shí)節(jié)點(diǎn)刪除即可
這種方法可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無(wú)法釋放而產(chǎn)生的死鎖問(wèn)題

使用 Zookeeper 實(shí)現(xiàn)分布式鎖的算法流程,根節(jié)點(diǎn)為 /lock
  • 客戶端連接 ZooKeeper ,并在 /lock 下創(chuàng)建臨時(shí)有序子節(jié)點(diǎn)
    第一個(gè)客戶端對(duì)應(yīng)的子節(jié)點(diǎn)為 /lock/lock01/0000001 ,第二個(gè)為 /locl/lock01/00000002
  • 其他客戶端獲取 /lock01 下的子節(jié)點(diǎn)列表,判斷自己創(chuàng)建的子節(jié)點(diǎn)是否為當(dāng)前列表中序號(hào)最小的子節(jié)點(diǎn)
  • 如果是則任務(wù)獲得鎖,執(zhí)行業(yè)務(wù)代碼,否則通過(guò) watch 事件監(jiān)聽(tīng) /lock01 的子節(jié)點(diǎn)變更消息,獲得變更通知后重復(fù)此步驟直至獲得鎖
  • 完成業(yè)務(wù)流程后,刪除對(duì)應(yīng)的子節(jié)點(diǎn),釋放分布式鎖

在實(shí)際開(kāi)發(fā)中,可以應(yīng)用 Apache Curator 來(lái)快速實(shí)現(xiàn)分布式鎖
Curator 是 Netflix 公司開(kāi)源的一個(gè) ZooKeeper 客戶端
對(duì) ZooKeeper 原生 API 做了抽象和封裝

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容