常用的鎖思想
1. 樂觀鎖與悲觀鎖
悲觀鎖:就是在并發(fā)環(huán)境下很悲觀,每次拿數(shù)據(jù)都會認為別人要修改數(shù)據(jù),所以每次拿數(shù)據(jù)的時候都會上鎖,這樣有人拿數(shù)據(jù)的時候,其他人就不能進行增刪改查的操作.很多關(guān)系型數(shù)據(jù)庫中用了這種鎖機制.比如行鎖,表鎖.
樂觀鎖:就是并發(fā)情況下很樂觀,每次拿數(shù)據(jù)的時候認為別人不會去修改,所以不會上鎖,而是采用一個version字段作為版本控制,如果別人修改時version與當前數(shù)據(jù)的version不一致的時候,就進行事物回滾.ElasticSearch就是采用的這種方式.但是這種方式會導(dǎo)致數(shù)據(jù)臟讀.
2. 死鎖與活鎖
死鎖:比如一個人進入一個廁所,并上死鎖,外面的人想要進去,但是門已經(jīng)上鎖進不去,也看不到這個廁所里面的內(nèi)容,這就是死鎖.表示一個人拿到了一個共享數(shù)據(jù)(例如一張表),其他人無法對這張表進行增刪改查的操作,除非拿到了這把鎖.
活鎖:比如一個人進入一個廁所,上了活鎖,這樣外面的人可以進來,看廁所里面的內(nèi)容,抽煙也好,但是坑位被占了,不能使用廁所.這就是活鎖,表示一個人拿到一個共享數(shù)據(jù)(例如一張數(shù)據(jù)表),其他人可以進行查詢的操作,但是不能進行增刪改的操作.
分布式環(huán)境下數(shù)據(jù)不一致的場景
現(xiàn)在有一個商城,在網(wǎng)上出售一個產(chǎn)品名為wazi的商品,采用分布式的方式,購買商品的主要業(yè)務(wù)流程如下:
現(xiàn)在模擬這個訂單服務(wù)部署到兩臺機器上,兩臺機器的進程同時訪問product表,庫存中有產(chǎn)品名為wazi的產(chǎn)品余量只有10雙,但是小A和小B同一時間各下單8雙,我們看看會有什么結(jié)果:
同時訪問結(jié)果如下:
由于兩個進程的線程同時進入購買wazi的訂單業(yè)務(wù),在獲取wazi的數(shù)量時都查出來是10雙,于是在后面一起完成了扣除庫存的操作,導(dǎo)致庫存數(shù)量為負數(shù).
那么對于這種會對共享數(shù)據(jù)進行增刪改的操作的業(yè)務(wù),我們需要使用分布式鎖的方式來保證高并發(fā)下的數(shù)據(jù)的一致性問題.
那么我們?nèi)绻褂梅植际芥i需要注意哪些問題?
分布式鎖需要注意的問題
- 完成訂單的業(yè)務(wù)后,該鎖是會釋放掉的.如果不能釋放掉,后面的用戶就無法下訂單了.(需要釋放鎖的操作)
- 如果用戶關(guān)閉瀏覽器,該鎖會自動釋放掉.(Zookeeper中的臨時節(jié)點,客戶端與服務(wù)端失去連接后,會自動刪除)
- 前面的鎖釋放后,后面的用戶要能夠知道鎖已經(jīng)被釋放,并獲得一把鎖(Zookeeper的監(jiān)聽機制)
分布式鎖實現(xiàn)的流程
本例使用的鎖是悲觀活鎖.當一個人拿到了一個共享數(shù)據(jù)的鎖后,其他人可以進行查詢操作,但是不能進行增刪改的操作.
分布式鎖的實現(xiàn)流程如下:
實現(xiàn)主要的過程是:
- 用戶進入訂單購買業(yè)務(wù),首先獲取一把鎖(在Zookeeper中創(chuàng)建一個臨時的znode,其他用戶獲取所也要創(chuàng)建,如果創(chuàng)建失敗,視為獲取鎖失敗,并阻塞當前線程)
- 用戶訂單購買的業(yè)務(wù)走完后,主動釋放鎖(在Zookeeper中刪除這個znode)
- ZK客戶端監(jiān)聽到對應(yīng)的znode被刪除后,主動喚醒后面的線程主動獲取鎖(CountDownLatch + Zookeeper的Watch機制)
使用Apache Curator實現(xiàn)分布式鎖的主要代碼:
創(chuàng)建一個類DistributedLock,這個類有釋放鎖,創(chuàng)建鎖和監(jiān)聽特定Znode節(jié)點的方法.
- 初始化Curator客戶端:
- 獲取分布式鎖
- 釋放分布式鎖
- 在訂單購買業(yè)務(wù)中添加分布式鎖和釋放分布式鎖
測試
當加上分布式鎖之后,我們在使用相同場景進行測試,
現(xiàn)在模擬這個訂單服務(wù)部署到兩臺機器上,兩臺機器的進程同時訪問product表,庫存中有產(chǎn)品名為wazi的產(chǎn)品余量只有10雙,但是小A和小B同一時間各下單8雙,我們看看會有什么結(jié)果:
購買前:
并發(fā)訪問后:
從日志中可以看出,當?shù)谝粋€人進入到訂單購買程序后,后面的用戶則進行了線程等待的狀態(tài),直到前面的用戶購買wazi成功了之后,后面的用戶才進入到訂單購買業(yè)務(wù)中來,最終一開始的人購買成功,后面的用戶購買失敗.
源碼位置
- github地址: https://github.com/lilike/chawuzhi.git