php并发加锁示例


Posted in PHP onOctober 17, 2016

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

1 没有应用锁机制

1.1 财务支付简化版本代码

<!--?php 
/** 
 * pay.php 
 * 
 * 支付没有应用锁
 * 
 * Copy right (c) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2016/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操作人:

  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 类图设计如下

php并发加锁示例

2.2 php源码设计如下

LockSystem.php

<!--?php 
/** 
 * LockSystem.php 
 * 
 * php锁机制
 * 
 * Copy right (c) 2016
 * 
 * modification history: 
 * -------------------- 
 * 2016/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) 2016 
 * 
 * modification history: 
 * -------------------- 
 * 2016/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操作人:

  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 相关文章推荐
在线竞拍系统的PHP实现框架(二)
Oct 09 PHP
PHP中动态显示签名和ip原理
Mar 28 PHP
PHP中用header图片地址 简单隐藏图片源地址
Apr 09 PHP
怎样使用php与jquery设置和读取cookies
Aug 08 PHP
Opcache导致php-fpm崩溃nginx返回502
Mar 02 PHP
php根据某字段对多维数组进行排序的方法
Mar 07 PHP
php字符串操作针对负值的判断分析
Jul 28 PHP
PHP简单遍历对象示例
Sep 28 PHP
详解php框架Yaf路由重写
Jun 20 PHP
PHP实现APP微信支付的实例讲解
Feb 10 PHP
php app支付宝回调(异步通知)详解
Jul 25 PHP
win7 wamp 64位 php环境开启curl服务遇到的问题及解决方法
Sep 16 PHP
thinkPHP自动验证、自动添加及表单错误问题分析
Oct 17 #PHP
PHP CURL post数据报错 failed creating formpost data
Oct 16 #PHP
PHPStrom 新建FTP项目以及在线操作教程
Oct 16 #PHP
php根据数据id自动生成编号的实现方法
Oct 16 #PHP
PHP结合Ueditor并修改图片上传路径
Oct 16 #PHP
jquery+thinkphp实现跨域抓取数据的方法
Oct 15 #PHP
php组合排序简单实现方法
Oct 15 #PHP
You might like
PHP文本数据库的搜索方法
2006/10/09 PHP
php获取apk包信息的方法
2014/08/15 PHP
PHP可变变量学习小结
2015/11/29 PHP
Yii框架中使用PHPExcel的方法分析
2019/07/25 PHP
关于jQuery中.attr()和.prop()的问题探讨
2013/09/06 Javascript
jQuery下拉友情链接美化效果代码分享
2015/08/26 Javascript
JS动态增删表格行的方法
2016/03/03 Javascript
对jquery的ajax进行二次封装以及ajax缓存代理组件:AjaxCache详解
2016/04/11 Javascript
自制微信公众号一键排版工具
2016/09/22 Javascript
浅析jQuery操作select控件的取值和设值
2016/12/07 Javascript
详解小程序原生使用ES7 async/await语法
2018/08/06 Javascript
vue通过滚动行为实现从列表到详情,返回列表原位置的方法
2018/08/31 Javascript
JS滚轮控制图片缩放大小和拖动的实例代码
2018/11/20 Javascript
利用vue-i18n实现多语言切换效果的方法
2019/06/19 Javascript
VScode格式化ESlint方法(最全最好用方法)
2019/09/10 Javascript
vue-preview动态获取图片宽高并增加旋转功能的实现
2020/07/29 Javascript
[03:36]DOTA2完美大师赛coL战队趣味视频——我演你猜
2017/11/23 DOTA
Python输出汉字字库及将文字转换为图片的方法
2016/06/04 Python
python使用scrapy发送post请求的坑
2018/09/04 Python
Python实现去除列表中重复元素的方法总结【7种方法】
2019/02/16 Python
ubuntu 18.04搭建python环境(pycharm+anaconda)
2019/06/14 Python
python分布式计算dispy的使用详解
2019/12/22 Python
Python如何读写二进制数组数据
2020/08/01 Python
Django Admin后台模型列表页面如何添加自定义操作按钮
2020/11/11 Python
多视角3D逼真HTML5水波动画
2016/03/03 HTML / CSS
英国女鞋购物网站:Moda in Pelle
2019/02/18 全球购物
德国滑雪和户外用品网上商店:XSPO
2019/10/30 全球购物
某IT外企面试题-二分法求方程!看看大家的C++功底
2015/07/04 面试题
中医药大学毕业生自荐信
2013/11/08 职场文书
教师实习自我鉴定
2013/12/11 职场文书
2015年营销工作总结范文
2015/04/23 职场文书
家长会感言
2015/08/01 职场文书
熟背这些句子,让您的英语口语突飞猛进(135句)
2019/09/06 职场文书
解读MySQL的客户端和服务端协议
2021/05/10 MySQL
Spring Boot mybatis-config 和 log4j 输出sql 日志的方式
2021/07/26 Java/Android
前端框架ECharts dataset对数据可视化的高级管理
2022/12/24 Javascript