佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > PHP+redis實(shí)現(xiàn)的悲觀鎖機(jī)制示例

PHP+redis實(shí)現(xiàn)的悲觀鎖機(jī)制示例

熱門(mén)標(biāo)簽:電子圍欄 阿里云 團(tuán)購(gòu)網(wǎng)站 服務(wù)器配置 Mysql連接數(shù)設(shè)置 銀行業(yè)務(wù) 科大訊飛語(yǔ)音識(shí)別系統(tǒng) Linux服務(wù)器

本文實(shí)例講述了PHP+redis實(shí)現(xiàn)的悲觀鎖。分享給大家供大家參考,具體如下:

鎖機(jī)制

通常使用的鎖分為樂(lè)觀鎖,悲觀鎖這兩種,簡(jiǎn)單介紹下這兩種鎖,作為本文的背景知識(shí),對(duì)這類知識(shí)已經(jīng)有足夠了解的同學(xué)可以跳過(guò)這部分。

樂(lè)觀鎖

先來(lái)看下百度百科上的解釋:大多是基于數(shù)據(jù)版本( Version )記錄機(jī)制實(shí)現(xiàn)。何謂數(shù)據(jù)版本?即為數(shù)據(jù)增加一個(gè)版本標(biāo)識(shí),在基于數(shù)據(jù)庫(kù)表的版本解決方案中,一般是通過(guò)為數(shù)據(jù)庫(kù)表增加一個(gè) “version” 字段來(lái)實(shí)現(xiàn)。讀取出數(shù)據(jù)時(shí),將此版本號(hào)一同讀出,之后更新時(shí),對(duì)此版本號(hào)加一。此時(shí),將提交數(shù)據(jù)的版本數(shù)據(jù)與數(shù)據(jù)庫(kù)表對(duì)應(yīng)記錄的當(dāng)前版本信息進(jìn)行比對(duì),如果提交的數(shù)據(jù)版本號(hào)大于數(shù)據(jù)庫(kù)表當(dāng)前版本號(hào),則予以更新,否則認(rèn)為是過(guò)期數(shù)據(jù)。

其實(shí)說(shuō)白了,就是好比一個(gè)健身房里只有一臺(tái)跑步機(jī),在健身房門(mén)口有個(gè)排號(hào)機(jī),每個(gè)進(jìn)健身房的人都得先領(lǐng)一個(gè)號(hào)碼才能進(jìn)入,如果跑步機(jī)上有人,則在一邊做做熱身、喝喝水,如果跑步機(jī)上沒(méi)人,則確認(rèn)跑步機(jī)上當(dāng)前顯示的號(hào)碼(上一個(gè)用過(guò)跑步機(jī)的人的號(hào)碼)是否比自己手持的小,如果小,則可以使用;否則,就意味著過(guò)號(hào),而過(guò)號(hào)在現(xiàn)實(shí)中我們的都知道要么走,要么重排,就是不能插隊(duì),在系統(tǒng)中也是一樣的,通常是返回錯(cuò)誤。

悲觀鎖

同樣,來(lái)看下百度百科的解釋:具有強(qiáng)烈的獨(dú)占和排他特性。它指的是對(duì)數(shù)據(jù)被外界(包括本系統(tǒng)當(dāng)前的其他事務(wù),以及來(lái)自外部系統(tǒng)的事務(wù)處理)修改持保守態(tài)度,因此,在整個(gè)數(shù)據(jù)處理過(guò)程中,將數(shù)據(jù)處于鎖定狀態(tài)。悲觀鎖的實(shí)現(xiàn),往往依靠數(shù)據(jù)庫(kù)提供的鎖機(jī)制(也只有數(shù)據(jù)庫(kù)層提供的鎖機(jī)制才能真正保證數(shù)據(jù)訪問(wèn)的排他性,否則,即使在本系統(tǒng)中實(shí)現(xiàn)了加鎖機(jī)制,也無(wú)法保證外部系統(tǒng)不會(huì)修改數(shù)據(jù))。

