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】COOKIE和SESSION

    一. COOKIE(小甜点,小饼干) a) 生活中的实例: i. 大保健的会员卡(记录你的姓名.性别.ID号码.手机号……) ii. 超市的会员卡(记录你的姓名,性别,会员积分) b) PHP当中的实 ...

  2. ECSHOP数据表结构完整仔细说明教程 (http://www.ecshop119.com/ecshopjc-868.html)

    s_account_log //用户账目日志表 字段 类型 Null 默认 注释 log_id mediumint(8) 否   自增ID号 user_id mediumint(8) 否   用户登录 ...

  3. C语言 文件操作(一)

    #include<stdio.h> int main(){          FILE *fp = fopen("f:\\lanyue.txt","r&quo ...

  4. Vue-CLI 3.x 部署项目至生产服务器

    本文已同步到专业技术网站 www.sufaith.com, 该网站专注于前后端开发技术与经验分享, 包含Web开发.Nodejs.Python.Linux.IT资讯等板块. 本教程主要讲解的是 Vue ...

  5. @suppressWarnings("unchecked") java 中是什么意思 (一般放dao查询方法上)

    J2SE 提供的最后一个批注是 @SuppressWarnings.该批注的作用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默. 一点背景:J2SE 5.0 为 Java 语言增加 ...

  6. Java创建线程的三种形式的区别以及优缺点

    1.实现Runnable,Callable Callable接口里定义的方法有返回值,可以声明抛出异常. 继承Callable接口实现线程 class ThreadCall implements Ca ...

  7. ASE past project:interview & analysis

    采访往届ASE课程学员李潇,他所在的团队blog戳这里http://www.cnblogs.com/smart-code/ Q1:师兄你觉得在团队项目中,有哪些需要注意的事情? A1:团队合作吧.首先 ...

  8. Dae-Da-Lus小组idea集锦

    Dae-Da-Lus小组成员经过认真的思考,每一位同学都提出了自己对于Team Project的想法,暂时Mark在这里,以备查阅~ 曹士杰: 作为一个计算机专业的学生,我想我们应该是幸运的.计算机科 ...

  9. python成语接龙小游戏

    上一篇讲了小游戏的坑现在把源码放出来 #coding:utf-8 import string import pypinyin import sys import random print(" ...

  10. 详解 NIO流

    在观看本篇博文前,建议先观看本人博文 -- <详解 IO流> NIO流: 首先,本人来介绍下什么是NIO流: 概述: Java NIO ( New IO )是从 Java 1.4 版本开始 ...