php并发加锁问题分析与设计代码实例讲解


Posted in PHP onFebruary 26, 2021

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

1 没有应用锁机制

1.1 财务支付简化版本代码

<!--?php 
/** 
 * pay.php 
 * 
 * 支付没有应用锁
 * 
 * Copy right (c) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2018/9/10, by CleverCode, Create 
 * 
 */
//用户支付
function pay($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
 return false;
 } 
 //取出总额
 $total = getUserLeftMoney($userId);
 //花费大于剩余
 if($money --> $total)
 {
 return false; 
 }
 //余额
 $left = $total - $money;
 //更新余额
 return setUserLeftMoney($userId,$left);
}
//取出用户的余额
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
 return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
 //$mysql = new mysql();//mysql数据库
 return $mysql->query($sql);
}
//更新用户余额
function setUserLeftMoney($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
 return false;
 } 
 $sql = "update user_account set account = ${money} where userid = ${userId}";
 //$mysql = new mysql();//mysql数据库
 return $mysql->execute($sql);
}
?>

1.2 问题分析

如果有两个操作人(p和m),都用用户编号100账户,分别在pc和手机端同时登陆,100账户总余额有1000,p操作人花200,m操作人花300。并发过程如下。

p操作人:

取出用户的余额1000。
支付后剩余 800 = 1000 - 200。
更新后账户余额800。

m操作人:

取出用户余额1000。
支付后剩余700 = 1000 - 300。
支付后账户余额700。
两次支付后,账户的余额居然还有700,应该的情况是花费了500,账户余额500才对。造成这个现象的根本原因,是并发的时候,p和m同时操作取到的余额数据都是1000。

2 加锁设计

锁的操作一般只有两步,一 获取锁(getLock);二是释放锁(releaseLock)。但现实锁的方式有很多种,可以是文件方式实现;sql实现;Memcache实现;根据这种场景我们考虑使用策略模式。

2.1 类图设计如下

php并发加锁问题分析与设计代码实例讲解

2.2 php源码设计如下

LockSystem.php

<!--?php 
/** 
 * LockSystem.php 
 * 
 * php锁机制
 * 
 * Copy right (c) 2018
 * 
 * modification history: 
 * -------------------- 
 * 2018/9/10, by CleverCode, Create 
 * 
 */
class LockSystem
{
 const LOCK_TYPE_DB = 'SQLLock';
 const LOCK_TYPE_FILE = 'FileLock';
 const LOCK_TYPE_MEMCACHE = 'MemcacheLock';
 private $_lock = null;
 private static $_supportLocks = array('FileLock', 'SQLLock', 'MemcacheLock'); 
 public function __construct($type, $options = array()) 
 {
 if(false == empty($type))
 {
 $this--->createLock($type, $options);
 }
 } 
 public function createLock($type, $options=array())
 {
 if (false == in_array($type, self::$_supportLocks))
 {
 throw new Exception("not support lock of ${type}");
 }
 $this->_lock = new $type($options);
 } 
 public function getLock($key, $timeout = ILock::EXPIRE)
 {
 if (false == $this->_lock instanceof ILock) 
 {
 throw new Exception('false == $this->_lock instanceof ILock'); 
 } 
 $this->_lock->getLock($key, $timeout); 
 }
 public function releaseLock($key)
 {
 if (false == $this->_lock instanceof ILock) 
 {
 throw new Exception('false == $this->_lock instanceof ILock'); 
 } 
 $this->_lock->releaseLock($key); 
 } 
}
interface ILock
{
 const EXPIRE = 5;
 public function getLock($key, $timeout=self::EXPIRE);
 public function releaseLock($key);
}
class FileLock implements ILock
{
 private $_fp;
 private $_single;
 public function __construct($options)
 {
 if (isset($options['path']) && is_dir($options['path']))
 {
 $this->_lockPath = $options['path'].'/';
 }
 else
 {
 $this->_lockPath = '/tmp/';
 }
 $this->_single = isset($options['single'])?$options['single']:false;
 }
 public function getLock($key, $timeout=self::EXPIRE)
 {
 $startTime = Timer::getTimeStamp();
 $file = md5(__FILE__.$key);
 $this->fp = fopen($this->_lockPath.$file.'.lock', "w+");
 if (true || $this->_single)
 {
 $op = LOCK_EX + LOCK_NB;
 }
 else
 {
 $op = LOCK_EX;
 }
 if (false == flock($this->fp, $op, $a))
 {
 throw new Exception('failed');
 }
 return true;
 }
 public function releaseLock($key)
 {
 flock($this->fp, LOCK_UN);
 fclose($this->fp);
 }
}
class SQLLock implements ILock
{
 public function __construct($options)
 {
 $this->_db = new mysql(); 
 }
 public function getLock($key, $timeout=self::EXPIRE)
 { 
 $sql = "SELECT GET_LOCK('".$key."', '".$timeout."')";
 $res = $this->_db->query($sql);
 return $res;
 }
 public function releaseLock($key)
 {
 $sql = "SELECT RELEASE_LOCK('".$key."')";
 return $this->_db->query($sql);
 }
}
class MemcacheLock implements ILock
{
 public function __construct($options)
 {
 $this->memcache = new Memcache();
 }
 public function getLock($key, $timeout=self::EXPIRE)
 { 
 $waitime = 20000;
 $totalWaitime = 0;
 $time = $timeout*1000000;
 while ($totalWaitime < $time && false == $this->memcache->add($key, 1, $timeout)) 
 {
 usleep($waitime);
 $totalWaitime += $waitime;
 }
 if ($totalWaitime >= $time)
 throw new Exception('can not get lock for waiting '.$timeout.'s.');
 }
 public function releaseLock($key)
 {
 $this->memcache->delete($key);
 }
}

