一 鎖的理解
作為一個(gè)編程人員,一般對(duì)鎖都比較熟悉。鎖车份,在生活中就是控制對(duì)一個(gè)資源的獨(dú)特占有權(quán),比如我用一把鎖鎖上自行車牡彻,這輛自行車在鎖上期間是完全屬于我的扫沼,因?yàn)橹挥形矣羞@個(gè)鎖的鑰匙;當(dāng)然如果我鎖上自行車庄吼,我也沒辦法騎車了:)缎除,也就是說現(xiàn)實(shí)中的鎖只是自己不使用的時(shí)候,一種獨(dú)特的防止別人來用的手段总寻,是一種防止共享的手段器罐。
計(jì)算機(jī)中鎖只是借助這種獨(dú)特的占有權(quán)的思想(算是臨時(shí)占有權(quán)),和現(xiàn)實(shí)中相反的是渐行,鎖是為了共享轰坊,是為了更好地共享,解決共享沖突而采用的手段祟印;在現(xiàn)實(shí)生活中肴沫,上鎖了,任何人無法使用了旁理;計(jì)算中上了鎖樊零,只能自己使用了,用完之后孽文,釋放了鎖驻襟,其他使用者才可以訪問。
由于計(jì)算機(jī)發(fā)展中多核芋哭,多cpu的出現(xiàn)沉衣,或者因?yàn)橐恍㊣O密集型程序,需要等待IO操作减牺,如果需要提高應(yīng)用的整體性能豌习,只能提高并發(fā)性,比如增加線程拔疚,增加進(jìn)程等肥隆。但是有些資源確實(shí)需要共享的,比如售票系統(tǒng)或電子商務(wù)系統(tǒng)稚失,票數(shù)或待售物品栋艳。這樣就帶來了,如何控制多個(gè)執(zhí)行體同一時(shí)刻訪問資源的問題句各,以免發(fā)生沖突吸占。
二 鎖的分類
鎖有很多種分類方法晴叨,有悲觀鎖,樂觀鎖矾屯;數(shù)據(jù)庫中的鎖有行級(jí)鎖兼蕊,表級(jí)鎖;悲觀鎖件蚕,就是認(rèn)為任務(wù)資源大部分時(shí)間是被別人占用的孙技,所以每次訪問均需要加鎖,如果加鎖不成功要么等待骤坐,要么報(bào)錯(cuò)绪杏,使用完成后,釋放鎖別人才可以使用纽绍;樂觀鎖就是樂觀地認(rèn)為需要訪問的資源是自己獨(dú)占的蕾久,每次直接操作,但是訪問控制還是要做的拌夏,如何做那僧著,這里面一般通過版本號(hào)來控制,比如數(shù)據(jù)庫中的多版本障簿,solr查詢中的多版本盹愚。
舉個(gè)樂觀鎖的例子,比如我們?cè)诟聅olr的索引中的字段的時(shí)候站故,可以帶上:version字段信息:
1. 如果_version_<0 如果文檔存在皆怕,則拒絕更新,不存在就添加西篓。
2.如果_version_= 0 如果文檔存在愈腾,則更新,不存在則添加岂津。
3.如果_version_ =1 如果文檔存在虱黄,則更新;不存在則拒絕更新吮成。
4.如果_version_ > 1 如果文檔的版本號(hào)和verison一致橱乱,則更新,不一致則拒絕粱甫。[這里面就用樂觀鎖的機(jī)制泳叠,通過版本號(hào)來解決并發(fā)沖突問題]
在Java中synchronized就是重量級(jí)鎖,是悲觀鎖茶宵;Java中通過CAS思想實(shí)現(xiàn)的類都是采用樂觀鎖的機(jī)制析二,比如java.util.concurrent.atomaic類。
三 分布式鎖
在編程中,Java中最常用的鎖比如synchronized等叶摄,ReentrantLock 等鎖,只適合單JVM環(huán)境安拟,沒辦法跨JVM共享的蛤吓,所以在剛才提到的大型的售票系統(tǒng),或電子商務(wù)中等高并發(fā)系統(tǒng)中糠赦,需要分布式鎖來控制共享資源的訪問会傲。
3.1 數(shù)據(jù)庫實(shí)現(xiàn)的分布式鎖
最簡(jiǎn)單的實(shí)現(xiàn),是通過select for update拙泽,這種方法鎖住了當(dāng)前數(shù)據(jù)淌山,防止其他并發(fā)進(jìn)程來操作。
好處:解決了分布式鎖的問題顾瞻;
壞處:
1)這種鎖會(huì)一直鎖住數(shù)據(jù)泼疑,直到事務(wù)提交時(shí)候才會(huì)釋放鎖。
2)需要設(shè)置事務(wù)的隔離級(jí)別為read commited荷荤,如果設(shè)置到其他高并發(fā)的級(jí)別退渗,仍然會(huì)其他線程讀到未提交數(shù)據(jù),從而導(dǎo)致超賣的可能蕴纳。
3)如果事務(wù)的操作時(shí)間太長(zhǎng)会油,長(zhǎng)時(shí)間不釋放,則容易打滿數(shù)據(jù)庫連接古毛;特別是事務(wù)操作中翻翩,還有第三方接口調(diào)用的時(shí)候。
4)容易產(chǎn)生交叉死鎖稻薇,在業(yè)務(wù)加鎖的時(shí)候要控制下加表的順序嫂冻,如果控制不好就會(huì)產(chǎn)生交叉死鎖的問題。
3.2 使用zooKeeper實(shí)現(xiàn)分布式鎖
作為分布式協(xié)調(diào)系統(tǒng)颖低,zooKeeper支持四種節(jié)點(diǎn)類型:
1.持久性節(jié)點(diǎn)絮吵。
2.持久性順序節(jié)點(diǎn)。
3.臨時(shí)節(jié)點(diǎn)忱屑。
4.臨時(shí)性順序節(jié)點(diǎn)蹬敲。
所謂臨時(shí)節(jié)點(diǎn),是通過客戶端心跳來控制的莺戒,如果客戶端掛了伴嗡,則超過一定時(shí)間沒有心跳連接,zookeeper就會(huì)把這個(gè)臨時(shí)節(jié)點(diǎn)刪除掉从铲,防止客戶端突然掛掉而導(dǎo)致鎖一直無法釋放的問題瘪校。
我曾經(jīng)利用zk來實(shí)現(xiàn)一個(gè)鎖,來解決多個(gè)程序同時(shí)創(chuàng)建solr的collection問題,利用的是臨時(shí)節(jié)點(diǎn)阱扬,實(shí)現(xiàn)原理很簡(jiǎn)單:
1)每個(gè)入solr索引的進(jìn)程再查詢到solr的collection不存在的時(shí)候泣懊,會(huì)再特定的目錄下創(chuàng)建臨時(shí)節(jié)點(diǎn)。
2)如果多個(gè)進(jìn)程同時(shí)創(chuàng)建麻惶,肯定存在創(chuàng)建成功和失敗的情況馍刮,創(chuàng)建成功的就稱為leader,負(fù)責(zé)collection的新建窃蹋。
3)創(chuàng)建節(jié)點(diǎn)失敗的則進(jìn)行循環(huán)等待solr的collection的結(jié)果創(chuàng)建成功卡啰。
但是一般情況是比我這種復(fù)雜,比如電子商務(wù)中扣庫存的例子警没,多個(gè)客戶端同時(shí)發(fā)起扣庫存請(qǐng)求匈辱。不光需要控制只能有一個(gè)進(jìn)行資源操作,還需要控制訪問的順序杀迹,這時(shí)候就不能用這種簡(jiǎn)單的臨時(shí)節(jié)點(diǎn)亡脸,可以用臨時(shí)順序節(jié)點(diǎn)。
步驟如下:
- 每個(gè)應(yīng)用同時(shí)在一個(gè)特定的節(jié)點(diǎn)比如/app下去創(chuàng)建臨時(shí)順序節(jié)點(diǎn)佛南。
- 每個(gè)應(yīng)用對(duì)自己的前一個(gè)節(jié)點(diǎn)建立監(jiān)聽(如果有前一個(gè)節(jié)點(diǎn)的話)梗掰。
- 如果沒有比自己小的,則自己獲取鎖嗅回,可以進(jìn)行資源操作及穗。
- 有比他小序號(hào)的應(yīng)用,則這個(gè)應(yīng)用進(jìn)入等待绵载。
- 一個(gè)應(yīng)用刪除臨時(shí)順序節(jié)點(diǎn)后埂陆,監(jiān)聽它的節(jié)點(diǎn),則自動(dòng)獲取鎖娃豹,操作資源焚虱。
- 特殊情況:假如A節(jié)點(diǎn)順序號(hào)最小,B其次懂版,C最大鹃栽,這時(shí)候B監(jiān)聽A節(jié)點(diǎn),如果B突然掛掉了躯畴,C發(fā)現(xiàn)B掛掉了就會(huì)對(duì)上監(jiān)聽A節(jié)點(diǎn)民鼓,如果AB都同時(shí)掛掉了,由于是臨時(shí)節(jié)點(diǎn)蓬抄,所以它們都會(huì)被ZK清理丰嘉,所以也沒關(guān)系。
用這種方式進(jìn)行集群的leader選舉也是極好的嚷缭,只是ZK在存在大量節(jié)點(diǎn)監(jiān)聽的時(shí)候饮亏,會(huì)存在不穩(wěn)定的問題耍贾。
zk方式實(shí)現(xiàn)的分布式鎖,具有很好的性能路幸,而且zk一般是集群部署荐开,可靠性還是不錯(cuò)。
3.3 Redis實(shí)現(xiàn)的分布式鎖
redis 也是實(shí)現(xiàn)分布式鎖的常用手段劝赔,redis通常利用setnx來實(shí)現(xiàn)分布式鎖誓焦,key設(shè)置為鎖的id,value設(shè)置為當(dāng)前時(shí)間+超時(shí)時(shí)間着帽,或者value為線程id,同時(shí)設(shè)置超時(shí)時(shí)間移层;進(jìn)程獲取鎖后仍翰,如果在超時(shí)時(shí)間內(nèi)沒有釋放鎖,鎖會(huì)自動(dòng)釋放观话,這樣可以避免鎖一直無法釋放的問題予借。
setnx 有0或1兩個(gè)返回值。
返回1: 說明該進(jìn)程獲取了鎖频蛔,該進(jìn)程的設(shè)置成功灵迫。
返回0: 說明其他進(jìn)程已經(jīng)獲取到了鎖,進(jìn)程不能訪問共享資源晦溪,只能等待瀑粥。
這里面有個(gè)問題,就是如果在超時(shí)的時(shí)間范圍內(nèi)三圆,線程還沒有使用完共享資源狞换,比如線程A沒有完成業(yè)務(wù)操作,由于超時(shí)時(shí)間已到舟肉,鎖被釋放了修噪,這時(shí)候另外一個(gè)線程B意外的獲取到了鎖。然后A線程執(zhí)行完業(yè)務(wù)的時(shí)候路媚,就用del命令刪除了B線程創(chuàng)建的鎖黄琼,然后線程C再次獲取鎖成功,從而造成業(yè)務(wù)的錯(cuò)亂的問題整慎。
鎖的超時(shí)時(shí)間脏款,如果設(shè)置過長(zhǎng)了,如果一個(gè)擁有鎖的應(yīng)用掛了院领,則其他應(yīng)用很長(zhǎng)時(shí)間都獲取不了這個(gè)線程弛矛。對(duì)于這種情況,有個(gè)解決辦法是在加鎖的應(yīng)用中比然,增加個(gè)守護(hù)線程丈氓,守護(hù)線程定時(shí)判斷主業(yè)務(wù)操作資源是否結(jié)束,如果沒有結(jié)束,而超時(shí)時(shí)間就要結(jié)束了万俗,則增加這個(gè)key的超時(shí)時(shí)間湾笛,從而達(dá)到對(duì)鎖的續(xù)命的目的。
redis實(shí)現(xiàn)分布式鎖闰歪,因?yàn)閿?shù)據(jù)是放在內(nèi)存中的嚎研,性能也不錯(cuò);但是如果是單機(jī)的redis則存在著不可靠的問題库倘。
redis鎖我只是有所了解临扮,自己沒有親自嘗試過,需要使用這種分布式鎖的朋友教翩,還要多加驗(yàn)證杆勇。