最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

分布式技術(shù)原理與實(shí)戰(zhàn)45講--第10講:分布式鎖有哪些應(yīng)用場(chǎng)景和實(shí)現(xiàn)

2023-02-17 15:54 作者:gzqhero  | 我要投稿

電商網(wǎng)站都會(huì)遇到秒殺、特價(jià)之類的活動(dòng),大促活動(dòng)有一個(gè)共同特點(diǎn)就是訪問量激增,在高并發(fā)下會(huì)出現(xiàn)成千上萬人搶購一個(gè)商品的場(chǎng)景。雖然在系統(tǒng)設(shè)計(jì)時(shí)會(huì)通過限流、異步、排隊(duì)等方式優(yōu)化,但整體的并發(fā)還是平時(shí)的數(shù)倍以上,參加活動(dòng)的商品一般都是限量庫存,如何防止庫存超賣,避免并發(fā)問題呢?分布式鎖就是一個(gè)解決方案。

如何理解分布式鎖

我們都知道,在業(yè)務(wù)開發(fā)中,為了保證在多線程下處理共享數(shù)據(jù)的安全性,需要保證同一時(shí)刻只有一個(gè)線程能處理共享數(shù)據(jù)。

Java 語言給我們提供了線程鎖,開放了處理鎖機(jī)制的 API,比如 Synchronized、Lock 等。當(dāng)一個(gè)鎖被某個(gè)線程持有的時(shí)候,另一個(gè)線程嘗試去獲取這個(gè)鎖會(huì)失敗或者阻塞,直到持有鎖的線程釋放了該鎖。

在單臺(tái)服務(wù)器內(nèi)部,可以通過線程加鎖的方式來同步,避免并發(fā)問題,那么在分布式場(chǎng)景下呢?

分布式場(chǎng)景下解決并發(fā)問題,需要應(yīng)用分布式鎖技術(shù)。如上圖所示,分布式鎖的目的是保證在分布式部署的應(yīng)用集群中,多個(gè)服務(wù)在請(qǐng)求同一個(gè)方法或者同一個(gè)業(yè)務(wù)操作的情況下,對(duì)應(yīng)業(yè)務(wù)邏輯只能被一臺(tái)機(jī)器上的一個(gè)線程執(zhí)行,避免出現(xiàn)并發(fā)問題。

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

實(shí)現(xiàn)分布式鎖目前有三種流行方案,即基于數(shù)據(jù)庫、Redis、ZooKeeper 的方案。

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

基于關(guān)系型數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖,是依賴數(shù)據(jù)庫的唯一性來實(shí)現(xiàn)資源鎖定,比如主鍵和唯一索引等。

以唯一索引為例,創(chuàng)建一張鎖表,定義方法或者資源名、失效時(shí)間等字段,同時(shí)針對(duì)加鎖的信息添加唯一索引,比如方法名,當(dāng)要鎖住某個(gè)方法或資源時(shí),就在該表中插入對(duì)應(yīng)方法的一條記錄,插入成功表示獲取了鎖,想要釋放鎖的時(shí)候就刪除這條記錄。

下面創(chuàng)建一張基于數(shù)據(jù)庫的分布式鎖表:

CREATE TABLE `methodLock` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',`method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '鎖定的方法或者資源',PRIMARY KEY (`id`),UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='對(duì)方法加鎖';

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

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

在數(shù)據(jù)表定義中,我們對(duì) method_name 做了唯一性約束,如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫的話,數(shù)據(jù)庫會(huì)保證只有一個(gè)操作可以成功,那么就可以認(rèn)為操作成功的那個(gè)線程獲得了該方法的鎖,可以執(zhí)行后面的業(yè)務(wù)邏輯。

當(dāng)方法執(zhí)行完畢之后,想要釋放鎖的話,在數(shù)據(jù)庫中刪除對(duì)應(yīng)的記錄即可。

基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖操作簡(jiǎn)單,但是并不是一個(gè)可以落地的方案,有很多地方需要優(yōu)化。

存在單點(diǎn)故障風(fēng)險(xiǎn)

數(shù)據(jù)庫實(shí)現(xiàn)方式強(qiáng)依賴數(shù)據(jù)庫的可用性,一旦數(shù)據(jù)庫掛掉,則會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)不可用,為了解決這個(gè)問題,需要配置數(shù)據(jù)庫主從機(jī)器,防止單點(diǎn)故障。

超時(shí)無法失效

如果一旦解鎖操作失敗,則會(huì)導(dǎo)致鎖記錄一直在數(shù)據(jù)庫中,其他線程無法再獲得鎖,解決這個(gè)問題,可以添加獨(dú)立的定時(shí)任務(wù),通過時(shí)間戳對(duì)比等方式,刪除超時(shí)數(shù)據(jù)。

不可重入

可重入性是鎖的一個(gè)重要特性,以 Java 語言為例,常見的 Synchronize、Lock 等都支持可重入。在數(shù)據(jù)庫實(shí)現(xiàn)方式中,同一個(gè)線程在沒有釋放鎖之前無法再次獲得該鎖,因?yàn)閿?shù)據(jù)已經(jīng)存在,再次插入會(huì)失敗。實(shí)現(xiàn)可重入,需要改造加鎖方法,額外存儲(chǔ)和判斷線程信息,不阻塞獲得鎖的線程再次請(qǐng)求加鎖。

無法實(shí)現(xiàn)阻塞

