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

1 没有应用锁机制

1.1 财务支付简化版本代码

  1. <?php
  2. /**
  3. * pay.php
  4. *
  5. * 支付没有应用锁
  6. *
  7. * Copy right (c) 2016 http://blog.csdn.net/CleverCode
  8. *
  9. * modification history:
  10. * --------------------
  11. * 2016/9/10, by CleverCode, Create
  12. *
  13. */
  14. //用户支付
  15. function pay($userId,$money)
  16. {
  17. if(false == is_int($userId) || false == is_int($money))
  18. {
  19. return false;
  20. }
  21. //取出总额
  22. $total = getUserLeftMoney($userId);
  23. //花费大于剩余
  24. if($money > $total)
  25. {
  26. return false;
  27. }
  28. //余额
  29. $left = $total - $money;
  30. //更新余额
  31. return setUserLeftMoney($userId,$left);
  32. }
  33. //取出用户的余额
  34. function getUserLeftMoney($userId)
  35. {
  36. if(false == is_int($userId))
  37. {
  38. return 0;
  39. }
  40. $sql = "select account form user_account where userid = ${userId}";
  41. //$mysql = new mysql();//mysql数据库
  42. return $mysql->query($sql);
  43. }
  44. //更新用户余额
  45. function setUserLeftMoney($userId,$money)
  46. {
  47. if(false == is_int($userId) || false == is_int($money))
  48. {
  49. return false;
  50. }
  51. $sql = "update user_account set account = ${money} where userid = ${userId}";
  52. //$mysql = new mysql();//mysql数据库
  53. return $mysql->execute($sql);
  54. }
  55. ?>

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. <?php
  2. /**
  3. * LockSystem.php
  4. *
  5. * php锁机制
  6. *
  7. * Copy right (c) 2016 http://blog.csdn.net/CleverCode
  8. *
  9. * modification history:
  10. * --------------------
  11. * 2016/9/10, by CleverCode, Create
  12. *
  13. */
  14. class LockSystem
  15. {
  16. const LOCK_TYPE_DB = 'SQLLock';
  17. const LOCK_TYPE_FILE = 'FileLock';
  18. const LOCK_TYPE_MEMCACHE = 'MemcacheLock';
  19. private $_lock = null;
  20. private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock');
  21. public function __construct($type, $options = array())
  22. {
  23. if(false == empty($type))
  24. {
  25. $this->createLock($type, $options);
  26. }
  27. }
  28. public function createLock($type, $options=array())
  29. {
  30. if (false == in_array($type, self::$_supportLocks))
  31. {
  32. throw new Exception("not support lock of ${type}");
  33. }
  34. $this->_lock = new $type($options);
  35. }
  36. public function getLock($key, $timeout = ILock::EXPIRE)
  37. {
  38. if (false == $this->_lock instanceof ILock)
  39. {
  40. throw new Exception('false == $this->_lock instanceof ILock');
  41. }
  42. $this->_lock->getLock($key, $timeout);
  43. }
  44. public function releaseLock($key)
  45. {
  46. if (false == $this->_lock instanceof ILock)
  47. {
  48. throw new Exception('false == $this->_lock instanceof ILock');
  49. }
  50. $this->_lock->releaseLock($key);
  51. }
  52. }
  53. interface ILock
  54. {
  55. const EXPIRE = 5;
  56. public function getLock($key, $timeout=self::EXPIRE);
  57. public function releaseLock($key);
  58. }
  59. class FileLock implements ILock
  60. {
  61. private $_fp;
  62. private $_single;
  63. public function __construct($options)
  64. {
  65. if (isset($options['path']) && is_dir($options['path']))
  66. {
  67. $this->_lockPath = $options['path'].'/';
  68. }
  69. else
  70. {
  71. $this->_lockPath = '/tmp/';
  72. }
  73. $this->_single = isset($options['single'])?$options['single']:false;
  74. }
  75. public function getLock($key, $timeout=self::EXPIRE)
  76. {
  77. $startTime = Timer::getTimeStamp();
  78. $file = md5(__FILE__.$key);
  79. $this->fp = fopen($this->_lockPath.$file.'.lock', "w+");
  80. if (true || $this->_single)
  81. {
  82. $op = LOCK_EX + LOCK_NB;
  83. }
  84. else
  85. {
  86. $op = LOCK_EX;
  87. }
  88. if (false == flock($this->fp, $op, $a))
  89. {
  90. throw new Exception('failed');
  91. }
  92. return true;
  93. }
  94. public function releaseLock($key)
  95. {
  96. flock($this->fp, LOCK_UN);
  97. fclose($this->fp);
  98. }
  99. }
  100. class SQLLock implements ILock
  101. {
  102. public function __construct($options)
  103. {
  104. $this->_db = new mysql();
  105. }
  106. public function getLock($key, $timeout=self::EXPIRE)
  107. {
  108. $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
  109. $res =  $this->_db->query($sql);
  110. return $res;
  111. }
  112. public function releaseLock($key)
  113. {
  114. $sql = "SELECT RELEASE_LOCK('".$key."')";
  115. return $this->_db->query($sql);
  116. }
  117. }
  118. class MemcacheLock implements ILock
  119. {
  120. public function __construct($options)
  121. {
  122. $this->memcache = new Memcache();
  123. }
  124. public function getLock($key, $timeout=self::EXPIRE)
  125. {
  126. $waitime = 20000;
  127. $totalWaitime = 0;
  128. $time = $timeout*1000000;
  129. while ($totalWaitime < $time && false == $this->memcache->add($key, 1, $timeout))
  130. {
  131. usleep($waitime);
  132. $totalWaitime += $waitime;
  133. }
  134. if ($totalWaitime >= $time)
  135. throw new Exception('can not get lock for waiting '.$timeout.'s.');
  136. }
  137. public function releaseLock($key)
  138. {
  139. $this->memcache->delete($key);
  140. }
  141. }

