Analyse des cas de verrouillage simultané PHP

墨辰丷
Libérer: 2023-03-28 18:20:01
original
2084 Les gens l'ont consulté

Cet article présente un exemple de verrouillage simultané en PHP. Les données sont verrouillées et un seul utilisateur est autorisé à opérer à la fois. À l'heure actuelle, le verrouillage est nécessaire.

Dans le projet de travail, vous rencontrerez quelques problèmes avec l'accès simultané de PHP pour modifier une donnée. Si les données ne sont pas verrouillées, cela provoquera des erreurs de données. Ci-dessous, j'analyserai un problème de blocage des paiements financiers. J'espère que cela aide tout le monde.

1 Aucun mécanisme de verrouillage de l'application

1.1 Code de version simplifié du paiement financier

<!--?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);
}
?>
Copier après la connexion

1.2 Analyse du problème

S'il y a deux opérateurs (p et m), tous deux utilisent le numéro d'utilisateur 100. Le compte est connecté simultanément sur PC et téléphone mobile respectivement. Le solde total de 100 comptes 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. Retirer le solde de l'utilisateur 1000.

  2. 800 restants = 1000 - 200 après paiement.

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

m Opérateur :

  1. Retirer 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) ; libérer 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
 * 
 * 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);
 }
}
Copier après la connexion

3 Mécanisme de verrouillage des applications

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

<!--?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);
}
?>
Copier après la connexion

3.2 Analyse du verrouillage

p opération Personne :

  1. Obtenir le verrou : payer 100

  2. Retirer le solde de 1000 de l'utilisateur.

  3. 800 restants = 1000 - 200 après paiement.

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

  5. Libérer le verrou : payer 100

m Opérateur :

  1. En attente du verrou : payer100

  2. Obtenir le verrouillage : payer100

  3. Obtenir le solde : 800

  4. 500 restant après le paiement = 800-300.

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

  6. Lâchez le verrou : payez 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.

Résumé : Ce qui précède représente l'intégralité du contenu de cet article, j'espère qu'il sera utile à l'étude de chacun.

Recommandations associées :

PHP utilise file_get_contents pour envoyer des requêtes http avec une fonction simple

Verrouillage à haute concurrence PHP+MySQL traitement des transactions Problème Solution

Algorithme Monkey King (Monkey Chooses the King) implémenté en PHP

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
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
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal