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. 面试刷题37:微服务是什么?springcloud,springboot是什么?

    面试中被问到为什么要使用微服务架构?springcloud的核心组件有哪些? 拿我们国家的兵种来说,如何把战争这个单体架构微服务化,就是根据适用的场景,拆分出不同的兵种(微服务) 然后每个兵种之间通过 ...

  2. 【PHP】流程控制

    一.      顺序结构 a)    PHP的脚本从上到下,从左往右的执行顺序就是顺序结构! 二.      分支结构 a)    单一的分支结构 If ( 条件表达式 ) { 程序体: } 执行过程 ...

  3. Vue项目添加动态浏览器头部title

    0. 直接上 预览链接 + 效果图 Vue项目添加动态浏览器头部title 1. 实现思路 ( 1 ) 从路由router里面得到组件的title ( 2 ) title存vuex (本项目已经封装h ...

  4. floyd最小环&&模板

    floyd的核心代码: ;k<=n;k++){ ;i<=n;i++){ ;j<=n;j++){ dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j] ...

  5. 墨者学院靶场:uWSGI(CVE-2018-7490)路径遍历漏洞复现

    0x01漏洞简介 uWSGI是一款Web应用程序服务器,它实现了WSGI.uwsgi和http等协议.uWSGI 2.0.17之前版本中存在路径遍历漏洞,该漏洞源于程序没有正确的处理DOCUMENT_ ...

  6. AJ学IOS(28)UI之Quartz2D简单介绍

    AJ分享,必须精品 iOS开发UI篇—Quartz2D简单介绍 什么是Quartz2D Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统 Quartz 2D能完成的工作: 绘制图形 : ...

  7. B. 复读机的力量

    我们规定一个人是复读机当且仅当他说的每一句话都是复读前一个人说的话. 我们规定一个人是复读机当且仅当他说的每一句话都是复读前一个人说的话. 我们规定一个人是复读机当且仅当他说的每一句话都是复读前一个人 ...

  8. CSS属性中的display属性浅谈;

    首先我们要知道什么是块级元素和行内元素有什么区别: 承接上篇文章:(浅谈HTML和body标签) 块级元素:浏览器解析为独占一行的元素(例如:div.table.ul等.),浏览器会在该元素的前后显示 ...

  9. Java优秀教程

    1.java中局部变量是在栈上分配的: 2.数组是储存在堆上的对象,可以保存多个同类型变量: 3.在Java语言中,所有的变量在使用前必须声明. 4.局部变量没有默认值,所以局部变量被声明后,必须经过 ...

  10. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之九(四十五)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...