PHP-Fallanalyse für gleichzeitiges Sperren

墨辰丷
Freigeben: 2023-03-28 18:20:01
Original
2086 Leute haben es durchsucht

In diesem Artikel wird ein Beispiel für die gleichzeitige Sperrung in PHP vorgestellt. Zu diesem Zeitpunkt ist die Sperrung erforderlich.

Im Arbeitsprojekt treten einige Probleme beim gleichzeitigen Zugriff von PHP zum Ändern von Daten auf. Wenn die Daten nicht gesperrt sind, führt dies zu Datenfehlern. Als nächstes werde ich ein Problem mit der Sperrung finanzieller Zahlungen analysieren. Ich hoffe, es hilft allen.

1 Kein Anwendungssperrmechanismus

1.1 Vereinfachter Versionscode für finanzielle Zahlungen

<!--?php 
/** 
 * pay.php 
 * 
 * 支付没有应用锁
 * 
 * Copy right (c) 2016 
 * 
 * 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 Operatoren (p und m) gibt, verwenden beide Konten mit der Benutzernummer 100, Melden Sie sich gleichzeitig am PC und an den mobilen Endgeräten an. Der Gesamtsaldo des 100-Kontos beträgt 1.000, der p-Betreiber gibt 300 aus. Der gleichzeitige Prozess ist wie folgt.

p Operator:

  1. Ziehen Sie das Guthaben des Benutzers 1000 ab.

  2. Verbleibende 800 = 1000 - 200 nach der Zahlung.

  3. Der aktualisierte Kontostand beträgt 800.

m Betreiber:

  1. Ziehen Sie das Benutzerguthaben 1000 ab.

  2. 700 nach der Zahlung übrig = 1000 - 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); Lösen Sie die Sperre (releaseLock). In der Realität gibt es jedoch viele Möglichkeiten, Sperren zu implementieren, einschließlich der Dateiimplementierung, der SQL-Implementierung und der Memcache-Implementierung. 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
 * 
 * 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 
 * 
 * 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 von 1000 ab.

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

  4. Der aktualisierte Kontostand beträgt 800.

  5. Sperre freigeben: pay100

m Betreiber:

  1. Warten auf Sperre: 100 bezahlen

  2. Sperre erhalten: 100 bezahlen

  3. Guthaben erhalten: 800

  4. 500 verbleiben nach der Zahlung = 800-300.

  5. Der Kontostand nach der Zahlung beträgt 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.

Zusammenfassung: Das Obige ist der gesamte Inhalt dieses Artikels, ich hoffe, dass er für das Studium aller hilfreich sein wird.

Verwandte Empfehlungen:

PHP verwendet file_get_contents, um HTTP-Anfragen mit einer einfachen Funktion zu senden

PHP+MySQL-Sperre mit hoher Parallelität Transaktionsverarbeitung Lösung des Problems

Monkey King-Algorithmus (Monkey Chooses the King), implementiert in PHP

Das obige ist der detaillierte Inhalt vonPHP-Fallanalyse für gleichzeitiges Sperren. 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