談?wù)勀銓?duì)分布式鎖的理解 設(shè)計(jì)一個(gè)分布式鎖需要考慮哪些問(wèn)題 Redis分布式鎖的關(guān)鍵命令是什么

分布式鎖
我們?cè)陂_(kāi)發(fā)應(yīng)用的時(shí)候,如果需要對(duì)某一個(gè)共享變量進(jìn)行多線程同步訪問(wèn)的時(shí)候源譬,可以使用我們學(xué)到的鎖進(jìn)行處理集惋,并且可以完美的運(yùn)行,毫無(wú)Bug踩娘!
在傳統(tǒng)單體應(yīng)用單機(jī)部署的情況下刮刑,可以使用并發(fā)處理相關(guān)的功能進(jìn)行互斥控制。但是养渴,隨著業(yè)務(wù)發(fā)展的需要雷绢,原單體單機(jī)部署的系統(tǒng)被演化成分布式集群系統(tǒng)后,由于分布式系統(tǒng)多線程理卑、多進(jìn)程并且分布在不同機(jī)器上翘紊,這將使原單機(jī)部署情況下的并發(fā)控制鎖策略失效,單純的應(yīng)用并不能提供分布式鎖的能力藐唠。為了解決這個(gè)問(wèn)題就需要一種跨機(jī)器的互斥機(jī)制來(lái)控制共享資源的訪問(wèn)帆疟,這就是分布式鎖要解決的問(wèn)題鹉究!
在分析分布式鎖的三種實(shí)現(xiàn)方式之前,先了解一下分布式鎖應(yīng)該具備哪些條件:
1踪宠、在分布式系統(tǒng)環(huán)境下自赔,一個(gè)方法在同一時(shí)間只能被一個(gè)機(jī)器的一個(gè)線程執(zhí)行;
2柳琢、高可用的獲取鎖與釋放鎖绍妨;
3、高性能的獲取鎖與釋放鎖柬脸;
4他去、具備可重入特性;
5倒堕、具備鎖失效機(jī)制灾测,防止死鎖;
6垦巴、具備阻塞鎖特性行施,即沒(méi)有獲取到鎖將直接返回獲取鎖失敗。
目前幾乎很多大型網(wǎng)站及應(yīng)用都是分布式部署的魂那,分布式場(chǎng)景中的數(shù)據(jù)一致性問(wèn)題一直是一個(gè)比較重要的話題蛾号。分布式的CAP理論告訴我們“任何一個(gè)分布式系統(tǒng)都無(wú)法同時(shí)滿足一致性(Consistency)、可用性(Availability)和分區(qū)容錯(cuò)性(Partition tolerance)涯雅,最多只能同時(shí)滿足兩項(xiàng)鲜结。”所以活逆,很多系統(tǒng)在設(shè)計(jì)之初就要對(duì)這三者做出取舍精刷。在互聯(lián)網(wǎng)領(lǐng)域的絕大多數(shù)的場(chǎng)景中,都需要犧牲強(qiáng)一致性來(lái)?yè)Q取系統(tǒng)的高可用性蔗候,系統(tǒng)往往只需要保證“最終一致性”怒允,只要這個(gè)最終時(shí)間是在用戶可以接受的范圍內(nèi)即可。

在很多場(chǎng)景中锈遥,我們?yōu)榱吮WC數(shù)據(jù)的最終一致性纫事,需要很多的技術(shù)方案來(lái)支持,比如分布式事務(wù)所灸、分布式鎖等丽惶。有的時(shí)候,我們需要保證一個(gè)方法在同一時(shí)間內(nèi)只能被同一個(gè)線程執(zhí)行爬立。
基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖钾唬;
基于緩存(Redis等)實(shí)現(xiàn)分布式鎖;
基于Zookeeper實(shí)現(xiàn)分布式鎖;

現(xiàn)在一一分析
基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方式

基于數(shù)據(jù)庫(kù)的實(shí)現(xiàn)方式的核心思想是:在數(shù)據(jù)庫(kù)中創(chuàng)建一個(gè)表抡秆,表中包含方法名等字段奕巍,并在方法名字段上創(chuàng)建唯一索引,想要執(zhí)行某個(gè)方法儒士,就使用這個(gè)方法名向表中插入數(shù)據(jù)请琳,成功插入則獲取鎖胡桃,執(zhí)行完成后刪除對(duì)應(yīng)的行數(shù)據(jù)釋放鎖啡专。
DROP TABLE IF EXISTS method_lock;
CREATE TABLE method_lock (
id int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵',
method_name varchar(64) NOT NULL COMMENT '鎖定的方法名',
desc varchar(255) NOT NULL COMMENT '備注信息',
update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uidx_method_name (method_name) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';
(2)想要執(zhí)行某個(gè)方法怜俐,就使用這個(gè)方法名向表中插入數(shù)據(jù):

INSERT INTO method_lock ( method_name, desc) VALUES ('methodName', '測(cè)試的methodName');

因?yàn)槲覀儗?duì)method_name做了唯一性約束效床,這里如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫(kù)的話睹酌,數(shù)據(jù)庫(kù)會(huì)保證只有一個(gè)操作可以成功,那么我們就可以認(rèn)為操作成功的那個(gè)線程獲得了該方法的鎖剩檀,可以執(zhí)行方法體內(nèi)容憋沿。

(3)成功插入則獲取鎖,執(zhí)行完成后刪除對(duì)應(yīng)的行數(shù)據(jù)釋放鎖:
delete from method_lock where method_name ='methodName';
注意:這只是使用基于數(shù)據(jù)庫(kù)的一種方法沪猴,使用數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖還有很多其他的玩法辐啄!

使用基于數(shù)據(jù)庫(kù)的這種實(shí)現(xiàn)方式很簡(jiǎn)單,但是對(duì)于分布式鎖應(yīng)該具備的條件來(lái)說(shuō)运嗜,它有一些問(wèn)題需要解決及優(yōu)化:

1壶辜、因?yàn)槭腔跀?shù)據(jù)庫(kù)實(shí)現(xiàn)的,數(shù)據(jù)庫(kù)的可用性和性能將直接影響分布式鎖的可用性及性能担租,所以砸民,數(shù)據(jù)庫(kù)需要雙機(jī)部署、數(shù)據(jù)同步奋救、主備切換岭参;

2、不具備可重入的特性尝艘,因?yàn)橥粋€(gè)線程在釋放鎖之前演侯,行數(shù)據(jù)一直存在,無(wú)法再次成功插入數(shù)據(jù)背亥,所以秒际,需要在表中新增一列,用于記錄當(dāng)前獲取到鎖的機(jī)器和線程信息狡汉,在再次獲取鎖的時(shí)候程癌,先查詢表中機(jī)器和線程信息是否和當(dāng)前機(jī)器和線程相同,若相同則直接獲取鎖轴猎;

3嵌莉、沒(méi)有鎖失效機(jī)制,因?yàn)橛锌赡艹霈F(xiàn)成功插入數(shù)據(jù)后捻脖,服務(wù)器宕機(jī)了锐峭,對(duì)應(yīng)的數(shù)據(jù)沒(méi)有被刪除中鼠,當(dāng)服務(wù)恢復(fù)后一直獲取不到鎖,所以沿癞,需要在表中新增一列援雇,用于記錄失效時(shí)間,并且需要有定時(shí)任務(wù)清除這些失效的數(shù)據(jù)椎扬;

4惫搏、不具備阻塞鎖特性,獲取不到鎖直接返回失敗蚕涤,所以需要優(yōu)化獲取邏輯筐赔,循環(huán)多次去獲取。

5揖铜、在實(shí)施的過(guò)程中會(huì)遇到各種不同的問(wèn)題茴丰,為了解決這些問(wèn)題,實(shí)現(xiàn)方式將會(huì)越來(lái)越復(fù)雜天吓;依賴數(shù)據(jù)庫(kù)需要一定的資源開(kāi)銷贿肩,性能問(wèn)題需要考慮。

基于Redis的實(shí)現(xiàn)方式
1龄寞、選用Redis實(shí)現(xiàn)分布式鎖原因:
(1)Redis有很高的性能汰规;
(2)Redis命令對(duì)此支持較好,實(shí)現(xiàn)起來(lái)比較方便

