使用redis分布式鎖解決并發(fā)線(xiàn)程資源共享問(wèn)題
前言
眾所周知, 在多線(xiàn)程中,因?yàn)楣蚕砣肿兞?會(huì)導(dǎo)致資源修改結(jié)果不一致,所以需要加鎖來(lái)解決這個(gè)問(wèn)題,保證同一時(shí)間只有一個(gè)線(xiàn)程對(duì)資源進(jìn)行操作
但是在分布式架構(gòu)中,我們的服務(wù)可能會(huì)有n個(gè)實(shí)例,但線(xiàn)程鎖只對(duì)同一個(gè)實(shí)例有效,就需要用到分布式鎖----redis setnx
原理
修改某個(gè)資源時(shí), 在redis中設(shè)置一個(gè)key,value根據(jù)實(shí)際情況自行決定如何表示
我們既然要通過(guò)檢查key是否存在(存在表示有線(xiàn)程在修改資源,資源上鎖,其他線(xiàn)程不可同時(shí)操作,若key不存在,表示資源未被線(xiàn)程占用,允許線(xiàn)程搶占,然后將通過(guò)setnx設(shè)置vlaue,表示資源上鎖,其他線(xiàn)程不可同時(shí)操作)
圖示:
分析
我們的服務(wù)處于一個(gè)集群中,如果只是簡(jiǎn)單的的使用線(xiàn)程鎖來(lái)解決以上問(wèn)題,是存在問(wèn)題的:因?yàn)榫€(xiàn)程是基于進(jìn)程的,兩個(gè)web server處于不同的進(jìn)程空間
也就是說(shuō),user1的請(qǐng)求發(fā)往web server1,那只能與web server1的其他請(qǐng)求進(jìn)行鎖的操作,而不能對(duì)web server2的請(qǐng)求產(chǎn)生影響
上面的圖中,user1發(fā)往web server1的請(qǐng)求負(fù)責(zé)處理的線(xiàn)程為T(mén)hread1,同理負(fù)責(zé)處理user2發(fā)往web server2的請(qǐng)求的線(xiàn)程thread2
在同一時(shí)刻1,兩個(gè)線(xiàn)程都讀取了mysql中residue_ticket的值為100,對(duì)應(yīng)上圖 (1)(2), 各自對(duì)100進(jìn)行-1操作,更新到數(shù)據(jù)庫(kù),對(duì)應(yīng)(3)(4)
我們預(yù)期的情況是residue_ticket值被減少了兩次,應(yīng)該為98,但是實(shí)際情況下,兩個(gè)線(xiàn)程都做了100-1=99的操作,并都將mysql中的值改為了99, 的這就會(huì)導(dǎo)致最終數(shù)據(jù)不一致,所以就要用到分布式鎖。
為什么用redis?
因?yàn)閞edis是單線(xiàn)程的,不存在多線(xiàn)程資源競(jìng)爭(zhēng),并且它真的很快
為什么用setnx 而不是set?
setnx表示只有在key不存在時(shí)才能設(shè)置成功,但是set會(huì)在key存在的情況下修改value
利用setnx的特性,我們可以這樣這樣設(shè)計(jì):
偽代碼:
# 設(shè)置redis鎖的 redis key = 'residue_ticket_lock' # get_ticket是處理購(gòu)票的邏輯 def get_ticket(): time_out = 5 # 為了防止線(xiàn)程過(guò)多,當(dāng)前線(xiàn)程獲取不到鎖,長(zhǎng)時(shí)間處于循環(huán)中而導(dǎo)致的性能影響,我們?cè)O(shè)置一個(gè)超時(shí)時(shí)間,如果當(dāng)前線(xiàn)程在超時(shí)時(shí)間內(nèi)還沒(méi)有搶占到分布式鎖,就返回失敗的結(jié)果 while True: if redis.setnx('residue_ticket_lock','lock',5): # 如果setnx返回True, 表示此刻沒(méi)有其他線(xiàn)程在操作數(shù)據(jù)庫(kù),當(dāng)前線(xiàn)程可以上鎖成功,注意不僅設(shè)置了value=lock,還設(shè)置了過(guò)期時(shí)間,這是必要的,為了防止上鎖的線(xiàn)程異常崩掉導(dǎo)致不能釋放(刪除key)而導(dǎo)致其他所有線(xiàn)程永遠(yuǎn)拿不到操作權(quán) residue_ticket = mysql.get('residue_ticket') # 從mysql中獲取當(dāng)前剩余票數(shù) mysql.update('residue_ticket',residue_ticket-1) # 訂購(gòu)成功,將票數(shù)-1,更新數(shù)據(jù)到mysql # 刪除key,釋放鎖 redis.del('residue_ticket') return True else: # 如果setnx返回False,表示有其他線(xiàn)程對(duì)在操作,當(dāng)前線(xiàn)程等待0.01s,并繼續(xù)循環(huán) time.sleep(0.01) time_out -= 0.01 continue return False
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
關(guān)于Redis數(shù)據(jù)庫(kù)三種持久化方案介紹
大家好,本篇文章主要講的是關(guān)于Redis數(shù)據(jù)庫(kù)三種持久化方案介紹,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話(huà)記得收藏一下2022-01-01Redis 數(shù)據(jù)遷移的項(xiàng)目實(shí)踐
本文主要介紹了Redis 數(shù)據(jù)遷移的項(xiàng)目實(shí)踐,通過(guò)Redis-shake的sync(同步)模式,可以將Redis的數(shù)據(jù)實(shí)時(shí)遷移至另一套R(shí)edis環(huán)境,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法
這篇文章主要介紹了kubernetes環(huán)境部署單節(jié)點(diǎn)redis數(shù)據(jù)庫(kù)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-01-01redis中Could not get a resource from
這篇文章主要介紹了redis中Could not get a resource from the pool異常及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12Redis?延時(shí)任務(wù)實(shí)現(xiàn)及與定時(shí)任務(wù)區(qū)別詳解
這篇文章主要為大家介紹了Redis?延時(shí)任務(wù)實(shí)現(xiàn)及與定時(shí)任務(wù)區(qū)別詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Redis實(shí)現(xiàn)唯一計(jì)數(shù)的3種方法分享
這篇文章主要介紹了Redis實(shí)現(xiàn)唯一計(jì)數(shù)的3種方法分享,本文講解了基于SET、基于 bit、基于 HyperLogLog三種方法,需要的朋友可以參考下2015-03-03redis慢查詢(xún)?nèi)罩镜脑L(fǎng)問(wèn)和管理方式
這篇文章主要介紹了redis慢查詢(xún)?nèi)罩镜脑L(fǎng)問(wèn)和管理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12