php如何將並發加鎖的範例程式碼

來源:互聯網
上載者:User
CleverCode在工作項目中,會遇到一些php並發訪問去修改一個資料問題,如果這個資料不加鎖,就會造成資料的錯誤。下面CleverCode將分析一個財務支付鎖的問題。

1 沒有應用鎖機制

1.1 財務支付簡化版本代碼

<?php/**  * pay.php  *  * 支付沒有應用鎖 *  * Copy right (c) 2016 http://blog.csdn.net/CleverCode  *  * modification history:  * --------------------  * 2016/9/10, by CleverCode, Create  *  */  //使用者支付function pay($userId,$money){        if(false == is_int($userId) || false == is_int($money))    {        return false;    }          //取出總額    $total = getUserLeftMoney($userId);        //花費大於剩餘    if($money > $total)    {        return false;        }        //餘額    $left = $total - $money;        //更新喻額    return setUserLeftMoney($userId,$left);}//取出使用者的餘額function getUserLeftMoney($userId){    if(false == is_int($userId))    {        return 0;    }    $sql = "select account form user_account where userid = ${userId}";        //$mysql = new mysql();//mysql資料庫    return $mysql->query($sql);}//更新使用者餘額function setUserLeftMoney($userId,$money){    if(false == is_int($userId) || false == is_int($money))    {        return false;    }                $sql = "update user_account set account = ${money} where userid = ${userId}";        //$mysql = new mysql();//mysql資料庫    return $mysql->execute($sql);}?>

1.2 問題分析

如果有兩個操作人(p和m),都用使用者編號100賬戶,分別在pc和手機端同時登陸,100賬戶總餘額有1000,p操作人花200,m操作人花300。並發過程如下。

p操作人:

1 取出使用者的餘額1000。

2 支付後剩餘 800 = 1000 - 200。

3 更新後賬戶餘額800。

m操作人:

1 取出使用者餘額1000。

2 支付後剩餘700 = 1000 - 300。

3 支付後賬戶餘額700。

兩次支付後,賬戶的餘額居然還有700,應該的情況是花費了500,賬戶餘額500才對。造成這個現象的根本原因,是並發的時候,p和m同時操作取到的餘額資料都是1000。

2 加鎖設計

鎖的操作一般只有兩步,一 擷取鎖(getLock);二是釋放鎖(releaseLock)。但現實鎖的方式有很多種,可以是檔案方式實現;sql實現;Memcache實現;根據這種情境我們考慮使用原則模式。

2.1 類圖設計如下


2.2 php源碼設計如下

LockSystem.php