2物邑、使用命令介紹:
(1)SETNX
SETNX key val:當(dāng)且僅當(dāng)key不存在時(shí)控轿,set一個(gè)key為val的字符串,返回1拂封;若key存在茬射,則什么都不做,返回0冒签。

(2)expire

expire key timeout:為key設(shè)置一個(gè)超時(shí)時(shí)間在抛,單位為second,超過(guò)這個(gè)時(shí)間鎖會(huì)自動(dòng)釋放萧恕,避免死鎖刚梭。

(3)delete
delete key:刪除key
在使用Redis實(shí)現(xiàn)分布式鎖的時(shí)候,主要就會(huì)使用到這三個(gè)命令票唆。

3朴读、實(shí)現(xiàn)思想:

(1)獲取鎖的時(shí)候,使用setnx加鎖走趋,并使用expire命令為鎖添加一個(gè)超時(shí)時(shí)間衅金,超過(guò)該時(shí)間則自動(dòng)釋放鎖,鎖的value值為一個(gè)隨機(jī)生成的UUID,通過(guò)此在釋放鎖的時(shí)候進(jìn)行判斷氮唯。
(2)獲取鎖的時(shí)候還設(shè)置一個(gè)獲取的超時(shí)時(shí)間鉴吹,若超過(guò)這個(gè)時(shí)間則放棄獲取鎖。
(3)釋放鎖的時(shí)候惩琉,通過(guò)UUID判斷是不是該鎖豆励,若是該鎖,則執(zhí)行delete進(jìn)行鎖釋放瞒渠。

4良蒸、 分布式鎖的簡(jiǎn)單實(shí)現(xiàn)代碼:

連接redis

redis_client = redis.Redis(host="localhost",
port=6379,
password=password,
db=10)

獲取一個(gè)鎖

lock_name:鎖定名稱
acquire_time: 客戶端等待獲取鎖的時(shí)間
time_out: 鎖的超時(shí)時(shí)間
def acquire_lock(lock_name, acquire_time=10, time_out=10):
"""獲取一個(gè)分布式鎖"""
identifier = str(uuid.uuid4())
end = time.time() + acquire_time
lock = "string:lock:" + lock_name
while time.time() < end:
if redis_client.setnx(lock, identifier):
# 給鎖設(shè)置超時(shí)時(shí)間, 防止進(jìn)程崩潰導(dǎo)致其他進(jìn)程無(wú)法獲取鎖
redis_client.expire(lock, time_out)
return identifier
elif not redis_client.ttl(lock):
redis_client.expire(lock, time_out)
time.sleep(0.001)
return False

釋放一個(gè)鎖

def release_lock(lock_name, identifier):
"""通用的鎖釋放函數(shù)"""
lock = "string:lock:" + lock_name
pip = redis_client.pipeline(True)
while True:
try:
pip.watch(lock)
lock_value = redis_client.get(lock)
if not lock_value:
return True

        if lock_value.decode() == identifier:
            pip.multi()
            pip.delete(lock)
            pip.execute()
            return True
        pip.unwatch()
        break
    except redis.excetions.WacthcError:
        pass
return False
  1. 測(cè)試剛才實(shí)現(xiàn)的分布式鎖
    例子中使用50個(gè)線程模擬秒殺一個(gè)商品,使用–運(yùn)算符來(lái)實(shí)現(xiàn)商品減少伍玖,從結(jié)果有序性就可以看出是否為加鎖狀態(tài)嫩痰。
    def seckill():
    identifier=acquire_lock('resource')
    print(Thread.getName(),"獲得了鎖")
    release_lock('resource',identifier)
    for i in range(50):
    t = Thread(target=seckill)
    t.start()

六、基于ZooKeeper的實(shí)現(xiàn)方式
ooKeeper是一個(gè)為分布式應(yīng)用提供一致性服務(wù)的開(kāi)源組件私沮,它內(nèi)部是一個(gè)分層的文件系統(tǒng)目錄樹(shù)結(jié)構(gòu),規(guī)定同一個(gè)目錄下只能有一個(gè)唯一文件名和橙∽醒啵基于ZooKeeper實(shí)現(xiàn)分布式鎖的步驟如下:

