摘要:分布式鎖也有類似的首先獲取鎖,然后執(zhí)行操作,最后釋放鎖動作,但這種鎖既不是給同一個進(jìn)程中的多個線程使用,也不是給同一臺機器上的多個進(jìn)程使用,而是由不同機器上的不同客戶端進(jìn)行獲取和釋放的。
一般來說,在對數(shù)據(jù)進(jìn)行“加鎖”時,程序首先需要通過獲取(acquire)鎖來得到對數(shù)據(jù)進(jìn)行排他性訪問的能力,然后才能對數(shù)據(jù)執(zhí)行一系列操作,最后還要釋放(release)給其他程序。對于能夠被多個線程訪問的共享內(nèi)存數(shù)據(jù)結(jié)構(gòu)(shared-memory data structure)來說,這種“先獲取鎖,然后執(zhí)行操作,最后釋放鎖”的動作非常常見。Redis使用WATCH命令來代替對數(shù)據(jù)進(jìn)行加鎖,因為WATCH只會在數(shù)據(jù)被其他客戶端搶先修改了的情況下通知執(zhí)行了這個命令的客戶端,而不會阻止其他客戶端對數(shù)據(jù)的修改,所以這個命令被稱為樂觀鎖(optimistic locking)。
分布式鎖也有類似的“首先獲取鎖,然后執(zhí)行操作,最后釋放鎖”動作,但這種鎖既不是給同一個進(jìn)程中的多個線程使用,也不是給同一臺機器上的多個進(jìn)程使用,而是由不同機器上的不同Redis客戶端進(jìn)行獲取和釋放的。
為了防止客戶端在取得鎖之后崩潰,并導(dǎo)致鎖一直處于“已被獲取”的狀態(tài),最終版的鎖實現(xiàn)將帶有超時限制特性:如果獲得鎖的進(jìn)程未能在指定的時限內(nèi)完成操作,那么鎖將自動釋放。
導(dǎo)致鎖出現(xiàn)不正確行為的原因,以及鎖在不正確運行時的癥狀:
持有鎖的進(jìn)程因為操作時間過長而導(dǎo)致鎖被自動釋放,但進(jìn)程本身并不知曉這一點,甚至還可能會錯誤地釋放掉了其他進(jìn)程持有的鎖。
一個持有鎖并打算執(zhí)行長時間操作的進(jìn)程已經(jīng)崩潰,但其他想要獲取鎖的進(jìn)程不知道哪個進(jìn)程持有著鎖,也無法檢測出持有鎖的進(jìn)程已經(jīng)崩潰,只能白白地浪費時間等待鎖被釋放。
在一個進(jìn)程持有的鎖過期之后,其他多個進(jìn)程同時嘗試去獲取鎖,并且都獲得了鎖。
上面第一種情況和第三種情況同時出現(xiàn),導(dǎo)致有多個進(jìn)程獲得了鎖,而每個進(jìn)程都以為自己是唯一一個獲得鎖的進(jìn)程。
簡易鎖
為了對數(shù)據(jù)進(jìn)行排他性訪問,程序首先要做的就是獲取鎖。SETNX命令天生就適合用來實現(xiàn)鎖的獲取功能,這個命令只會在鍵不存在的情況下為鍵賦值,而鎖要做的就是將一個隨機生成的128位UUID設(shè)置為鍵的值,并使用這個值來防止鎖被其他進(jìn)程取得。
如果程序嘗試獲取鎖的時候失敗,那么它將不斷地進(jìn)行重試,直到成功地取得鎖或者超過給定的時限為止。
def acquire_lock(conn, lockname, acquire_timeout=10): identifier = str(uuid.uuid4()) //128位隨機標(biāo)識符 end = time.time() + acquire_timeout while time.time() < end: if conn.setnx("lock:" + lockname, identifier): //嘗試獲取鎖 return identifier time.sleep(.001) return False
下面代碼展示了使用鎖重新實現(xiàn)的商品購買操作:程序首先對市場進(jìn)行加鎖,接著檢查商品的價格,并在確保買家有足夠的錢來購買商品之后,對錢和商品進(jìn)行相應(yīng)的轉(zhuǎn)移。當(dāng)操作執(zhí)行完之后,程序就會釋放鎖。
def purchase_item_with_lock(conn, buyerid, itemid, sellerid): buyer = "users:%s"%buyerid sellerid = "users:%s"%sellerid item = "%s.%s"%(itemid, sellerid) inventory = "inventory:%s"%buyerid locked = acquire_lock(conn, market) if not locked: return False pipe = conn.pipeline(True) try://檢查指定的商品是否仍在出售,以及買家是否有足夠的錢來購買該商品 pipe.zscore("market:", item) pipe.hget(buyer, "funds") price, funds = pipe.execute() if price is None or price > funds: return None pipe.hincrby(seller, "funds", int(price)) pipe.hincrby(buyer, "funds", int(-price)) pipe.sadd(inventory, itemid) pipe.zrem("market:", item) pipe.execute() return True finally: release_lock(conn, market, locked) //釋放鎖
上面代碼的鎖似乎是用來加鎖整個購買操作的,但實際上這把鎖是用來鎖住市場數(shù)據(jù)的,它之所以會包圍著執(zhí)行購買操作的代碼,是因為程序在操作市場數(shù)據(jù)期間必須一直持有鎖。
接下面的代碼release_lock函數(shù)展示了鎖釋放操作的實現(xiàn)代碼:函數(shù)首先使用WATCH命令監(jiān)視代表鎖的鍵,接著檢查鍵目前的值是否和加鎖時設(shè)置的值相同,并且確認(rèn)值沒有變化之后刪除該鍵(這個檢查還可以防止程序錯誤地釋放同一個鎖多次)。
def release_lock(conn, lockname, identifier): pipe = conn.pipeline(True) lockname = "lock:" + lockname while True: try: pipe.watch(lockname) if pipe.get(lockname) == identifier: pipe.multi() pipe.delete(lockname) pipe.execute() return True pipe.unwatch() break except redis.exceptions.WatchError: pass return False
經(jīng)過測試,與之前WATCH實現(xiàn)相比,鎖實現(xiàn)的上架商品數(shù)量雖然有所減少,但是在買入商品時卻不需要進(jìn)行重試,并且上架商品數(shù)量和買入商品數(shù)量之間的比率,也跟賣家數(shù)量和買家數(shù)量之間的比率接近。
帶有超時限制的鎖
目前的鎖實現(xiàn)在持有者崩潰的時候不會自動釋放,這將導(dǎo)致鎖一直處于已被獲取的狀態(tài)。為了解決這個問題,我們將為鎖加上超時功能。
為了給鎖加上超時的限制特性,程序?qū)⒃谌〉面i之后,調(diào)用EXPIRE命令來為鎖設(shè)置過期時間,使得Redis可以自動刪除超時的鎖。為了確保鎖在客戶端已經(jīng)崩潰(客戶端在執(zhí)行介于SETNX和EXPIRE之間的時候崩潰是最糟糕的)的情況下仍然能夠自動被釋放,客戶端會嘗試獲取鎖失敗之后,檢查鎖的超時時間,并為未設(shè)置超時時間的鎖設(shè)置超時時間。因為鎖總會帶有超時時間,并最終因為超時而自動被釋放,使得其他客戶端可以繼續(xù)嘗試獲取已被釋放的鎖。
需要注意的一點是,因為多個客戶端在同一時間內(nèi)設(shè)置的超時時間基本上都是相同的,所以即使有多個客戶端同時為同一個鎖設(shè)置超時時間,鎖的超時時間也不會產(chǎn)生太大變化。
def acquire_lock_with_timeout(conn, lockname, acquire_timeout=10, lock_timeout=10): identifier = str(uuid.uuid4()) lockname = "lock:" + lockname lock_timeout = int(math.ceil(lock_timeout)) end = time.time() + acquire_timeout while time.time() < end: if conn.setnx(lockname, identifier): conn.expire(lockname, lock_timeout) return identifier elif not conn.ttl(lockname): conn.expire(lockname, lock_timeout) time.sleep(.001) return False
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/44609.html
摘要:集群實現(xiàn)分布式鎖上面的討論中我們有一個非常重要的假設(shè)是單點的。但是其實這已經(jīng)超出了實現(xiàn)分布式鎖的范圍,單純用沒有命令來實現(xiàn)生成。這個問題用實現(xiàn)分布式鎖暫時無解。結(jié)論并不能實現(xiàn)嚴(yán)格意義上的分布式鎖。 關(guān)于Redis實現(xiàn)分布式鎖的問題,網(wǎng)絡(luò)上很多,但是很多人的討論基本就是把原來博主的貼過來,甚至很多面試官也是一知半解經(jīng)不起推敲就來面候選人,最近結(jié)合我自己的學(xué)習(xí)和資料查閱,整理一下用Redi...
摘要:分布式鎖的作用在單機環(huán)境下,有個秒殺商品的活動,在短時間內(nèi),服務(wù)器壓力和流量會陡然上升。分布式集群業(yè)務(wù)業(yè)務(wù)場景下,每臺服務(wù)器是獨立存在的。這里就用到了分布式鎖這里簡單介紹一下,以的事務(wù)機制來延生。 Redis 分布式鎖的作用 在單機環(huán)境下,有個秒殺商品的活動,在短時間內(nèi),服務(wù)器壓力和流量會陡然上升。這個就會存在并發(fā)的問題。想要解決并發(fā)需要解決以下問題 1、提高系統(tǒng)吞吐率也就是qps 每...
閱讀 874·2021-10-25 09:45
閱讀 3298·2021-09-22 14:58
閱讀 3856·2021-08-31 09:43
閱讀 919·2019-08-30 15:55
閱讀 923·2019-08-29 13:51
閱讀 1235·2019-08-29 13:02
閱讀 3490·2019-08-29 12:52
閱讀 1965·2019-08-26 13:27