php并发加锁
CleverCode在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误。下面CleverCode将分析一个财务支付锁的问题。
1 没有应用锁机制
1.1 财务支付简化版本代码
- <?php
- /**
- * pay.php
- *
- * 支付没有应用锁
- *
- * Copy right (c) 2016 http://blog.csdn.net/CleverCode
- *
- * 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 类图设计如下
2.2 php源码设计如下
LockSystem.php
- <?php
- /**
- * LockSystem.php
- *
- * php锁机制
- *
- * Copy right (c) 2016 http://blog.csdn.net/CleverCode
- *
- * 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 http://blog.csdn.net/CleverCode
- *
- * 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并发加锁的更多相关文章
- ZooKeeper 分布式锁 Curator 源码 03:可重入锁并发加锁
前言 在了解了加锁和锁重入之后,最需要了解的还是在分布式场景下或者多线程并发加锁是如何处理的? 并发加锁 先来看结果,在多线程对 /locks/lock_01 加锁时,是在后面又创建了新的临时节点. ...
- php并发加锁示例
在工作项目中,会遇到一些php并发访问去修改一个数据问题,如果这个数据不加锁,就会造成数据的错误.下面我将分析一个财务支付锁的问题.希望对大家有所帮助. 1 没有应用锁机制 1.1 财务支付简化版本代 ...
- PHP_MySQL高并发加锁事务处理
1.背景: 现在有这样的需求,插入数据时,判断test表有无username为‘mraz’的数据,无则插入,有则提示“已插入”,目的就是想只插入一条username为‘mraz’的记录. 2.一般程序 ...
- Java并发(9)- 从同步容器到并发容器
引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的, ...
- 【整理】互联网服务端技术体系:高性能之并发(Java)
分而合之,并行不悖. 综述入口见:"互联网应用服务端的常用技术思想与机制纲要" 引子 并发,就是在同一时间段内有多个任务同时进行着.这些任务或者互不影响互不干扰,或者共同协作来完成 ...
- C# 设计模式巩固 - 单例模式
前言 设计模式的文章很多,所以此文章只是为了巩固一下自己的基础,说的不详细请见谅. 介绍 - 单例模式 官方定义:确保一个类只有一个实例,并提供一个全局访问点. 通俗定义:就是一个类只有一个单个实例. ...
- Java之架构(0) - 架构之路
软件架构作为一个概念,体现在技术和业务两个方面. 从技术角度来说:软件架构随着技术的革新不断地更新其内容,软件架构建立于当前技术和一些基本原则的基础之上. 先说一些基本原则: 分层原则:分层是为了降低 ...
- 为什么我要选择erlang+go进行server架构(2)
原创文章,转载请注明出处:server非业余研究http://blog.csdn.net/erlib 作者Sunface 为什么我要选择Erlang呢? 一.erlang特别适合中小团队创业: erl ...
- AQS系列(六)- Semaphore的使用及原理
前言 Semaphore也是JUC包中一个用于并发控制的工具类,举个常用场景的例子:有三台电脑五个人,每个人都要用电脑注册一个自己的账户,这时最开始只能同时有三个人操作电脑注册账户,这三个人中有人操作 ...
随机推荐
- 微信小程序H5预览页面框架(二维码不隐藏)
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- java编写规范
编码规范 转载于:https://www.cnblogs.com/ftl1012/p/javaCode.html 1 前言为确保系统源程序可读性,从而增强系统可维护性,java编程人员应具有基本类似的 ...
- 程序员的 Ubuntu 19.10 配置与优化指南
原文地址:程序员的 Ubuntu 19.10 配置与优化指南 0x00 环境 CPU: Intel Core i9-9900k GPU: GeForce RTX 2070 SUPER RAM: DDR ...
- EL表达式 -- 比较不错
EL表达式 EL 全名为Expression Language EL 语法很简单,它最大的特点就是使用上很方便.接下来介绍EL主要的语法结构: ${sessionScope.user.sex} 所有E ...
- Spring温习(1)--最基础的示例
Spring温习(1)--最基础的示例 博客分类: 框架-Spring专栏 SpringXMLBeanWebDAO 从现在开始,我将从Spring为起点,逐步复习几大框架各方面的知识,以便今后查看使用 ...
- AJ学IOS 之tableView的下拉放大图片的方法
AJ分享,必须精品 一:效果 tableview下拉的时候上部分图片放大会 二:代码 直接上代码,自己研究吧 #import "NYViewController.h" //图片的高 ...
- 3d模型一般怎么导入到到Threejs中使用
这是我之前做的一个demo,导入的3d模型文件是obj格式的,需要使用OBJLoader和MTLLoader, mtl文件用于描述多边形可视面貌的材质如果你可以导出obj.mtl文件的话,那么就可以使 ...
- python超实用的30 个简短的代码片段(三)
Python是目前最流行的语言之一,它在数据科学.机器学习.web开发.脚本编写.自动化方面被许多人广泛使用. 它的简单和易用性造就了它如此流行的原因. 如果你正在阅读本文,那么你或多或少已经使用过P ...
- A. Number Theory Problem
题目大意:计算小于2^n,且满足2^k-1并且是7的倍数的个数 思路:优先打表,数据不大,1e5,然后求个前n项和 #include<bits/stdc++.h> using namesp ...
- nginx配置虚拟主机、反向代理和负载均衡
为了实现这个功能,需要修改nginx的配置文件,将nginx.conf清理一下,使结构更清晰. worker_processes ; events { worker_connections ; } h ...