3 应用锁机制

3.1 支付系统应用锁

  1. <?php
  2. /**
  3. * pay.php
  4. *
  5. * 支付应用锁
  6. *
  7. * Copy right (c) 2016 http://blog.csdn.net/CleverCode
  8. *
  9. * modification history:
  10. * --------------------
  11. * 2016/9/10, by CleverCode, Create
  12. *
  13. */
  14. //用户支付
  15. function pay($userId,$money)
  16. {
  17. if(false == is_int($userId) || false == is_int($money))
  18. {
  19. return false;
  20. }
  21. try
  22. {
  23. //创建锁(推荐使用MemcacheLock)
  24. $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE);
  25. //获取锁
  26. $lockKey = 'pay'.$userId;
  27. $lockSystem->getLock($lockKey,8);
  28. //取出总额
  29. $total = getUserLeftMoney($userId);
  30. //花费大于剩余
  31. if($money > $total)
  32. {
  33. $ret = false;
  34. }
  35. else
  36. {
  37. //余额
  38. $left = $total - $money;
  39. //更新余额
  40. $ret = setUserLeftMoney($userId,$left);
  41. }
  42. //释放锁
  43. $lockSystem->releaseLock($lockKey);
  44. }
  45. catch (Exception $e)
  46. {
  47. //释放锁
  48. $lockSystem->releaseLock($lockKey);
  49. }
  50. }
  51. //取出用户的余额
  52. function getUserLeftMoney($userId)
  53. {
  54. if(false == is_int($userId))
  55. {
  56. return 0;
  57. }
  58. $sql = "select account form user_account where userid = ${userId}";
  59. //$mysql = new mysql();//mysql数据库
  60. return $mysql->query($sql);
  61. }
  62. //更新用户余额
  63. function setUserLeftMoney($userId,$money)
  64. {
  65. if(false == is_int($userId) || false == is_int($money))
  66. {
  67. return false;
  68. }
  69. $sql = "update user_account set account = ${money} where userid = ${userId}";
  70. //$mysql = new mysql();//mysql数据库
  71. return $mysql->execute($sql);
  72. }
  73. ?>

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。非常完美了解决了并发造成的临界区资源的访问问题。

 

