zoukankan      html  css  js  c++  java
  • php并发加锁示例

    在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面我将分析一个财务支付锁的问题。希望对大家有所帮助。

    1 没有应用锁机制

    1.1 财务支付简化版本代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    <!--?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);
    }
    ?>

    1.2 问题分析

    如果有两个操作人(p和m),都用用户编号100账户,分别在pc和手机端同时登陆,100账户总余额有1000,p操作人花200,m操作人花300。并发过程如下。

    p操作人:

    1. 取出用户的余额1000。
    2. 支付后剩余 800 = 1000 - 200。
    3. 更新后账户余额800。

    m操作人:

    1. 取出用户余额1000。
    2. 支付后剩余700 = 1000 - 300。
    3. 支付后账户余额700。

    两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。

    2 加锁设计

    锁的操作一般只有两步,一 获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式。

    2.1 类图设计如下

    2.2 php源码设计如下

    LockSystem.php

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    <!--?php
    /**
     * LockSystem.php
     *
     * php锁机制
     *
     * Copy right (c) 2016
     *
     * modification history:
     * --------------------
     * 2016/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);
     }
    }

    3 应用锁机制

    3.1 支付系统应用锁

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    <!--?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 = 'pay'.$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 锁分析

    p操作人:

    1.  获取锁:pay100
    2.  取出用户的余额1000。
    3.  支付后剩余 800 = 1000 - 200。
    4.  更新后账户余额800。
    5.  释放锁:pay100

     m操作人:

    1. 等待锁:pay100
    2. 获取锁:pay100
    3. 获取余额:800
    4. 支付后剩余500 = 800 - 300。
    5. 支付后账户余额500。
    6. 释放锁:pay100

    两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。

    感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

  • 相关阅读:
    软工实践个人总结
    第02组 Beta版本演示
    第02组 Beta冲刺(5/5)
    第02组 Beta冲刺(4/5)
    第02组 Beta冲刺(3/5)
    第02组 Beta冲刺(2/5)
    第02组 Beta冲刺(1/5)
    第02组 Alpha事后诸葛亮
    第02组 Alpha冲刺(6/6)
    第02组 Alpha冲刺(5/6)
  • 原文地址:https://www.cnblogs.com/pomeng/p/7347375.html
Copyright © 2011-2022 走看看