3 应用锁机制

3.1 支付系统应用锁

<!--?php
/** 
 * pay.php 
 * 
 * 支付应用锁
 * 
 * Copy right (c) 2018 
 * 
 * modification history: 
 * -------------------- 
 * 2018/9/10, by CleverCode, Create 
 * 
 */
//用户支付
function pay($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
 return false;
 } 
 try
 {
 //创建锁(推荐使用MemcacheLock)
 $lockSystem = new LockSystem(LockSystem::LOCK_TYPE_MEMCACHE); 
 //获取锁
 $lockKey = 'pay'.$userId;
 $lockSystem--->getLock($lockKey,8);
 //取出总额
 $total = getUserLeftMoney($userId);
 //花费大于剩余
 if($money > $total)
 {
 $ret = false; 
 }
 else
 { 
 //余额
 $left = $total - $money;
 //更新余额
 $ret = setUserLeftMoney($userId,$left);
 }
 //释放锁
 $lockSystem->releaseLock($lockKey); 
 }
 catch (Exception $e)
 {
 //释放锁
 $lockSystem->releaseLock($lockKey); 
 }
}
//取出用户的余额
function getUserLeftMoney($userId)
{
 if(false == is_int($userId))
 {
 return 0;
 }
 $sql = "select account form user_account where userid = ${userId}";
 //$mysql = new mysql();//mysql数据库
 return $mysql->query($sql);
}
//更新用户余额
function setUserLeftMoney($userId,$money)
{
 if(false == is_int($userId) || false == is_int($money))
 {
 return false;
 } 
 $sql = "update user_account set account = ${money} where userid = ${userId}";
 //$mysql = new mysql();//mysql数据库
 return $mysql->execute($sql);
}
?>

3.2 锁分析

p操作人:

获取锁:pay100
取出用户的余额1000。
支付后剩余 800 = 1000 - 200。
更新后账户余额800。
释放锁:pay100

m操作人:

1、等待锁:pay100
2、获取锁:pay100
3、获取余额:800
3、支付后剩余500 = 800 - 300。
5、支付后账户余额500。
6、释放锁:pay100

两次支付后,余额500。非常完美了解决了并发造成的临界区资源的访问问题。

到此这篇关于php并发加锁问题分析与设计代码实例讲解的文章就介绍到这了,更多相关php并发加锁问题分析与设计内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