php并发加锁的更多相关文章

  1. ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁

    前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...

  2. php并发加锁示例

    在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误.下面我将分析一个财务支付锁的问题.希望对大家有所帮助. 1 没有应用锁机制 1.1 财务支付简化版本代 ...

  3. PHP_MySQL高并发加锁事务处理

    1.背景: 现在有这样的需求,插入数据时,判断test表有无username为‘mraz’的数据,无则插入,有则提示“已插入”,目的就是想只插入一条username为‘mraz’的记录. 2.一般程序 ...

  4. Java并发(9)- 从同步容器到并发容器

    引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的, ...

  5. 【整理】互联网服务端技术体系:高性能之并发(Java)

    分而合之,并行不悖. 综述入口见:"互联网应用服务端的常用技术思想与机制纲要" 引子 并发,就是在同一时间段内有多个任务同时进行着.这些任务或者互不影响互不干扰,或者共同协作来完成 ...

  6. C# 设计模式巩固 - 单例模式

    前言 设计模式的文章很多,所以此文章只是为了巩固一下自己的基础,说的不详细请见谅. 介绍 - 单例模式 官方定义:确保一个类只有一个实例,并提供一个全局访问点. 通俗定义:就是一个类只有一个单个实例. ...

  7. Java之架构(0) - 架构之路

    软件架构作为一个概念,体现在技术和业务两个方面. 从技术角度来说:软件架构随着技术的革新不断地更新其内容,软件架构建立于当前技术和一些基本原则的基础之上. 先说一些基本原则: 分层原则:分层是为了降低 ...

  8. 为什么我要选择erlang+go进行server架构(2)

    原创文章,转载请注明出处:server非业余研究http://blog.csdn.net/erlib 作者Sunface 为什么我要选择Erlang呢? 一.erlang特别适合中小团队创业: erl ...

  9. AQS系列(六)- Semaphore的使用及原理

    前言 Semaphore也是JUC包中一个用于并发控制的工具类,举个常用场景的例子:有三台电脑五个人,每个人都要用电脑注册一个自己的账户,这时最开始只能同时有三个人操作电脑注册账户,这三个人中有人操作 ...

随机推荐

  1. css | js 实现扩展卡片小demo

    1.代码如下 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...

  2. pythone 时间模块

    时间模块(时区) 计算方式:时间戳是一串数字,从计算机诞生的那一秒到现在过了多少秒,每过一秒+1 #时间戳#由时间戳获取格式化时间#由格式化时间获取时间戳 import time def timene ...

  3. SpringBoot入门系列(十一)统一异常处理的实现

    前面介绍了Spring Boot 如何整合定时任务已经Spring Boot 如何创建异步任务和定时任务.不清楚的朋友可以看看之前的文章:<Spring Boot 入门系列文章> 接下来主 ...

  4. "文字链接"组件:<h-link> —— 快应用组件库H-UI

     <import name="h-link" src="../Common/ui/h-ui/basic/c_link"></import&g ...

  5. Struts2-学习笔记系列(7)-PreResultListener

    在action处理完成之后,系统转入实际的物理试图之间被回调. Action,拦截器都可以添加该监听器.拦截器添加了该监听器后,该监听器会对该拦截器所有拦截的action其作用 public Stri ...

  6. 选择IT行业的自我心得,希望能帮助到各位!(三)失败篇

    可能很多小伙伴会说人人创业岂不是人人都能成功,岂不是人人都能成功,是不是每个人都能开上保时捷,法拉利泡着美女,很多人也会说你看他看她多轻松,做个IT一样就赚钱赚钱了. 那么又有多少人能理解到你的心酸了 ...

  7. Daily Scrum 1/7/2015

    Process: Zhaoyang: Do some code intergration and test the total feature in the IOS APP. Yandong: Cod ...

  8. B - How many integers can you find 杭电1976

     Now you get a number N, and a M-integers set, you should find out how many integers which are small ...

  9. 1. jquery插件手机

    1. http://jqtjs.com/preview/demos/main/index.html#home2. jquery weUI ===== 插件:https://blog.csdn.net/ ...

  10. python 基础篇 模块化

    在做项目的时候,虽然你不可能把全世界的代码都放到一个文件夹下,但是类似模块化的思想还是要有的--那就是以项目的根目录作为最基本的目录,所有的模块调用,都要通过根目录一层层向下索引的方式来 import ...