Maison  >  Article  >  développement back-end  >  Exemple de code montrant comment verrouiller la concurrence en php

Exemple de code montrant comment verrouiller la concurrence en php

黄舟
黄舟original
2017-07-17 15:50:162313parcourir

Dans le projet de travail de CleverCode, certains accès simultanés PHP seront rencontrés pour modifier un problème de données. Si les données ne sont pas verrouillées, des erreurs de données se produiront. Le prochain CleverCode analysera un problème de blocage de paiement financier.

1 Aucun mécanisme de verrouillage de l'application

1.1 Code de version simplifiée du paiement financier

<?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 Analyse du problème

S'il y a deux opérateurs (p et m), tous deux utilisent des comptes avec le numéro d'utilisateur 100 et se connectent simultanément sur PC et téléphone portable respectivement. Le solde total du compte 100 est de 1000, l'opérateur p dépense 200 et l'opérateur. m en dépense 300. Le processus simultané est le suivant.

p Opérateur :

1 Retirez le solde de 1000 de l'utilisateur.

2 Le montant restant après paiement est de 800 = 1000 - 200.

3 Le solde du compte mis à jour est de 800.

m Opérateur :

1 Retirez le solde utilisateur 1000.

2 700 restant après paiement = 1000 - 300.

3 Le solde du compte après paiement est de 700.

Après deux paiements, le solde du compte est toujours de 700. Il faudrait qu'après avoir dépensé 500, le solde du compte soit de 500. La cause fondamentale de ce phénomène est que pendant la concurrence, les données de solde obtenues par p et m en même temps sont toutes deux de 1 000.

2 Conception du verrou

L'opération de verrouillage ne comporte généralement que deux étapes, l'une consiste à obtenir le verrou (getLock) ; l'autre consiste à déverrouiller le verrou (releaseLock). Cependant, il existe de nombreuses façons d'implémenter les verrous dans la réalité, notamment l'implémentation de fichiers, l'implémentation de SQL et l'implémentation de Memcache. Selon ce scénario, nous envisageons d'utiliser le mode stratégie .

2.1 Le diagramme de classes est conçu comme suit


2.2 Le code source php est conçu comme suit

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 = &#39;SQLLock&#39;;
    const LOCK_TYPE_FILE = &#39;FileLock&#39;;
    const LOCK_TYPE_MEMCACHE = &#39;MemcacheLock&#39;;
    
    private $_lock = null;
    private static $_supportLocks = array(&#39;FileLock&#39;, &#39;SQLLock&#39;, &#39;MemcacheLock&#39;);  
    
    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(&#39;false == $this->_lock instanceof ILock&#39;);          
        }  
        $this->_lock->getLock($key, $timeout);   
    }
    
    public function releaseLock($key)
    {
        if (false == $this->_lock instanceof ILock)  
        {
            throw new Exception(&#39;false == $this->_lock instanceof ILock&#39;);          
        }  
        $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[&#39;path&#39;]) && is_dir($options[&#39;path&#39;]))
        {
            $this->_lockPath = $options[&#39;path&#39;].&#39;/&#39;;
        }
        else
        {
            $this->_lockPath = &#39;/tmp/&#39;;
        }
       
        $this->_single = isset($options[&#39;single&#39;])?$options[&#39;single&#39;]:false;
    }

    public function getLock($key, $timeout=self::EXPIRE)
    {
        $startTime = Timer::getTimeStamp();

        $file = md5(FILE.$key);
        $this->fp = fopen($this->_lockPath.$file.&#39;.lock&#39;, "w+");
        if (true || $this->_single)
        {
            $op = LOCK_EX + LOCK_NB;
        }
        else
        {
            $op = LOCK_EX;
        }
        if (false == flock($this->fp, $op, $a))
        {
            throw new Exception(&#39;failed&#39;);
        }
       
	    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(&#39;".$key."&#39;, &#39;".$timeout."&#39;)";
        $res =  $this->_db->query($sql);
        return $res;
    }

    public function releaseLock($key)
    {
        $sql = "SELECT RELEASE_LOCK(&#39;".$key."&#39;)";
        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(&#39;can not get lock for waiting &#39;.$timeout.&#39;s.&#39;);

    }

    public function releaseLock($key)
    {
        $this->memcache->delete($key);
    }
}

3 Mécanisme de verrouillage de l'application

3.1 Verrouillage de l'application du système de paiement

<?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 = &#39;pay&#39;.$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 Analyse du verrou

p Opérateur :

1 Obtenez le verrou : pay100

2 Retirez le solde de 1000 de l'utilisateur.

3 Le montant restant après paiement est de 800 = 1000 - 200.

4 Le solde du compte mis à jour est de 800.

5 Libérer le verrou : payer 100

m Opérateur :

1 Attendre le verrou : payer 100

2 Obtenir le verrou : payer 100

3 Obtenez le solde : 800

4 Les 500 restants après paiement = 800 - 300.

5 Le solde du compte est de 500 après paiement.

6 Libérer le verrou : payer 100

Après deux paiements, le solde est de 500. Il résout parfaitement le problème d’accès aux ressources des sections critiques causé par la concurrence.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn