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. PHP获取所有扩展及扩展下的所有函数签名生成php.snippet

    <?php $ext_info = array(); $modules = get_loaded_extensions(); foreach ($modules as $module) { $f ...

  2. golang--深入简出,带你用golang的反射撸一个公用后台查询方法

    一些基本方法 本篇不会介绍反射的基本概念和原理等,会从每个常用的方法入手,讲解一些基本和进阶用法,反射不太适合在业务层使用,因为会几何倍的降低运行速度,而且用反射做出来的程序健壮度不高,一旦一个环节没 ...

  3. mpvue微信小程序怎么写轮播图,和官方微信代码的差别

    目前用mpvue很多第三方的ui库是引入不了的,因为它不支持含有dom操作. 那我们要做轮播图的话一个是手写另外一个就是用小程序的swiper组件了: 官方代码: <swiper indicat ...

  4. 抽签小程序,妈妈再也不用担心谁洗碗(分配任务)了,so easy

    背景 今天谁炒菜,谁洗碗,谁买菜...啊,Boss说用抽签吧,于是有了下图 这样存在作弊的问题(记住棍子特征,谁先,谁后抽等等)于是有了这个抽签小程序(当然小程序我一个人控制,我想不想作弊看心情了) ...

  5. 中阶d03.3 JDBC_CURD_Util --- 使用 junit执行单元测试(增删改查)

    1.单元测试环境准备 https://www.cnblogs.com/longesang/p/11399010.html 2.测试 3.结果返回 4.代码 新建一个test目录统一存放测试案例 查: ...

  6. Java序列化机制中的类版本问题 serialVersionUID的静态字段 含义

    Java序列化机制中的类版本问题 分类: [Java 基础]2014-10-31 21:13 480人阅读 评论(0) 收藏 举报   目录(?)[+]       原文地址:http://yanwu ...

  7. 【Java】抽象类、接口

    什么是抽象类? 特点: - 抽象类几乎普通类一样,除了不能实例化 - 不能实例化不代表没有构造器,依然可以声明构造器,便于子类实例化调用 - 具有抽象方法的类,一定是抽象类 abstract 抽象的 ...

  8. Python - 和我聊Python节目最新一期介绍 - 257期:使用超级电脑,Python,射电天文学知识来探索银河系

    今天,给大家简单介绍和我聊Python的最新一期节目,第257期:使用超级电脑,Python,射电天文学知识来探索银河系. 听着标题就觉得高大上,是的,我也是这么认为的.这次请的嘉宾来头很大,来自国际 ...

  9. javascript实例教程使用canvas技术模仿echarts柱状图

    canvas 画布是HTML5中新增的标签,可以通过js操作 canvas 绘图 API在网页中绘制图像. 百度开发了一个开源的可视化图表库ECharts,功能非常强大,可以实现折线图.柱状图.散点图 ...

  10. [yii2] 实现所有action方法之前执行一段代码或者方法

    我做的是在执行任何方法之前,验证用户登陆状态! 其实就是在controller中写beforeaction()方法, 然后我的方案就是做一个基类,然后让你所有控制器继承你的基类, 如果控制器的基类用_ ...