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. MyBatis(八):高级结果映射

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出一遍就懂!b站搜索狂神说或点击下面链接 https://space.bilibili.com/95256449?spm_id_from=33 ...

  2. (29)ASP.NET Core3.1 Swagger(OpenAPI)

    1.什么是Swagger/OpenAPI? Swagger是一个与语言无关的规范,用于描述REST API.因为Swagger项目已捐赠给OpenAPI计划,所以也叫OpenAPI.它允许计算机和人员 ...

  3. 适用于小白的 python 快速入门教程

    文章更新于:2020-02-17 按照惯例,需要的文件附上链接放在文首 文件名:python-3.7.6-amd64.exe 文件大小:25.6 M 下载链接:https://www.lanzous. ...

  4. 【Debug记录】Exeption thrown by glCreateVertexArrays

    继在机场丢失笔记本后又一大灾难--小组项目无法在老电脑上运行. 位置:glCreateVertexArrays函数 报错:Exception thrown at 0x00000000 in Clien ...

  5. Linux 压缩备分篇(一 备份数据)

    备份文件                dump dump: -S                    仅列出待备份数据需要多少磁盘空间才能够备份完毕 -u                    将 ...

  6. PHPDocumentor2.8.5 安装,使用及快速上手

    PHPDocumentor当前版本是phpDocumentor-2.8.5.tgz 关于PHPDocumentor有什么用,还有其历史,我就不介绍了,直接进入正题.老版本的叫PHPDoc,从1.0开始 ...

  7. java 字符串截取 - 最后带上mysql字符串截取比较

    Java中的substring()方法有两个方法的重载,一个带一个参数的,一个带两个参数的. 第一种写法: substring(n);//从索引是n的字符开始截取,条件(n>=0,n<字符 ...

  8. Tomcat5启动流程与配置详解

    标签:配置 tomcat 休闲 职场 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://zhangjunhd.blog.51cto. ...

  9. fork()系统调用的理解

    系统调用fork()用于创建一个新进程.我们可以通过下面的代码来理解,最好是能自己敲一遍运行验证. ​#include<stdio.h> #include<stdlib.h> ...

  10. qt creator源码全方面分析(4-1)

    目录 d指针和q指针 简单示例 q指针 QObject和QObjectPrivate qtcreator中的变体1 qtcreator中的变体2 小结 d指针和q指针 我们在类成员名称和使用d指针中, ...