您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關怎么在PHP中使用redis實現一個悲觀鎖機制,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
鎖機制
通常使用的鎖分為樂觀鎖,悲觀鎖這兩種,簡單介紹下這兩種鎖,作為本文的背景知識,對這類知識已經有足夠了解的同學可以跳過這部分。
樂觀鎖
先來看下百度百科上的解釋:大多是基于數據版本( Version )記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
其實說白了,就是好比一個健身房里只有一臺跑步機,在健身房門口有個排號機,每個進健身房的人都得先領一個號碼才能進入,如果跑步機上有人,則在一邊做做熱身、喝喝水,如果跑步機上沒人,則確認跑步機上當前顯示的號碼(上一個用過跑步機的人的號碼)是否比自己手持的小,如果小,則可以使用;否則,就意味著過號,而過號在現實中我們的都知道要么走,要么重排,就是不能插隊,在系統中也是一樣的,通常是返回錯誤。
悲觀鎖
同樣,來看下百度百科的解釋:具有強烈的獨占和排他特性。它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改數據)。
然后,也同樣通俗的解釋下,還是那個健身房。這次在門口不需要排號機了,而是掛著把鑰匙(只有一把),想進去的人必須拿到這把鑰匙才行,拿到鑰匙的人可以進入,不管是熱身、喝水還是跑步都可以,直到他出來把鑰匙掛回墻上,下一個才能去爭取,拿到的才可以再進去。聽著好像有點不人性化,所以悲觀鎖比較適合強一致性的場景,但效率比較低,特別是讀的并發低。樂觀鎖則適用于讀多寫少,并發沖突少的場景。
背景
先說下,本文的開發背景,方便大家了解為什么要使用悲觀鎖以及文中鎖的詳細設計。
任務分發系統:任務池(mysql)中存在大量任務(文章),現在需要用戶協助編輯,系統基本需求如下(簡化版):
1、推送用戶感興趣的分類下的任務到用戶編輯器中;
2、用戶編輯提交一個任務后,自動推送下一個任務;
3、每次只分配一個任務給用戶;
4、如果一個用戶占有某任務超過一定時間,則自動釋放任務,任務進任務池,重新循環;
5、……
目標
目標有兩個:
1、一個任務在同一時間段內只能被一個用戶所持有;
2、避免出現死任務,即避免任務被用戶長時間占有,無法釋放。
思路
由于系統并發量較大,并且有頻繁的寫操作,所以選擇悲觀鎖來控制每個任務只能同時被一個用戶領取。主要思路如下:
1、從任務池中找出一部分可分配的任務;
2、根據一定順序,選擇一個任務,作為候選推送任務;
3、嘗試對候選推送任務加鎖;
4、如果加鎖成功,則推送任務給用戶,并修改對應的任務狀態和用戶狀態;
5、如果加鎖失敗,則任務已被領取,重復2-5,直到推送成功。
實現
這里只介紹下鎖的實現機制,其余業務邏輯略過。由于加鎖過程應該是不可拆解的,也就是常說的原子型操作,因此這里選擇redis中的setnx操作作為加鎖的方法。
簡化版的代碼如下:
function lock($strMutex, $intTimeout) { $objRedis = new Redis(); //使用setnx原子型操作加鎖 $intRet = $objRedis->setnx($strMutex, 1); if ($intRet) { //設置過期時間,防止死任務的出現 $objRedis->expire($strMutex, $intTimeout); return true; } return false; }
這段代碼有個問題,就是setnx成功,但expire失敗,這就可能存在死任務的情況。解決這個問題的一種通用方法是通過使用incr方法代替setnx,具體如下:
function lock($strMutex, $intTimeout, $intMaxTimes = 0) { $objRedis = new Redis(); //使用incr原子型操作加鎖 $intRet = $objRedis->incr($strMutex); if ($intRet === 1) { //設置過期時間,防止死任務的出現 $objRedis->expire($strMutex, $intTimeout); return true; } if ($intMaxTimes > 0 && $intRet >= $intMaxTimes && $objRedis->ttl($strMutex) === -1) { //當設置了最大加鎖次數時,如果嘗試加鎖次數大于最大加鎖次數并且無過期時間則強制解鎖 $objRedis->del($strMutex); } return false; }
這段代碼通過$intMaxTimes來保證即使在expire未成功的時候也能強制解鎖,保證系統不會出現死任務。
還有沒有更好的方法呢?
其實redis中的set操作已兼容了setnx,并且支持設置過期時間。
function lock($strMutex, $intTimeout) { $objRedis = new Redis(); //使用setnx操作加鎖,同時設置過期時間 $strRet = $objRedis->set($strMutex, 1, 'ex', $intTimeout, 'nx'); if ($strRet === 'OK') { return true; } return false; }
看完上述內容,你們對怎么在PHP中使用redis實現一個悲觀鎖機制有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。