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 相关文章推荐
php checkdate、getdate等日期时间函数操作详解
Mar 11 PHP
apache和php之间协同工作的配置经验分享
Apr 08 PHP
php-cli简介(不会Shell语言一样用Shell)
Jun 03 PHP
CodeIgniter基本配置详细介绍
Nov 12 PHP
PHP修改session_id示例代码
Jan 08 PHP
PHP连接MySQL的2种方法小结以及防止乱码
Mar 11 PHP
ThinkPHP实现多数据库连接的解决方法
Jul 01 PHP
PHP实现将浏览历史页面网址保存到cookie的方法
Jan 26 PHP
详解PHP数组赋值方法
Nov 07 PHP
浅析Yii2中GridView常见操作
Apr 22 PHP
PHP设计模式之工厂模式详解
Oct 24 PHP
php的扩展写法总结
May 14 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
sony ICF-2010 拆解与改装
2021/03/02 无线电
用Flash图形化数据(二)
2006/10/09 PHP
使用PHP提取视频网站页面中的FLASH地址的代码
2010/04/17 PHP
深入apache host的配置详解
2013/06/09 PHP
PHP学习笔记(一) 简单了解PHP
2014/08/04 PHP
PHP使用CURL模拟登录的方法
2015/07/08 PHP
CodeIgniter框架钩子机制实现方法【hooks类】
2018/08/21 PHP
ExtJS GridPanel 根据条件改变字体颜色
2010/03/08 Javascript
jquery对表单操作2
2011/04/06 Javascript
js函数的延迟加载实现代码
2012/10/11 Javascript
JS实现可改变列宽的table实例
2013/07/02 Javascript
javascript中的window.location.search方法简介
2013/09/02 Javascript
7个有用的jQuery代码片段分享
2015/05/19 Javascript
JavaScript暂停和继续定时器的实现方法
2016/07/18 Javascript
前端开发之CSS原理详解
2017/03/11 Javascript
Bootstrap实现的经典栅格布局效果实例【附demo源码】
2017/03/30 Javascript
Angular4表单验证代码详解
2017/09/03 Javascript
vue-cli脚手架引入图片的几种方法总结
2018/03/13 Javascript
微信小程序授权登录及解密unionId出错的方法
2018/09/26 Javascript
vue实现输入框的模糊查询的示例代码(节流函数的应用场景)
2019/09/01 Javascript
简单了解JavaScript sort方法
2019/11/25 Javascript
[05:26]TI10典藏宝瓶套装外观展示
2020/07/03 DOTA
numpy排序与集合运算用法示例
2017/12/15 Python
Python解析并读取PDF文件内容的方法
2018/05/08 Python
selenium+python实现1688网站验证码图片的截取功能
2018/08/14 Python
在Python中实现替换字符串中的子串的示例
2018/10/31 Python
Python中print和return的作用及区别解析
2019/05/05 Python
python获取磁盘号下盘符步骤详解
2019/06/19 Python
Python selenium模拟手动操作实现无人值守刷积分功能
2020/05/13 Python
Keras—embedding嵌入层的用法详解
2020/06/10 Python
中药专业毕业自荐书范文
2014/02/08 职场文书
离职感谢信怎么写
2015/01/22 职场文书
2015国庆节66周年演讲稿
2015/03/20 职场文书
税务会计岗位职责
2015/04/02 职场文书
MySQL系列之五 视图、存储函数、存储过程、触发器
2021/07/02 MySQL
Ruby GDBM操作简介及数据存储原理
2022/04/19 Ruby