然后,也同樣通俗的解釋下,還是那個(gè)健身房。這次在門(mén)口不需要排號(hào)機(jī)了,而是掛著把鑰匙(只有一把),想進(jìn)去的人必須拿到這把鑰匙才行,拿到鑰匙的人可以進(jìn)入,不管是熱身、喝水還是跑步都可以,直到他出來(lái)把鑰匙掛回墻上,下一個(gè)才能去爭(zhēng)取,拿到的才可以再進(jìn)去。聽(tīng)著好像有點(diǎn)不人性化,所以悲觀鎖比較適合強(qiáng)一致性的場(chǎng)景,但效率比較低,特別是讀的并發(fā)低。樂(lè)觀鎖則適用于讀多寫(xiě)少,并發(fā)沖突少的場(chǎng)景。

背景

先說(shuō)下,本文的開(kāi)發(fā)背景,方便大家了解為什么要使用悲觀鎖以及文中鎖的詳細(xì)設(shè)計(jì)。

任務(wù)分發(fā)系統(tǒng):任務(wù)池(mysql)中存在大量任務(wù)(文章),現(xiàn)在需要用戶協(xié)助編輯,系統(tǒng)基本需求如下(簡(jiǎn)化版):

1、推送用戶感興趣的分類下的任務(wù)到用戶編輯器中;
2、用戶編輯提交一個(gè)任務(wù)后,自動(dòng)推送下一個(gè)任務(wù);
3、每次只分配一個(gè)任務(wù)給用戶;
4、如果一個(gè)用戶占有某任務(wù)超過(guò)一定時(shí)間,則自動(dòng)釋放任務(wù),任務(wù)進(jìn)任務(wù)池,重新循環(huán);
5、……

目標(biāo)

目標(biāo)有兩個(gè):

1、一個(gè)任務(wù)在同一時(shí)間段內(nèi)只能被一個(gè)用戶所持有;

2、避免出現(xiàn)死任務(wù),即避免任務(wù)被用戶長(zhǎng)時(shí)間占有,無(wú)法釋放。

思路

由于系統(tǒng)并發(fā)量較大,并且有頻繁的寫(xiě)操作,所以選擇悲觀鎖來(lái)控制每個(gè)任務(wù)只能同時(shí)被一個(gè)用戶領(lǐng)取。主要思路如下:

1、從任務(wù)池中找出一部分可分配的任務(wù);
2、根據(jù)一定順序,選擇一個(gè)任務(wù),作為候選推送任務(wù);
3、嘗試對(duì)候選推送任務(wù)加鎖;
4、如果加鎖成功,則推送任務(wù)給用戶,并修改對(duì)應(yīng)的任務(wù)狀態(tài)和用戶狀態(tài);
5、如果加鎖失敗,則任務(wù)已被領(lǐng)取,重復(fù)2-5,直到推送成功。

實(shí)現(xiàn)

這里只介紹下鎖的實(shí)現(xiàn)機(jī)制,其余業(yè)務(wù)邏輯略過(guò)。由于加鎖過(guò)程應(yīng)該是不可拆解的,也就是常說(shuō)的原子型操作,因此這里選擇redis中的setnx操作作為加鎖的方法。

簡(jiǎn)化版的代碼如下:

function lock($strMutex, $intTimeout) {
  $objRedis = new Redis();
  //使用setnx原子型操作加鎖
  $intRet  = $objRedis->setnx($strMutex, 1);
  if ($intRet) {
    //設(shè)置過(guò)期時(shí)間,防止死任務(wù)的出現(xiàn)
    $objRedis->expire($strMutex, $intTimeout);
    return true;
  }
  return false;
}

這段代碼有個(gè)問(wèn)題,就是setnx成功,但expire失敗,這就可能存在死任務(wù)的情況。解決這個(gè)問(wèn)題的一種通用方法是通過(guò)使用incr方法代替setnx,具體如下:

function lock($strMutex, $intTimeout, $intMaxTimes = 0) {
  $objRedis = new Redis();
  //使用incr原子型操作加鎖
  $intRet  = $objRedis->incr($strMutex);
  if ($intRet === 1) {
    //設(shè)置過(guò)期時(shí)間,防止死任務(wù)的出現(xiàn)
    $objRedis->expire($strMutex, $intTimeout);
    return true;
  }
  if ($intMaxTimes > 0  $intRet >= $intMaxTimes  $objRedis->ttl($strMutex) === -1) {
    //當(dāng)設(shè)置了最大加鎖次數(shù)時(shí),如果嘗試加鎖次數(shù)大于最大加鎖次數(shù)并且無(wú)過(guò)期時(shí)間則強(qiáng)制解鎖
    $objRedis->del($strMutex);
  }
  return false;
}

這段代碼通過(guò)$intMaxTimes來(lái)保證即使在expire未成功的時(shí)候也能強(qiáng)制解鎖,保證系統(tǒng)不會(huì)出現(xiàn)死任務(wù)。

還有沒(méi)有更好的方法呢?

其實(shí)redis中的set操作已兼容了setnx,并且支持設(shè)置過(guò)期時(shí)間。

function lock($strMutex, $intTimeout) {
  $objRedis = new Redis();
  //使用setnx操作加鎖,同時(shí)設(shè)置過(guò)期時(shí)間
  $strRet  = $objRedis->set($strMutex, 1, 'ex', $intTimeout, 'nx');
  if ($strRet === 'OK') {
    return true;
  }
  return false;
}

這個(gè)方法是我認(rèn)為目前最好的,但是為什么沒(méi)有直接介紹這個(gè)方法,而是先介紹incr那個(gè)方法呢?其實(shí)細(xì)心的同學(xué)可以看到上面那個(gè)方面有兩個(gè)加粗的字”通用“。之所以這么說(shuō)是因?yàn)閟et方法是從redis2.6.12版本才開(kāi)始支持多參數(shù)的。

水平有限,歡迎指正~

參考資料:http://redisdoc.com/string/set.html

更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php+redis數(shù)據(jù)庫(kù)程序設(shè)計(jì)技巧總結(jié)》、《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門(mén)教程》、《PHP基本語(yǔ)法入門(mén)教程》、《PHP數(shù)組(Array)操作技巧大全》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫(kù)操作入門(mén)教程》及《php常見(jiàn)數(shù)據(jù)庫(kù)操作技巧匯總》

希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。

您可能感興趣的文章:
  • Java使用Redisson分布式鎖實(shí)現(xiàn)原理
  • 基于Redis的分布式鎖的簡(jiǎn)單實(shí)現(xiàn)方法
  • Redis Template實(shí)現(xiàn)分布式鎖的實(shí)例代碼
  • 如何利用Redis鎖解決高并發(fā)問(wèn)題詳解
  • Python實(shí)現(xiàn)的redis分布式鎖功能示例
  • PHP實(shí)現(xiàn)Redis單據(jù)鎖以及防止并發(fā)重復(fù)寫(xiě)入
  • java基于jedisLock—redis分布式鎖實(shí)現(xiàn)示例代碼
  • Java編程redisson實(shí)現(xiàn)分布式鎖代碼示例
  • redis實(shí)現(xiàn)加鎖的幾種方法示例詳解
  • redis鎖機(jī)制介紹與實(shí)例

標(biāo)簽:棗莊 衡水 大理 萍鄉(xiāng) 蚌埠 江蘇 衢州 廣元

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《PHP+redis實(shí)現(xiàn)的悲觀鎖機(jī)制示例》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    巫溪县| 宜春市| 弥渡县| 尼木县| 英吉沙县| 贵港市| 周至县| 喀喇沁旗| 宝兴县| 祁门县| 四川省| 宾川县| 礼泉县| 西贡区| 余姚市| 通河县| 宿迁市| 吉林市| 开原市| 榆树市| 明光市| 北票市| 富宁县| 始兴县| 营口市| 临邑县| 临江市| 太白县| 罗山县| 开化县| 汉阴县| 彝良县| 玉环县| 汶川县| 布尔津县| 禹州市| 秦安县| 呼图壁县| 乌鲁木齐县| 怀远县| 东明县|