<?php/**  * LockSystem.php  *  * php鎖機制 *  * Copy right (c) 2016 http://blog.csdn.net/CleverCode  *  * modification history:  * --------------------  * 2016/9/10, by CleverCode, Create  *  */ class LockSystem{    const LOCK_TYPE_DB = 'SQLLock';    const LOCK_TYPE_FILE = 'FileLock';    const LOCK_TYPE_MEMCACHE = 'MemcacheLock';        private $_lock = null;    private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock');          public function construct($type, $options = array())     {        if(false == empty($type))        {            $this->createLock($type, $options);        }    }       public function createLock($type, $options=array())    {        if (false == in_array($type, self::$_supportLocks))        {            throw new Exception("not support lock of ${type}");        }        $this->_lock = new $type($options);    }        public function getLock($key, $timeout = ILock::EXPIRE)    {        if (false == $this->_lock instanceof ILock)          {            throw new Exception('false == $this->_lock instanceof ILock');                  }          $this->_lock->getLock($key, $timeout);       }        public function releaseLock($key)    {        if (false == $this->_lock instanceof ILock)          {            throw new Exception('false == $this->_lock instanceof ILock');                  }          $this->_lock->releaseLock($key);             }   }interface ILock{    const EXPIRE = 5;    public function getLock($key, $timeout=self::EXPIRE);    public function releaseLock($key);}class FileLock implements ILock{    private $_fp;    private $_single;    public function construct($options)    {        if (isset($options['path']) && is_dir($options['path']))        {            $this->_lockPath = $options['path'].'/';        }        else        {            $this->_lockPath = '/tmp/';        }               $this->_single = isset($options['single'])?$options['single']:false;    }    public function getLock($key, $timeout=self::EXPIRE)    {        $startTime = Timer::getTimeStamp();        $file = md5(FILE.$key);        $this->fp = fopen($this->_lockPath.$file.'.lock', "w+");        if (true || $this->_single)        {            $op = LOCK_EX + LOCK_NB;        }        else        {            $op = LOCK_EX;        }        if (false == flock($this->fp, $op, $a))        {            throw new Exception('failed');        }           return true;    }    public function releaseLock($key)    {        flock($this->fp, LOCK_UN);        fclose($this->fp);    }}class SQLLock implements ILock{    public function construct($options)    {        $this->_db = new mysql();     }    public function getLock($key, $timeout=self::EXPIRE)    {               $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";        $res =  $this->_db->query($sql);        return $res;    }    public function releaseLock($key)    {        $sql = "SELECT RELEASE_LOCK('".$key."')";        return $this->_db->query($sql);    }}class MemcacheLock implements ILock{    public function construct($options)    {                $this->memcache = new Memcache();    }    public function getLock($key, $timeout=self::EXPIRE)    {             $waitime = 20000;        $totalWaitime = 0;        $time = $timeout*1000000;        while ($totalWaitime < $time && false == $this->memcache->add($key, 1, $timeout))         {            usleep($waitime);            $totalWaitime += $waitime;        }        if ($totalWaitime >= $time)            throw new Exception('can not get lock for waiting '.$timeout.'s.');    }    public function releaseLock($key)    {        $this->memcache->delete($key);    }}

3 應用鎖機制

3.1 支付系統應用鎖

<?php/**  * pay.php  *  * 支付應用鎖 *  * Copy right (c) 2016 http://blog.csdn.net/CleverCode  *  * modification history:  * --------------------  * 2016/9/10, by CleverCode, Create  *  */  //使用者支付function pay($userId,$money){        if(false == is_int($userId) || false == is_int($money))    {        return false;    }          try    {        //建立鎖(推薦使用MemcacheLock)        $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE);                             //擷取鎖        $lockKey = 'pay'.$userId;        $lockSystem->getLock($lockKey,8);                //取出總額        $total = getUserLeftMoney($userId);                //花費大於剩餘        if($money > $total)        {            $ret = false;            }        else        {             //餘額            $left = $total - $money;                        //更新喻額            $ret = setUserLeftMoney($userId,$left);        }                //釋放鎖        $lockSystem->releaseLock($lockKey);     }    catch (Exception $e)    {        //釋放鎖        $lockSystem->releaseLock($lockKey);         }}//取出使用者的餘額function getUserLeftMoney($userId){    if(false == is_int($userId))    {        return 0;    }    $sql = "select account form user_account where userid = ${userId}";        //$mysql = new mysql();//mysql資料庫    return $mysql->query($sql);}//更新使用者餘額function setUserLeftMoney($userId,$money){    if(false == is_int($userId) || false == is_int($money))    {        return false;    }                $sql = "update user_account set account = ${money} where userid = ${userId}";        //$mysql = new mysql();//mysql資料庫    return $mysql->execute($sql);}?>

3.2 鎖分析

p操作人:

1 擷取鎖:pay100

2 取出使用者的餘額1000。

3 支付後剩餘 800 = 1000 - 200。

4 更新後賬戶餘額800。

5 釋放鎖:pay100

m操作人:

1 等待鎖:pay100

2 擷取鎖:pay100

3 擷取餘額:800

4 支付後剩餘500 = 800 - 300。

5 支付後賬戶餘額500。

6 釋放鎖:pay100

兩次支付後,餘額500。非常完美瞭解決了並發造成的臨界區資源的訪問問題。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.