PHP 相关文章推荐
.htaccess文件保护实例讲解
Feb 06 PHP
discuz程序的PHP加密函数原理分析
Aug 05 PHP
解析php防止form重复提交的方法
Jul 01 PHP
php使用curl抓取qq空间的访客信息示例
Feb 28 PHP
使用php方法curl抓取AJAX异步内容思路分析及代码分享
Aug 25 PHP
PHP生成静态HTML页面最简单方法示例
Apr 09 PHP
PHP中Enum(枚举)用法实例详解
Dec 07 PHP
基于PHP微信红包的算法探讨
Jul 21 PHP
PHP基于ICU扩展intl快速实现汉字转拼音及按拼音首字母分组排序的方法
May 03 PHP
PHP以json或xml格式返回请求数据的方法
May 31 PHP
PHP获取HTTP body内容的方法
Dec 31 PHP
php使用redis的有序集合zset实现延迟队列应用示例
Feb 20 PHP
PHP内存溢出优化代码详解
Feb 26 #PHP
php自动加载代码实例详解
Feb 26 #PHP
PHP的重载使用魔术方法代码实例详解
Feb 26 #PHP
PHP解密支付宝小程序的加密数据、手机号的示例代码
Feb 26 #PHP
php中get_object_vars()在数组的实例用法
Feb 22 #PHP
MacOS下PHP7.1升级到PHP7.4.15的方法
Feb 22 #PHP
关于PhpStorm设置点击编辑文件自动定位源文件的实现方式
Dec 30 #PHP
You might like
PHP音乐采集(部分代码)
2007/02/14 PHP
php使用curl并发减少后端访问时间的方法分析
2016/05/12 PHP
PHP怎样用正则抓取页面中的网址
2016/08/09 PHP
jquery判断checkbox(复选框)是否被选中的代码
2010/10/20 Javascript
Js表格万条数据瞬间加载实现代码
2014/02/20 Javascript
jquery调取json数据实现省市级联的方法
2015/01/29 Javascript
jQuery插件实现大图全屏图片相册
2015/03/14 Javascript
详解JavaScript正则表达式中的global属性的使用
2015/06/16 Javascript
javascript中offset、client、scroll的属性总结
2015/08/13 Javascript
javascript超过容器后显示省略号效果的方法(兼容一行或者多行)
2016/07/14 Javascript
AngularJS入门教程之Helloworld示例
2016/12/25 Javascript
利用JavaScript对中文(汉字)进行排序实例详解
2017/06/18 Javascript
Node.js引入UIBootstrap的方法示例
2018/05/11 Javascript
js限制input只能输入有效的数字(第一个不能是小数点)
2018/09/28 Javascript
angular4+百分比进度显示插件用法示例
2019/05/05 Javascript
使用JS location实现搜索框历史记录功能
2019/12/23 Javascript
python 测试实现方法
2008/12/24 Python
Python实现国外赌场热门游戏Craps(双骰子)
2015/03/31 Python
python批量提取word内信息
2015/08/09 Python
Python if语句知识点用法总结
2018/06/10 Python
Python基于最小二乘法实现曲线拟合示例
2018/06/14 Python
浅析python标准库中的glob
2020/03/13 Python
学点简单的Django之第一个Django程序的实现
2021/02/24 Python
总结30个CSS3选择器
2017/04/13 HTML / CSS
微信浏览器左上角返回按钮拦截功能
2017/11/21 HTML / CSS
加拿大时尚床上用品零售商:QE Home | Quilts Etc
2018/01/22 全球购物
专注澳大利亚特产和新西兰特产的澳洲中文网:0061澳洲制造
2019/03/24 全球购物
汽车技术服务英文求职信范文
2014/01/02 职场文书
校本教研工作方案
2014/01/14 职场文书
村班子对照检查材料
2014/08/18 职场文书
党支部创先争优活动总结
2014/08/28 职场文书
学前教育专业求职信
2014/09/02 职场文书
党章学习心得体会2016
2016/01/14 职场文书
Django项目配置Memcached和Redis, 缓存选择哪个更有优势
2021/04/06 Python
还在手动盖楼抽奖?教你用Python实现自动评论盖楼抽奖(一)
2021/06/07 Python
Python函数对象与闭包函数
2022/04/13 Python