(1)創(chuàng)建一個(gè)目錄mylock;
(2)線程A想獲取鎖就在mylock目錄下創(chuàng)建臨時(shí)順序節(jié)點(diǎn)魔招;
(3)獲取mylock目錄下所有的子節(jié)點(diǎn)晰搀,然后獲取比自己小的兄弟節(jié)點(diǎn),如果不存在办斑,則說(shuō)明當(dāng)前線程順序號(hào)最小外恕,獲得鎖;
(4)線程B獲取所有節(jié)點(diǎn)乡翅,判斷自己不是最小節(jié)點(diǎn)鳞疲,設(shè)置監(jiān)聽(tīng)比自己次小的節(jié)點(diǎn);
(5)線程A處理完蠕蚜,刪除自己的節(jié)點(diǎn)尚洽,線程B監(jiān)聽(tīng)到變更事件,判斷自己是不是最小的節(jié)點(diǎn)靶累,如果是則獲得鎖腺毫。

這里推薦一個(gè)Apache的開(kāi)源庫(kù)Curator,它是一個(gè)ZooKeeper客戶端挣柬,Curator提供的InterProcessMutex是分布式鎖的實(shí)現(xiàn)潮酒,acquire方法用于獲取鎖,release方法用于釋放鎖邪蛔。

優(yōu)點(diǎn):具備高可用急黎、可重入、阻塞鎖特性,可解決失效死鎖問(wèn)題叁熔。
缺點(diǎn):因?yàn)樾枰l繁的創(chuàng)建和刪除節(jié)點(diǎn)委乌,性能上不如Redis方式。
上面的三種實(shí)現(xiàn)方式荣回,沒(méi)有在所有場(chǎng)合都是完美的遭贸,所以,應(yīng)根據(jù)不同的應(yīng)用場(chǎng)景選擇最適合的實(shí)現(xiàn)方式心软。

在分布式環(huán)境中壕吹,對(duì)資源進(jìn)行上鎖有時(shí)候是很重要的,比如搶購(gòu)某一資源删铃,這時(shí)候使用分布式鎖就可以很好地控制資源耳贬。
當(dāng)然,在具體使用中猎唁,還需要考慮很多因素咒劲,比如超時(shí)時(shí)間的選取,獲取鎖時(shí)間的選取對(duì)并發(fā)量都有很大的影響诫隅,上述實(shí)現(xiàn)的分布式鎖也只是一種簡(jiǎn)單的實(shí)現(xiàn)腐魂,主要是一種思想

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市逐纬,隨后出現(xiàn)的幾起案子蛔屹,更是在濱河造成了極大的恐慌,老刑警劉巖豁生,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兔毒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡甸箱,警方通過(guò)查閱死者的電腦和手機(jī)育叁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)芍殖,“玉大人擂红,你說(shuō)我怎么就攤上這事∥。” “怎么了昵骤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)肯适。 經(jīng)常有香客問(wèn)我变秦,道長(zhǎng),這世上最難降的妖魔是什么框舔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任蹦玫,我火速辦了婚禮赎婚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘樱溉。我一直安慰自己挣输,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布福贞。 她就那樣靜靜地躺著撩嚼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪挖帘。 梳的紋絲不亂的頭發(fā)上完丽,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音拇舀,去河邊找鬼逻族。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骄崩,可吹牛的內(nèi)容都是我干的聘鳞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼要拂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼抠璃!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起宇弛,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鸡典,失蹤者是張志新(化名)和其女友劉穎源请,沒(méi)想到半個(gè)月后枪芒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡谁尸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年舅踪,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片良蛮。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抽碌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出决瞳,到底是詐尸還是另有隱情货徙,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布皮胡,位于F島的核電站痴颊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏屡贺。R本人自食惡果不足惜蠢棱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一锌杀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泻仙,春花似錦糕再、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至冤吨,卻和暖如春蒿柳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漩蟆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工垒探, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人怠李。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓圾叼,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捺癞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夷蚊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容