Python使用分布式鎖的代碼演示示例
在計(jì)算機(jī)并發(fā)領(lǐng)域編程中總是會(huì)與鎖打交道,鎖又有很多種,互斥鎖、自旋鎖等等。
鎖總是伴隨著線程、進(jìn)程這樣的詞匯出現(xiàn),阮一峰有 一篇文章 對(duì)這些名詞進(jìn)行了簡(jiǎn)單易懂的解釋。
我的理解是,使用線程、進(jìn)程是為了實(shí)現(xiàn)并發(fā)從而獲得性能的提升(利用多核CPU,多臺(tái)服務(wù)器),但這種并發(fā)由于調(diào)度的不確定性,很容易出亂子,為了(在一些共享資源、關(guān)鍵節(jié)點(diǎn)上)不出亂子,又需要對(duì)資源加鎖,在操作這個(gè)資源時(shí)控制這種并發(fā),將亂子消滅。
很多語(yǔ)言都提供了一些線程級(jí)別的鎖實(shí)現(xiàn)以及一些相應(yīng)的工具,但在進(jìn)程方面就無(wú)能為力了。而一個(gè)服務(wù)部署到生產(chǎn)環(huán)境,往往會(huì)部署多個(gè)實(shí)例,這種情況下,就經(jīng)常會(huì)用到給不同進(jìn)程用的鎖,分布式鎖便是在分布式系統(tǒng)中對(duì)某共享資源進(jìn)行加鎖的構(gòu)件。
現(xiàn)在來(lái)試著展示一下在Python項(xiàng)目中如何使用簡(jiǎn)單的分布式互斥鎖。
不使用分布式鎖會(huì)怎樣
先用一個(gè)簡(jiǎn)單的實(shí)例來(lái)演示一下,不使用分布式鎖會(huì)出怎樣的亂子。
假設(shè)商城系統(tǒng)要做秒殺活動(dòng),在redis中記錄著 count:1 的信息,到秒殺時(shí)間點(diǎn)的時(shí)候,會(huì)收到許多的請(qǐng)求,這時(shí)各應(yīng)用程序去查redis中count的值,若count還大于0,則將count-1,這樣其他請(qǐng)求就不再能秒殺到了。
# -*- coding: utf-8 -*- import os import arrow import redis from multiprocessing import Pool HOT_KEY = 'count' r = redis.Redis(host='localhost', port=6379) def seckilling(): name = os.getpid() v = r.get(HOT_KEY) if int(v) > 0: print name, ' decr redis.' r.decr(HOT_KEY) else: print name, ' can not set redis.', v def run_without_lock(name): while True: if arrow.now().second % 5 == 0: seckilling() return if __name__ == '__main__': p = Pool(16) r.set(HOT_KEY, 1) for i in range(16): p.apply_async(run_without_lock, args=(i, )) print 'now 16 processes are going to get lock!' p.close() p.join() print('All subprocesses done.')
以上代碼使用多進(jìn)程來(lái)模仿這種并發(fā)請(qǐng)求場(chǎng)景,程序開(kāi)始的時(shí)候?qū)ount設(shè)為1,之后各進(jìn)程開(kāi)始進(jìn)入等待,當(dāng)秒數(shù)為5的時(shí)候,所有進(jìn)程同時(shí)去訪問(wèn)秒殺函數(shù),來(lái)看一下效果:
運(yùn)行結(jié)果
redis查詢展示
從程序打印與查redis的結(jié)果來(lái)說(shuō)并未如愿,本來(lái)秒殺商品只有一件,但卻被成功搶購(gòu)到了4次。這是由于各進(jìn)程在get count的值時(shí),對(duì)redis值更新的指令已經(jīng)發(fā)出而還未進(jìn)行完畢,會(huì)讓其他進(jìn)程認(rèn)為自己可以購(gòu)得。
這種問(wèn)題可歸為 不可重復(fù)讀 種類(lèi)的數(shù)據(jù)并發(fā)問(wèn)題。
在這種毫無(wú)保護(hù)的情況下,其他常見(jiàn)并發(fā)問(wèn)題幻讀、臟讀、第一第二類(lèi)丟失更新等都有可能發(fā)生,這里不再一一舉例。
使用ZooKeeper作分布式鎖
作為致力于解決分布式協(xié)同問(wèn)題的知名工具,利用zookeeper提供的API和它對(duì)于節(jié)點(diǎn)唯一性與順序一致性的保證可以實(shí)現(xiàn)分式式鎖。
實(shí)現(xiàn)思路為,各進(jìn)程去創(chuàng)建 /exclusive_lock/lock
的結(jié)點(diǎn),zookeeper保證只有一個(gè)client可以創(chuàng)建成功,那么便認(rèn)為創(chuàng)建成功的那個(gè)client獲得了鎖,當(dāng)它處理完業(yè)務(wù)后,將該node刪除,其他client會(huì)監(jiān)聽(tīng)到這個(gè)事件,并再次嘗試創(chuàng)建該節(jié)點(diǎn),如此進(jìn)行下去。
Kazoo 庫(kù)實(shí)現(xiàn)了這種Lock,使用起來(lái)非常簡(jiǎn)單,編程人員可以不用再去自己實(shí)現(xiàn)acquire,release等鎖的通用接口。
同時(shí)在Python中,對(duì)鎖的使用往往可以通過(guò)優(yōu)雅的上下文管理器with。
def run_with_zk_lock(name): zk = KazooClient() zk.start() lock = zk.Lock("/lockpath", "my-identifier") while True: if arrow.now().second % 5 == 0: with lock: seckilling() return
使用zk結(jié)果
redis查詢展示
當(dāng)秒殺發(fā)生時(shí),只有獲得鎖的進(jìn)程可以去進(jìn)行秒殺操作。
在鎖的幫助下,程序按照預(yù)想的方式運(yùn)行了。
使用redis作分布式鎖
在redis的網(wǎng)站有一篇文章 專(zhuān)門(mén)介紹如何使用redis作為分布式鎖,文尾還附帶了對(duì)此文章的反對(duì)文章以及再次回?fù)舻奈恼拢悬c(diǎn)精彩。
文章提到了一個(gè)redlock的分布式鎖設(shè)計(jì)。
設(shè)置鎖的redis命令為 SET resource_name my_random_value NX PX 30000
,當(dāng)加NX參數(shù)時(shí),若resouce_name不存在才會(huì)創(chuàng)建,若不存在則會(huì)向client返回不同的結(jié)果,利用這個(gè)機(jī)制,便只有一個(gè)client可以set成功,就像上面的zk一樣了。
但是,實(shí)現(xiàn)這樣一個(gè)分布式鎖遠(yuǎn)不止這么簡(jiǎn)單,redis并不像zk一樣是一個(gè)分布式協(xié)同工具,會(huì)向client做出分布式中各種一致性及容錯(cuò)、可用性的保證。
redis本身也是集群部署的,它們之間有著異步復(fù)制時(shí)間差、容錯(cuò)等問(wèn)題可能會(huì)出現(xiàn),要真正做到這個(gè)鎖的實(shí)現(xiàn)在線上大規(guī)模分布式系統(tǒng)中可用,真的是要考慮各種情況,很不容易。
關(guān)于如何在語(yǔ)言上實(shí)現(xiàn)一個(gè)鎖的接口,redlock的原理與代碼實(shí)現(xiàn),以及上述kazoo包里實(shí)現(xiàn)lock的源碼,我會(huì)在另一篇專(zhuān)門(mén)的文章中說(shuō)一下。
redlock-py 包是python語(yǔ)言中對(duì)上述文章的實(shí)現(xiàn),我們現(xiàn)在使用它來(lái)進(jìn)行嘗試。
rlock = RedLock([{"host": "localhost", "port": 6379, "db": 0}, ]) def run_with_redis_lock(name): while True: if arrow.now().second % 5 == 0: with rlock: seckilling() return
redis鎖運(yùn)行結(jié)果
運(yùn)行結(jié)果和上面使用zk一樣,符合程序設(shè)計(jì)預(yù)期。
以上只是基于python語(yǔ)言的一些代碼展示,通過(guò)使用兩個(gè)第三方包,來(lái)使用分布式鎖來(lái)避免并發(fā)程序中混亂的產(chǎn)生。
但其實(shí)這中間是有一個(gè)斷層的,即,這兩個(gè)工具都是提供了一個(gè)機(jī)制,而并不是直接對(duì)外提供了操作鎖的API,那么如何利用這個(gè)機(jī)制來(lái)實(shí)現(xiàn)這樣的鎖正是這兩個(gè)第三方做的事情。
簡(jiǎn)單看過(guò)它們實(shí)現(xiàn)的源碼,以及threading中一些lock的代碼,發(fā)現(xiàn)在鎖的實(shí)現(xiàn)上是有著共通之處的,都有通用的acquire與release方法,然后將 enter 與 exit 指向前面兩個(gè)方法來(lái)實(shí)現(xiàn)上下文管理器with的用法。
此外,還可以利用關(guān)系型數(shù)據(jù)庫(kù)如MySQL固有的鎖機(jī)制來(lái)作為分布式鎖,但由于數(shù)據(jù)庫(kù)往往是系統(tǒng)的瓶頸所在,沒(méi)有必要為它引入不必要的壓力。同時(shí),MySQL中的鎖、隔離級(jí)別也有一大堆可說(shuō)的,在github上找了一下也并未找到一個(gè)成熟的像上面的基于MySQL實(shí)現(xiàn)的對(duì)外暴露鎖通用API的第三方包,故未能在上面加以展示。
想要說(shuō)清楚這個(gè)事情并沒(méi)有那么容易,之后我會(huì)嘗試搞清楚如何寫(xiě)一個(gè)比較地道的鎖,并對(duì)上面兩個(gè)第三方包的具體實(shí)現(xiàn)加以研究,爭(zhēng)取把這個(gè)斷層補(bǔ)上。之后,或許可以嘗試實(shí)現(xiàn)一下基于MySQL的類(lèi)似第三方包,這需要對(duì)MySQL的一些機(jī)制搞得更加清楚才行。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Python3爬蟲(chóng)中Selenium的用法詳解
在本篇內(nèi)容里小編給大家分享了關(guān)于Python3爬蟲(chóng)中Selenium的用法詳解內(nèi)容,需要的朋友們可以參考下。2020-07-07python實(shí)現(xiàn)小世界網(wǎng)絡(luò)生成
今天小編就為大家分享一篇python實(shí)現(xiàn)小世界網(wǎng)絡(luò)生成,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11Python網(wǎng)絡(luò)爬蟲(chóng)實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Python網(wǎng)絡(luò)爬蟲(chóng)實(shí)例,爬蟲(chóng)的定義、主要框架等基礎(chǔ)概念,感興趣的小伙伴們可以參考一下2016-04-04python入門(mén)for循環(huán)嵌套理解學(xué)習(xí)
這篇文章主要介紹了python入門(mén)關(guān)于for循環(huán)嵌套的理解學(xué)習(xí),希望大家可以學(xué)會(huì)并運(yùn)用到日常工作中,有需要的朋友可以借鑒參考下,希望能夠有幫助2021-09-09python項(xiàng)目對(duì)接釘釘SDK的實(shí)現(xiàn)
這篇文章主要介紹了python項(xiàng)目對(duì)接釘釘SDK的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Python常見(jiàn)庫(kù)matplotlib學(xué)習(xí)筆記之多個(gè)子圖繪圖
Matplotlib是Python提供的一個(gè)繪圖庫(kù),通過(guò)該庫(kù)我們可以很容易的繪制出折線圖、直方圖、散點(diǎn)圖、餅圖等豐富的統(tǒng)計(jì)圖,下面這篇文章主要給大家介紹了關(guān)于Python常見(jiàn)庫(kù)matplotlib學(xué)習(xí)筆記之多個(gè)子圖繪圖的相關(guān)資料,需要的朋友可以參考下2023-05-05Python中如何處理常見(jiàn)報(bào)錯(cuò)
大家好,本篇文章主要講的是Python中如何處理常見(jiàn)報(bào)錯(cuò),感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01PyCharm 2020.2.2 x64 下載并安裝的詳細(xì)教程
這篇文章主要介紹了PyCharm 2020.2.2 x64 下載并安裝的詳細(xì)教程,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10PyQt5+QtChart實(shí)現(xiàn)繪制極坐標(biāo)圖
QChart是一個(gè)QGraphicScene中可以顯示的QGraphicsWidget。本文將利用QtChart實(shí)現(xiàn)極坐標(biāo)圖的繪制,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2022-12-12