基于数据库的分布式锁实现
一、基于數(shù)據(jù)庫(kù)表
要實(shí)現(xiàn)分布式鎖,最簡(jiǎn)單的方式可能就是直接創(chuàng)建一張鎖表,然后通過(guò)操作該表中的數(shù)據(jù)來(lái)實(shí)現(xiàn)了。當(dāng)我們要鎖住某個(gè)方法或資源的時(shí)候,我們就在該表中增加一條記錄,想要釋放鎖的時(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 '鎖定的方法名',`desc` varchar(1024) NOT NULL DEFAULT '備注信息',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存數(shù)據(jù)時(shí)間,自動(dòng)生成',PRIMARY KEY (`id`),UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='鎖定中的方法';當(dāng)我們要鎖住某個(gè)方法時(shí),執(zhí)行以下SQL:
insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)因?yàn)槲覀儗?duì)method_name做了唯一性約束,這里如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫(kù)的話(huà),數(shù)據(jù)庫(kù)會(huì)保證只有一個(gè)操作可以成功,那么我們可以認(rèn)為操作成功的那個(gè)線(xiàn)程獲得了該方法的鎖,可以執(zhí)行具體內(nèi)容。
當(dāng)方法執(zhí)行完畢之后,想要釋放鎖的話(huà),需要執(zhí)行以下sql:
delete from methodLock where method_name ='method_name'上面這種簡(jiǎn)單的實(shí)現(xiàn)有以下幾個(gè)問(wèn)題:
1、這把鎖依賴(lài)數(shù)據(jù)庫(kù)的可用性,數(shù)據(jù)庫(kù)是一個(gè)單點(diǎn),一旦數(shù)據(jù)庫(kù)掛掉,會(huì)導(dǎo)致業(yè)務(wù)系統(tǒng)不可用。
2、這把鎖沒(méi)有失效時(shí)間,一旦解決操作失敗,就會(huì)導(dǎo)致記錄一直在數(shù)據(jù)庫(kù)中,其他線(xiàn)程無(wú)法在獲得鎖。
3、這把鎖只能是非阻塞的,因?yàn)閿?shù)據(jù)的insert操作,一旦插入失敗就會(huì)直接報(bào)錯(cuò)。沒(méi)有獲得鎖的線(xiàn)程并不會(huì)進(jìn)入排隊(duì)隊(duì)列,要想再次獲得鎖就要再次觸發(fā)獲得鎖的操作。
4、這把鎖是非重入的,同一個(gè)線(xiàn)程在沒(méi)有釋放鎖之前無(wú)法再次獲得該鎖。因?yàn)閿?shù)據(jù)庫(kù)表中數(shù)據(jù)已經(jīng)存在了。
當(dāng)然,我們也可以有其它方式解決上面的問(wèn)題:
1、數(shù)據(jù)庫(kù)是單點(diǎn)?那就搞兩個(gè)數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)之前雙向同步,一旦掛掉快速切換到備庫(kù)上。
2、沒(méi)有失效時(shí)間?可以做一個(gè)定時(shí)任務(wù),每隔一定時(shí)間把數(shù)據(jù)庫(kù)中的超時(shí)數(shù)據(jù)清理一遍。
3、非阻塞?可以寫(xiě)一個(gè)while循環(huán),直到insert成功再返回成功。
4、非重入?可以在數(shù)據(jù)庫(kù)表中加一個(gè)字段,記錄當(dāng)前獲得鎖的機(jī)器的主機(jī)信息和線(xiàn)程信息,那么下次再獲取鎖的時(shí)候先查詢(xún)數(shù)據(jù)庫(kù),如果當(dāng)前機(jī)器的主機(jī)信息和線(xiàn)程信息在數(shù)據(jù)庫(kù)中可以查到的話(huà),就直接把鎖分配給它即可。
二、基于數(shù)據(jù)庫(kù)表做樂(lè)觀鎖
常用的方法是為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),具體實(shí)現(xiàn)請(qǐng)參考http://blog.csdn.net/lmb55/article/details/78266667
三、基于數(shù)據(jù)庫(kù)表做悲觀鎖(排它鎖)
除了可以通過(guò)增刪操作數(shù)據(jù)庫(kù)表中的記錄以外,還可以借助數(shù)據(jù)庫(kù)中自帶的鎖來(lái)實(shí)現(xiàn)分布式鎖。
我們還用上面創(chuàng)建的數(shù)據(jù)庫(kù)表,可以通過(guò)數(shù)據(jù)庫(kù)的排它鎖來(lái)實(shí)現(xiàn)分布式鎖?;贛ySQL的InnoDB引擎,可以使用以下方法來(lái)實(shí)現(xiàn)加鎖操作:
public boolean lock(){Connection.setAutoCommit(false);while (true) {try {result = select * from MethodLock where methodName = 'xxxx' for update;if (result == null) {return false;}} catch (Exception e) {}sleep(1000);}returnType false;}在查詢(xún)語(yǔ)句后面增加for update,數(shù)據(jù)庫(kù)會(huì)在查詢(xún)過(guò)程中給數(shù)據(jù)庫(kù)表增加排他鎖(這里再多提一句,InnoDB引擎在加鎖的時(shí)候,只有通過(guò)索引進(jìn)行檢索的時(shí)候才會(huì)使用行級(jí)鎖,否則會(huì)使用表級(jí)鎖。這里我們希望使用行級(jí)鎖,就要給method_name添加索引,值得注意的是,這個(gè)索引一定要?jiǎng)?chuàng)建成唯一索引,否則會(huì)出現(xiàn)多個(gè)重載方法之間無(wú)法同時(shí)被訪(fǎng)問(wèn)的問(wèn)題。重載方法的話(huà)建議把參數(shù)類(lèi)型也加上)。當(dāng)某條記錄被加上排他鎖之后,其他線(xiàn)程無(wú)法再在該行記錄上增加排他鎖。
我們可以認(rèn)為獲得排它鎖的線(xiàn)程即可獲得分布式鎖,當(dāng)獲取到鎖之后,可以執(zhí)行方法的業(yè)務(wù)邏輯,執(zhí)行完方法之后,再通過(guò)以下方法解鎖:
public void unlock(){connection.commit(); }通過(guò)connection.commit()操作來(lái)釋放鎖。
這里還可能存在另外一個(gè)問(wèn)題,雖然我們對(duì)method_name 使用了唯一索引,并且顯示使用for update來(lái)使用行級(jí)鎖。但是,MySql會(huì)對(duì)查詢(xún)進(jìn)行優(yōu)化,即便在條件中使用了索引字段,但是否使用索引來(lái)檢索數(shù)據(jù)是由 MySQL 通過(guò)判斷不同執(zhí)行計(jì)劃的代價(jià)來(lái)決定的,如果 MySQL 認(rèn)為全表掃效率更高,比如對(duì)一些很小的表,它就不會(huì)使用索引,這種情況下 InnoDB 將使用表鎖,而不是行鎖。如果發(fā)生這種情況就悲劇了。
還有一個(gè)問(wèn)題,就是我們要使用排他鎖來(lái)進(jìn)行分布式鎖的lock,那么一個(gè)排他鎖長(zhǎng)時(shí)間不提交,就會(huì)占用數(shù)據(jù)庫(kù)連接。一旦類(lèi)似的連接變得多了,就可能把數(shù)據(jù)庫(kù)連接池?fù)伪?/p>
這種方法可以有效的解決上面提到的無(wú)法釋放鎖和阻塞鎖的問(wèn)題:
1、阻塞鎖?for update語(yǔ)句會(huì)在執(zhí)行成功后立即返回,在執(zhí)行失敗時(shí)一直處于阻塞狀態(tài),直到成功。
2、鎖定之后服務(wù)宕機(jī),無(wú)法釋放?使用這種方式,服務(wù)宕機(jī)之后數(shù)據(jù)庫(kù)會(huì)自己把鎖釋放掉。
但是還是無(wú)法解決數(shù)據(jù)庫(kù)單點(diǎn)和可重入的問(wèn)題。
優(yōu)點(diǎn):直接借助數(shù)據(jù)庫(kù)容易理解;
缺點(diǎn):
1、會(huì)有各種各樣的問(wèn)題,在解決問(wèn)題的過(guò)程中會(huì)使整個(gè)方案變的越來(lái)越復(fù)雜。
2、操作數(shù)據(jù)庫(kù)需要一定的開(kāi)銷(xiāo),性能問(wèn)題需要考慮。
總結(jié):
使用數(shù)據(jù)庫(kù)來(lái)實(shí)現(xiàn)分布式鎖,這三種方式都是依賴(lài)數(shù)據(jù)庫(kù)中的一張表,一種是通過(guò)表中的記錄存在情況確定當(dāng)前是否有鎖存在,另外兩種是通過(guò)數(shù)據(jù)庫(kù)的樂(lè)觀鎖和排它鎖/悲觀鎖來(lái)實(shí)現(xiàn)分布式鎖。
總結(jié)
以上是生活随笔為你收集整理的基于数据库的分布式锁实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【Java并发编程】java高并发的解决
- 下一篇: 【webservice】Java JAX