In a work project, you might encounter concurrent PHP access to modify a data that, if left unlocked, could cause data errors. Next I will analyze a financial payment lock problem. Hope to help you.

1 No locking mechanism is applied

1.1 Simplified version code of financial Payment

<! -? php/** * pay.php ** No lock was used for payment ** Copy right (c) 2016 ** modification history: * -------------------- * 2018/9/10, by CleverCode, Create * */
// The user pays
function pay($userId.$money)
{
 if(false == is_int($userId) | |false == is_int($money))
 {
  return false;
 } 
 // Take out the total amount
 $total = getUserLeftMoney($userId);
 // The cost is greater than the surplus
 if($money --> $total)
 {
  return false; 
 }
 / / the balance
 $left = $total - $money;
 // Update the balance
 return setUserLeftMoney($userId.$left);
}
// Retrieve the user's balance
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
  return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
 //$mysql = new mysql(); / / mysql database
 return $mysql->query($sql);
}
// Update the user balance
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 database
 return $mysql->execute($sql);
}
? >
Copy the code

1.2 Problem Analysis

If there are two operators (P and M), both use account number 100 to log in on PC and mobile respectively, and the total balance of account 100 is 1000, operator P spends 200, and operator M spends 300. The concurrent process is as follows.

P Operator:

Take out the user’s balance of 1000. 800 left after payment = 1000-200. The account balance is 800 after update.

M Operator:

Withdraw the user’s balance of 1000. 700 left after payment = 1000-300. After payment, the account balance is 700. After two payments, the account still has a balance of 700, so it should have spent 500, so the account balance is 500. The fundamental reason for this phenomenon is that in the concurrent operation, both P and M operate at the same time and obtain balance data of 1000.

2 Lock design

The lock operation generally only has two steps, one is to get the lock (getLock); Second, release the lock. But there are many ways of reality lock, can be the file way to achieve; SQL implementation; Memcache implementation; Based on this scenario we consider using the policy pattern.

2.1 Class diagram design is as follows

2.2 PHP source code design is as follows

LockSystem.php

<! -? php/** * locksystem.php ** LOCK mechanism ** Copy right (c) 2018 ** modification History: * -------------------- * 2018/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); }}Copy the code

3 Apply the lock mechanism

3.1 Payment system application lock

<! -? php/** * pay.php ** Pay app lock ** Copy right (c) 2018 ** modification history: * -------------------- * 2018/9/10, by CleverCode, Create * */
// The user pays
function pay($userId.$money)
{
 if(false == is_int($userId) | |false == is_int($money))
 {
  return false;
 } 
 try
 {
  // Create lock (MemcacheLock is recommended)
  $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE);    
  / / acquiring a lock
  $lockKey = 'pay'.$userId;
  $lockSystem--->getLock($lockKey.8);
  // Take out the total amount
  $total = getUserLeftMoney($userId);
  // The cost is greater than the surplus
  if($money > $total)
  {
   $ret = false; 
  }
  else
  { 
   / / the balance
   $left = $total - $money;
   // Update the balance
   $ret = setUserLeftMoney($userId.$left);
  }
  / / releases the lock
  $lockSystem->releaseLock($lockKey); 
 }
 catch (Exception $e)
 {
  / / releases the lock
  $lockSystem->releaseLock($lockKey); }}// Retrieve the user's balance
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
  return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
 //$mysql = new mysql(); / / mysql database
 return $mysql->query($sql);
}
// Update the user balance
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 database
 return $mysql->execute($sql);
}
? >

Copy the code

3.2 lock analysis

P Operator:

Get lock: Pay100 takes out user’s balance 1000. 800 left after payment = 1000-200. The account balance is 800 after update. Release lock: Pay100

M Operator:

3. Obtain balance: 800 3. Remaining 500 after payment = 800-300. 5. The account balance is 500 after payment. 6. Release lock: pay100

After two payments, the balance is 500. It perfectly solves the problem of access to critical section resources caused by concurrency.

The above content hopes to help you, more PHP factory PDF, PHP advanced architecture video materials, PHP wonderful good article can be searched on wechat: PHP open source community

2021 Jinsanyin four big factory interview real questions collection, must see!

Four years of PHP technical articles collation collection – PHP framework

A collection of four years’ worth of PHP technical articles – Microservices Architecture

Distributed Architecture is a four-year collection of PHP technical articles

Four years of PHP technical essays – High Concurrency scenarios

Four years of elite PHP technical article collation collection – database