python-redis-lock實(shí)現(xiàn)鎖自動(dòng)續(xù)期的源碼邏輯
python-redis-lock簡(jiǎn)介
python-redis-lock是一個(gè)python的第三方庫(kù),基于Redis,封裝了分布式鎖的邏輯,提供了更高級(jí)的API來(lái)簡(jiǎn)化鎖的獲取、保持和釋放過(guò)程。包括自動(dòng)續(xù)期、鎖超時(shí)、重入鎖等功能。
相比于直接使用redis的setnx,避免了寫(xiě)額外代碼來(lái)實(shí)現(xiàn)鎖的復(fù)雜邏輯。
鎖的續(xù)期
在使用分布式鎖的情況下,如果某個(gè)服務(wù)器A的業(yè)務(wù)還未執(zhí)行完(可能因?yàn)榫W(wǎng)絡(luò)擁塞、數(shù)據(jù)量突然變大等各種原因),但是鎖過(guò)期了,可能引發(fā)額外的問(wèn)題。例如另一臺(tái)服務(wù)器B發(fā)現(xiàn)鎖釋放了,于是加鎖并開(kāi)始執(zhí)行業(yè)務(wù),從而導(dǎo)致出現(xiàn)問(wèn)題。
更有可能導(dǎo)致服務(wù)器A業(yè)務(wù)在執(zhí)行完后去釋放鎖時(shí),意外地釋放了服務(wù)器B加的鎖,導(dǎo)致服務(wù)器C進(jìn)入,從而引發(fā)問(wèn)題(在未使用唯一性ID表示加鎖者的情況下)。
所以對(duì)鎖的過(guò)期時(shí)間來(lái)續(xù)期是很有必要的,它確保了鎖在業(yè)務(wù)執(zhí)行完后才釋放。
python-redis-lock實(shí)現(xiàn)自動(dòng)續(xù)期的源碼分析
首先在配置python-redis-lock實(shí)例時(shí),有兩個(gè)參數(shù):expire int 和 auto_renewal boolean。
expire:鎖過(guò)期時(shí)間,單位為秒。
auto_renewal:是否開(kāi)啟自動(dòng)續(xù)期鎖。
python-redis-lock實(shí)例在初始化時(shí),針對(duì)這兩個(gè)參數(shù)的源碼:
class Lock(object): def __init__(self, redis_client, name, expire=None, auto_renewal=False, ...): ... if expire: expire = int(expire) ... self._expire = expire self._lock_renewal_interval = float(expire) * 2/3 if auto_renewal else None ...
假設(shè)我們?cè)O(shè)置了一個(gè)過(guò)期時(shí)間30秒,開(kāi)啟自動(dòng)續(xù)期的Lock實(shí)例,則self._expire=30秒,self._lock_renewal_interval=20秒。
當(dāng)在代碼層調(diào)用Lock.acquire方法時(shí),判斷self._lock_renewal_interval是否為空,如果不為空,則開(kāi)啟鎖的自動(dòng)續(xù)期(這里省略了一些內(nèi)容,因?yàn)橹魂P(guān)注續(xù)期的代碼實(shí)現(xiàn)邏輯):
class Lock(object): def acquire(self, blocking=True, timeout=None): ... if self._lock_renewal_interval is not None: self._start_lock_renewer()
在self._start_lock_renewer方法中,基于python的threading模塊,開(kāi)啟了一個(gè)鎖自動(dòng)更新線程self._lock_renewal_thread。這個(gè)線程執(zhí)行self._lock_renewer方法
def _start_lock_renewer(self): ... self._lock_renewal_stop = threading.Event() # threading.Event()用于線程間的協(xié)調(diào) self._lock_renewal_thread = threading.Thread( group=None, target=self._lock_renewer, kwargs={ 'name': self._name, 'lockref': weakref.ref(self), # 對(duì)Lock實(shí)例的弱引用 'interval': self._lock_renewal_interval, 'stop': self._lock_renewal_stop, }, ) self._lock_renewal_thread.demon = True self._lock_renewal_thread.start()
weakref.ref(self)創(chuàng)建當(dāng)前類實(shí)例(self)的弱引用,并將其存儲(chǔ)在變量 lockref 中。使用 weakref.ref 創(chuàng)建的弱引用不會(huì)阻止對(duì)象被垃圾回收。
在self._lock_renewer方法中,首先用了while循環(huán)來(lái)每次等待20秒(interval=20)再執(zhí)行循環(huán)體:如果弱引用對(duì)象(即Lock實(shí)例本身)沒(méi)有被垃圾回來(lái),則執(zhí)行續(xù)期的方法(lock.extend):
def _lock_renewer(name, lockref, interval, stop): while not stop.wait(timeout=interval): ... lock: "Lock" = lockref() if lock is None: break lock.extend(expire=lock._expire)
在self.extend方法中,執(zhí)行了一個(gè)Lua腳本來(lái)更新鎖的過(guò)期時(shí)間。
在滿足self._name和self._signal的緩存鍵值存在且鎖未過(guò)期的情況下,將當(dāng)前鎖的過(guò)期時(shí)間重置為expire(30秒)。
def extend(self. expire=None): if expire: expire = int(expire) error = self.extend_script( client=self._client, # 對(duì)redis的連接 keys=(self._name, self._signal), args=(self._id, expire)) ... # self.extend_script的執(zhí)行邏輯: EXTEND_SCRIPT = b""" if redis.call("get", KEYS[1]) ~= ARGV[1] then return 1 elseif redis.call("ttl", KEYS[1]) < 0 then return 2 else redis.call("expire", KEYS[1], ARGV[2]) return 0 end """ cls.extend_script = redis_client.register_script(EXTEND_SCRIPT)
以上就是python-redis-lock實(shí)現(xiàn)鎖自動(dòng)續(xù)期的源碼邏輯。
其中用到了多線程threading、弱引用weakref和Lua腳本等相關(guān)知識(shí)。
到此這篇關(guān)于python-redis-lock是如何實(shí)現(xiàn)鎖自動(dòng)續(xù)期的的文章就介紹到這了,更多相關(guān)python-redis-lock鎖自動(dòng)續(xù)期內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用python 的matplotlib 畫(huà)軌道實(shí)例
今天小編就為大家分享一篇使用python 的matplotlib 畫(huà)軌道實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-01-01python 與GO中操作slice,list的方式實(shí)例代碼
這篇文章主要介紹了python 與GO中操作slice,list的方式實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-03-03關(guān)于Python數(shù)據(jù)結(jié)構(gòu)中字典的心得
給大家詳細(xì)簡(jiǎn)介了Python數(shù)據(jù)結(jié)構(gòu)中字典的方法和使用心得,學(xué)習(xí)一下吧 ,有助于你更好的理解Python數(shù)據(jù)結(jié)構(gòu)。2017-12-12Jupyter notebook運(yùn)行Spark+Scala教程
這篇文章主要介紹了Jupyter notebook運(yùn)行Spark+Scala教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04使用sklearn進(jìn)行對(duì)數(shù)據(jù)標(biāo)準(zhǔn)化、歸一化以及將數(shù)據(jù)還原的方法
今天小編就為大家分享一篇使用sklearn進(jìn)行對(duì)數(shù)據(jù)標(biāo)準(zhǔn)化、歸一化以及將數(shù)據(jù)還原的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-07-07Python3實(shí)現(xiàn)定時(shí)任務(wù)的四種方式
Python實(shí)現(xiàn)定點(diǎn)與定時(shí)任務(wù)方式比較多,找到下面四中實(shí)現(xiàn)方式,每個(gè)方式都有自己應(yīng)用場(chǎng)景;下面來(lái)快速介紹Python中常用的定時(shí)任務(wù)實(shí)現(xiàn)方式,一起看看吧2019-06-06Python+Kepler.gl實(shí)現(xiàn)時(shí)間輪播地圖過(guò)程解析
這篇文章主要介紹了Python+Kepler.gl實(shí)現(xiàn)時(shí)間輪播地圖過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07