介紹
許多場(chǎng)景中,數(shù)據(jù)一致性是一個(gè)比較重要的話題,在單機(jī)環(huán)境中,我們可以通過(guò)Java提供的并發(fā)API來(lái)解決;而在分布式環(huán)境(會(huì)遇到網(wǎng)絡(luò)故障、消息重復(fù)、消息丟失等各種問(wèn)題)下要復(fù)雜得多,比如電商的庫(kù)存扣減,秒殺活動(dòng),集群定時(shí)任務(wù)執(zhí)行等需要進(jìn)程互斥的場(chǎng)景。本文主要探討如何利用Zookeeper來(lái)實(shí)現(xiàn)分布式鎖,對(duì)比了一些其他方案分布式鎖的優(yōu)缺點(diǎn)。至于使用何種,要因自己的業(yè)務(wù)場(chǎng)景去決定,沒(méi)有絕對(duì)的方案。
分布式鎖是什么?
分布式鎖是控制分布式系統(tǒng)之間同步訪問(wèn)共享資源的一種方式。
實(shí)現(xiàn)分布式鎖注意點(diǎn):
鎖的可重入性(遞歸調(diào)用不應(yīng)該被阻塞、避免死鎖)
鎖的超時(shí)(避免死鎖、死循環(huán)等意外情況)
鎖的阻塞(保證原子性等)
鎖的特性支持(阻塞鎖、可重入鎖、公平鎖、聯(lián)鎖、信號(hào)量、讀寫鎖)
使用分布式鎖注意點(diǎn):
分布式鎖的開銷(分布式鎖一般能不用就不用,有些場(chǎng)景可以用樂(lè)觀鎖代替)
加鎖的粒度(控制加鎖的粒度,可以優(yōu)化系統(tǒng)的性能)
加鎖的方式
常見的實(shí)現(xiàn)分布式鎖方案
數(shù)據(jù)庫(kù)
基于數(shù)據(jù)庫(kù)表唯一索引
最簡(jiǎn)單的方式就是直接創(chuàng)建一張鎖表,當(dāng)我們要鎖住某個(gè)方法或資源時(shí),我們就在該表中增加一條記錄,想要釋放鎖的時(shí)候就刪除這條記錄。給某字段添加唯一性約束,如果有多個(gè)請(qǐng)求同時(shí)提交到數(shù)據(jù)庫(kù)的話,數(shù)據(jù)庫(kù)會(huì)保證只有一個(gè)操作可以成功,那么我們就可以認(rèn)為操作成功的那個(gè)線程獲得了該方法的鎖,可以執(zhí)行方法體內(nèi)容。
但會(huì)引入數(shù)據(jù)庫(kù)單點(diǎn)、無(wú)失效時(shí)間、不阻塞、不可重入等問(wèn)題。
基于數(shù)據(jù)庫(kù)排他鎖
如果使用的是MySql的InnoDB引擎,在查詢語(yǔ)句后面增加for update,數(shù)據(jù)庫(kù)會(huì)在查詢過(guò)程中(須通過(guò)唯一索引查詢)給數(shù)據(jù)庫(kù)表增加排他鎖,我們可以認(rèn)為獲得排它鎖的線程即可獲得分布式鎖,通過(guò) connection.commit() 操作來(lái)釋放鎖。
會(huì)引入數(shù)據(jù)庫(kù)單點(diǎn)、不可重入、無(wú)法保證一定使用行鎖、排他鎖,所以有可能長(zhǎng)時(shí)間不提交導(dǎo)致占用數(shù)據(jù)庫(kù)連接等問(wèn)題。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
直接借助數(shù)據(jù)庫(kù),容易理解。
缺點(diǎn)
會(huì)引入更多的問(wèn)題,使整個(gè)方案變得越來(lái)越復(fù)雜
操作數(shù)據(jù)庫(kù)需要一定的開銷,有一定的性能問(wèn)題
使用數(shù)據(jù)庫(kù)的行級(jí)鎖并不一定靠譜,尤其是當(dāng)我們的鎖表并不大的時(shí)候
緩存
相比較于基于數(shù)據(jù)庫(kù)實(shí)現(xiàn)分布式鎖的方案來(lái)說(shuō),基于緩存來(lái)實(shí)現(xiàn)在性能方面會(huì)表現(xiàn)的更好一點(diǎn),目前有很多成熟的緩存產(chǎn)品,包括Redis、memcached、tair等。
基于 redis 的 setnx()、expire() 方法做分布式鎖
setnx 的含義就是 SET if Not Exists,其主要有兩個(gè)參數(shù) setnx(key, value)。該方法是原子的,如果 key 不存在,則設(shè)置當(dāng)前 key 成功,返回 1;如果當(dāng)前 key 已經(jīng)存在,則設(shè)置當(dāng)前 key 失敗,返回 0。
expire 設(shè)置過(guò)期時(shí)間,要注意的是 setnx 命令不能設(shè)置 key 的超時(shí)時(shí)間,只能通過(guò) expire() 來(lái)對(duì) key 設(shè)置。