其他線程在請(qǐng)求對(duì)應(yīng)方法時(shí),插入數(shù)據(jù)失敗會(huì)直接返回,不會(huì)阻塞線程,如果需要阻塞其他線程,需要不斷的重試 insert 操作,直到數(shù)據(jù)插入成功,這個(gè)操作是服務(wù)器和數(shù)據(jù)庫資源的極大浪費(fèi)。

可以看到,借助數(shù)據(jù)庫實(shí)現(xiàn)一個(gè)完備的分布式鎖,存在很多問題,并且讀寫數(shù)據(jù)庫需要一定的性能,可能會(huì)影響業(yè)務(wù)執(zhí)行的耗時(shí)。

下面我們來看下應(yīng)用緩存如何實(shí)現(xiàn)。

應(yīng)用 Redis 緩存

相比基于數(shù)據(jù)庫實(shí)現(xiàn)分布式鎖,緩存的性能更好,并且各種緩存組件也提供了多種集群方案,可以解決單點(diǎn)問題。

常見的開源緩存組件都支持分布式鎖,包括 Redis、Memcached 及 Tair。以常見的 Redis 為例,應(yīng)用 Redis 實(shí)現(xiàn)分布式鎖,最直接的想法是利用 setnx 和 expire 命令實(shí)現(xiàn)加鎖。

在 Redis 中,setnx 是「set if not exists」如果不存在,則 SET 的意思,當(dāng)一個(gè)線程執(zhí)行 setnx 返回 1,說明 key 不存在,該線程獲得鎖;當(dāng)一個(gè)線程執(zhí)行 setnx 返回 0,說明 key 已經(jīng)存在,那么獲取鎖失敗,expire 就是給鎖加一個(gè)過期時(shí)間。

偽代碼如下:

if(setnx(key,value)==1){ ? ? expire(key,expireTime) ? ? try{ ? ? ? ?//業(yè)務(wù)處理 ? ? }finally{ ? ? ? //釋放鎖 ? ? ? del(key) ? ? }}

使用 setnx 和 expire 有一個(gè)問題,這兩條命令可能不會(huì)同時(shí)失敗,不具備原子性,如果一個(gè)線程在執(zhí)行完 setnx 之后突然崩潰,導(dǎo)致鎖沒有設(shè)置過期時(shí)間,那么這個(gè)鎖就會(huì)一直存在,無法被其他線程獲取。

為了解決這個(gè)問題,在 Redis 2.8 版本中,添加了 SETEX 命令,SETEX 支持 setnx 和 expire 指令組合的原子操作,解決了加鎖過程中失敗的問題。

添加 SETEX 命令, 就是一個(gè)完善的分布式鎖嗎?在下一課時(shí)的內(nèi)容中我會(huì)詳細(xì)分享。

基于 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)分布式鎖。

當(dāng)客戶端對(duì)某個(gè)方法加鎖時(shí),在 ZooKeeper 中該方法對(duì)應(yīng)的指定節(jié)點(diǎn)目錄下,生成一個(gè)唯一的臨時(shí)有序節(jié)點(diǎn)。

判斷是否獲取鎖,只需要判斷持有的節(jié)點(diǎn)是否是有序節(jié)點(diǎn)中序號(hào)最小的一個(gè),當(dāng)釋放鎖的時(shí)候,將這個(gè)臨時(shí)節(jié)點(diǎn)刪除即可,這種方式可以避免服務(wù)宕機(jī)導(dǎo)致的鎖無法釋放而產(chǎ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/00000001,第二個(gè)為 /lock/lock01/00000002;

  • 其他客戶端獲取 /lock01 下的子節(jié)點(diǎn)列表,判斷自己創(chuàng)建的子節(jié)點(diǎn)是否為當(dāng)前列表中序號(hào)最小的子節(jié)點(diǎn);

  • 如果是則認(rèn)為獲得鎖,執(zhí)行業(yè)務(wù)代碼,否則通過 watch 事件監(jiān)聽 /lock01 的子節(jié)點(diǎn)變更消息,獲得變更通知后重復(fù)此步驟直至獲得鎖;

  • 完成業(yè)務(wù)流程后,刪除對(duì)應(yīng)的子節(jié)點(diǎn),釋放分布式鎖。

在實(shí)際開發(fā)中,可以應(yīng)用 Apache Curator 來快速實(shí)現(xiàn)分布式鎖,Curator 是 Netflix 公司開源的一個(gè) ZooKeeper 客戶端,對(duì) ZooKeeper 原生 API 做了抽象和封裝,若感興趣可自行查詢資料了解。

總結(jié)

這一課時(shí)分享了分布式鎖的應(yīng)用場(chǎng)景和幾種實(shí)現(xiàn),包括分布式鎖的概念,使用數(shù)據(jù)庫方式、緩存和 ZooKeeper 實(shí)現(xiàn)分布式鎖等。


分布式技術(shù)原理與實(shí)戰(zhàn)45講--第10講:分布式鎖有哪些應(yīng)用場(chǎng)景和實(shí)現(xiàn)的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
甘南县| 湛江市| 九江县| 庆城县| 开封市| 郓城县| 芒康县| 陈巴尔虎旗| 吴桥县| 常熟市| 甘孜县| 赤峰市| 当阳市| 德江县| 乡宁县| 横峰县| 治县。| 东乡| 天门市| 游戏| 辽宁省| 蛟河市| 荆门市| 睢宁县| 凤阳县| 阳信县| 许昌市| 沂水县| 南川市| 雷山县| 瑞安市| 义马市| 安岳县| 吕梁市| 鹤峰县| 天柱县| 昌江| 汝南县| 抚远县| 额敏县| 阳城县|