閱讀目錄
一、什么是分布式鎖
我們在開發應用的時候,如果需要對某一個共享變量進行多線程同步訪問的時候,可以使用我們學到的鎖進行處理,並且可以完美的運行,毫無Bug!
注意這是單機應用,后來業務發展,需要做集群,一個應用需要部署到幾台機器上然后做負載均衡,大致如下圖:
上圖可以看到,變量A存在三個服務器內存中(這個變量A主要體現是在一個類中的一個成員變量,是一個有狀態的對象),如果不加任何控制的話,變量A同時都會在分配一塊內存,三個請求發過來同時對這個變量操作,顯然結果是不對的!即使不是同時發過來,三個請求分別操作三個不同內存區域的數據,變量A之間不存在共享,也不具有可見性,處理的結果也是不對的!
如果我們業務中確實存在這個場景的話,我們就需要一種方法解決這個問題!
為了保證一個方法或屬性在高並發情況下的同一時間只能被同一個線程執行,在傳統單體應用單機部署的情況下,可以使用並發處理相關的功能進行互斥控制。但是,隨着業務發展的需要,原單體單機部署的系統被演化成分布式集群系統后,由於分布式系統多線程、多進程並且分布在不同機器上,這將使原單機部署情況下的並發控制鎖策略失效,單純的應用並不能提供分布式鎖的能力。為了解決這個問題就需要一種跨機器的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題!
分布式鎖應該具備哪些條件:
1、在分布式系統環境下,一個方法在同一時間只能被一個機器的一個線程執行;
2、高可用的獲取鎖與釋放鎖;
3、高性能的獲取鎖與釋放鎖;
4、具備可重入特性;
5、具備鎖失效機制,防止死鎖;
6、具備非阻塞鎖特性,即沒有獲取到鎖將直接返回獲取鎖失敗
二、基於redis實現分布式鎖
1、選用Redis實現分布式鎖原因:
(1)Redis有很高的性能;
(2)Redis命令對此支持較好,實現起來比較方便
2、使用命令介紹:
(1)SETNX
SETNX key val:當且僅當key不存在時,set一個key為val的字符串,返回1;若key存在,則什么都不做,返回0。
(2)expire
expire key timeout:為key設置一個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。
(3)delete
delete key:刪除key
在使用Redis實現分布式鎖的時候,主要就會使用到這三個命令。
3、實現思想:
(1)獲取鎖的時候,使用setnx加鎖,並使用expire命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
(2)獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
(3)釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。
#連接redis import time import uuid from threading import Thread import redis redis_client = redis.Redis(host="localhost", port=6379, # password=123, db=10) #獲取一個鎖 # lock_name:鎖定名稱 # acquire_time: 客戶端等待獲取鎖的時間 # time_out: 鎖的超時時間 def acquire_lock(lock_name, acquire_time=10, time_out=10): """獲取一個分布式鎖""" identifier = str(uuid.uuid4()) end = time.time() + acquire_time lock = "string:lock:" + lock_name while time.time() < end: if redis_client.setnx(lock, identifier): # 給鎖設置超時時間, 防止進程崩潰導致其他進程無法獲取鎖 redis_client.expire(lock, time_out) return identifier elif not redis_client.ttl(lock): redis_client.expire(lock, time_out) time.sleep(0.001) return False #釋放一個鎖 def release_lock(lock_name, identifier): """通用的鎖釋放函數""" lock = "string:lock:" + lock_name pip = redis_client.pipeline(True) while True: try: pip.watch(lock) lock_value = redis_client.get(lock) if not lock_value: return True if lock_value.decode() == identifier: pip.multi() pip.delete(lock) pip.execute() return True pip.unwatch() break except redis.excetions.WacthcError: pass return False if __name__ == __main__: count=10 def seckill(i): identifier=acquire_lock('resource') print("線程:{}--獲得了鎖".format(i)) time.sleep(1) global count if count<1: print("線程:{}--沒搶到,票搶完了".format(i)) return count-=1 print("線程:{}--搶到一張票,還剩{}張票".format(i,count)) release_lock('resource',identifier) # 線程之間通用一個變量,
# 此處也可以考慮使用進程, 進程的結果和線程反差很大
for i in range(50): t = Thread(target=seckill,args=(i,)) t.start()