Beispielcode zum Sperren der Parallelität in PHP

黄舟
Freigeben: 2023-03-14 09:48:01
Original
2312 Leute haben es durchsucht

Im Arbeitsprojekt von CleverCode treten einige gleichzeitige PHP-Zugriffe auf, um ein Datenproblem zu ändern. Wenn die Daten nicht gesperrt sind, treten Datenfehler auf. Als nächstes analysiert CleverCode ein Problem mit der Sperrung finanzieller Zahlungen.

1 Kein Anwendungssperrmechanismus

1.1 Vereinfachter Versionscode für finanzielle Zahlungen

<?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);
}

?>
Nach dem Login kopieren

1.2 Problemanalyse

Wenn es zwei Betreiber gibt (p und m), verwenden beide Konten mit der Benutzernummer 100 und melden sich gleichzeitig auf dem PC bzw. dem Mobiltelefon an. Der Gesamtsaldo des 100-Kontos beträgt 1000, Betreiber p gibt 200 aus und Betreiber m gibt 300 aus. Der gleichzeitige Prozess ist wie folgt.

p Operator:

1 Den Benutzersaldo von 1000 abheben.

2 Der Restbetrag nach der Zahlung beträgt 800 = 1000 - 200.

3 Der aktualisierte Kontostand beträgt 800.

m Operator:

1 Heben Sie das Benutzerguthaben von 1000 ab.

2.700 verbleiben nach der Zahlung = 1.000 – 300.

3 Der Kontostand nach der Zahlung beträgt 700.

Nach zwei Zahlungen beträgt der Kontostand immer noch 700. Es sollte so sein, dass nach einer Ausgabe von 500 der Kontostand 500 beträgt. Die Hauptursache für dieses Phänomen liegt darin, dass während der Parallelität die von p und m gleichzeitig erhaltenen Bilanzdaten jeweils 1000 betragen.

2 Sperrendesign

Der Sperrvorgang besteht im Allgemeinen nur aus zwei Schritten: Einer besteht darin, die Sperre zu erhalten (getLock), der andere darin, die Sperre aufzuheben (releaseLock). Es gibt jedoch viele Möglichkeiten, Sperren in der Realität zu implementieren, einschließlich der Implementierung von Dateien und des Memcaches. Gemäß diesem Szenario erwägen wir die Verwendung des -Strategiemodus .

2.1 Das Klassendiagramm ist wie folgt gestaltet


2.2 Der PHP-Quellcode ist wie folgt gestaltet

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);
    }
}
Nach dem Login kopieren

3 Anwendungssperrmechanismus

3.1 Zahlungssystem-Anwendungssperre

<?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);
}

?>
Nach dem Login kopieren

3.2 Sperranalyse

p Operator:

1 Holen Sie sich die Sperre: pay100

2 Heben Sie das Guthaben des Benutzers in Höhe von 1000 ab.

3 Der Restbetrag nach der Zahlung beträgt 800 = 1000 - 200.

4 Der aktualisierte Kontostand beträgt 800.

5 Sperre aufheben: pay100

m Operator:

1 Auf Sperre warten: pay100

2 Sperre erhalten: pay100

3 Holen Sie sich den Restbetrag: 800

4 Die restlichen 500 nach der Zahlung = 800 - 300.

5 Der Kontostand beträgt nach der Zahlung 500.

6 Sperre aufheben: 100 bezahlen

Nach zwei Zahlungen beträgt der Restbetrag 500. Es löst perfekt das Zugriffsproblem kritischer Abschnittsressourcen, das durch Parallelität verursacht wird.

Das obige ist der detaillierte Inhalt vonBeispielcode zum Sperren der Parallelität in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage
Über uns Haftungsausschluss Sitemap
Chinesische PHP-Website:Online-PHP-Schulung für das Gemeinwohl,Helfen Sie PHP-Lernenden, sich schnell weiterzuentwickeln!