关于php 高并发解决的一点思路


Posted in PHP onApril 16, 2017

我的思路如下(伪代码):

sql1:查询商品库存

if(库存数量 > 0)
{
//生成订单...
sql2:同时库存-1
}

当没有并发时,上面的流程看起来是再正常不过了,假设同时两个人下单,而库存只有1个了,在sql1阶段两个人查询到的库存都是>0的,于是最终都执行了sql2,库存最后变为-1,超售了,这不是我们想要的结果吧。

解决这个问题比较流行的思路我总结了下:
1.用额外的单进程处理一个队列,下单请求放到队列里,一个个处理,就不会有并发的问题了,但是要额外的开启后台进程以及延迟问题,这里暂不予考虑。这里我可使用消息队列,我们常用到Memcacheq、Radis。 比如:有100张票可供用户抢,那么就可以把这100张票放到缓存中,读写时不要加锁。 当并发量大的时候,可能有500人左右抢票成功,这样对于500后面的请求可以直接转到活动结束的静态页面。进去的500个人中有400个人是不可能获得商品的。所以可以根据进入队列的先后顺序只能前100个人购买成功。后面400个人就直接转到活动结束页面。当然进去500个人只是举个例子,至于多少可以自己调整。而活动结束页面一定要用静态页面,不要用数据库。这样就减轻了数据库的压力。
2.mysql乐观锁,意思是比如总库存是2,抢购事件提交时,立马将库存+1,那么此时库存是3,然后订单生成后,在更新库存前再查询一次库存(因为订单生成理所当然库存-1,但是先不急,再查一次库存返回结果是3),看看跟预期的库存数量(这里预期的库存是3)是否保持一致,不一致就回滚,提示用户库存不足。这里说道悲观锁,可能有朋友会问,那一定有乐观锁了吧??这里我就浅谈下我所了解的悲观与乐观锁了

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。

悲观锁(Pessimistic Lock)

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。

乐观锁(Optimistic Lock)

乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现:

1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操作,得到new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
  // 乐观锁获取成功,操作完成
} else {
  // 乐观锁获取失败,回滚并重试
}

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。好吧,在此唠叨总结下这两个锁:

总结

  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方

3.根据update结果来判断,我们可以在sql2的时候加一个判断条件update table set 库存=xxx where 库存>0,如果返回false,则说明库存不足,并回滚事务。
4.借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"

大致代码如下:
阻塞(等待)模式

<?php
$fp = fopen("lock.txt", "w+");
if(flock($fp,LOCK_EX))  //锁定当前指针,,,
{
 //..处理订单
 flock($fp,LOCK_UN);
}
fclose($fp);
?>

非阻塞模式

<?php
$fp = fopen("lock.txt", "w+");
if(flock($fp,LOCK_EX | LOCK_NB))
{
 //..处理订单
 flock($fp,LOCK_UN);
}
else
{
 echo "系统繁忙,请稍后再试";
}
 
fclose($fp);
?>

5.如果是分布式集群服务器,就需要一个或多个队列服务器 小米和淘宝的抢购还是有稍许不同的,小米重在抢的那瞬间,抢到了名额,就是你的,你就可以下单结算。而淘宝则重在付款的时候的过滤,做了多层过滤,比如要卖10件商品,他会让大于10的用户抢到,在付款的时候再进行并发过滤,一层层的减少一瞬间的并发量。

6.使用redis锁 product_lock_key 为票锁key 当product_key存在于redis中时,所有用户都可以进入下单流程。 当进入支付流程时,首先往redis存放sadd(product_lock_key, “1″),如果返回成功,进入支付流程。如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行sadd操作。

当然类似于淘宝双11的疯抢架构远远比我说滴这些复杂多啦....更多解决方案需要不停滴去实战中获取心得....大家有好的解决思路清随时共享留言哈

PHP 相关文章推荐
PHP的FTP学习(四)
Oct 09 PHP
php模板中出现空行解决方法
Mar 08 PHP
解析php5配置使用pdo
Jul 03 PHP
PHP生成Gif图片验证码
Oct 27 PHP
PHP FTP操作类代码( 上传、拷贝、移动、删除文件/创建目录)
May 10 PHP
完善CodeIgniter在IDE中代码提示功能的方法
Jul 19 PHP
ThinkPHP中自定义目录结构的设置方法
Aug 15 PHP
PHP创建word文档的方法(平台无关)
Mar 29 PHP
PHP实现将多个文件压缩成zip格式并下载到本地的方法示例
May 23 PHP
thinkPHP5框架auth权限控制类与用法示例
Jun 12 PHP
在Ubuntu 18.04上安装PHP 7.3 7.2和7.0的方法
Apr 09 PHP
php反射学习之不用new方法实例化类操作示例
Jun 14 PHP
PHP无限极分类函数的实现方法详解
Apr 15 #PHP
基于ThinkPHP实现的日历功能实例详解
Apr 15 #PHP
php修改数组键名的方法示例
Apr 15 #PHP
php自定义函数实现统计中文字符串长度的方法小结
Apr 15 #PHP
php+mysql+jquery实现简易的检索自动补全提示功能
Apr 15 #PHP
php+mysql+ajax实现单表多字段多关键词查询的方法
Apr 15 #PHP
PHP快速推送微信模板消息
Apr 14 #PHP
You might like
用ADODB来让PHP操作ACCESS数据库的方法
2006/12/31 PHP
require(),include(),require_once()和include_once()区别
2008/03/27 PHP
WordPress判断用户是否登录的代码
2011/03/17 PHP
php中批量修改文件后缀名的函数代码
2011/10/23 PHP
PHP 中使用explode()函数切割字符串为数组的示例
2017/05/06 PHP
PHP7 其他修改
2021/03/09 PHP
javascript 动态调整图片尺寸实现代码
2009/12/28 Javascript
js 键盘记录实现(兼容FireFox和IE)
2010/02/07 Javascript
了解Javascript的模块化开发
2015/03/02 Javascript
javascript判断css3动画结束 css3动画结束的回调函数
2015/03/10 Javascript
js模仿java的Map集合详解
2016/01/06 Javascript
浅谈Jquery中Ajax异步请求中的async参数的作用
2016/06/06 Javascript
Kendo Grid editing 自定义验证报错提示的解决方法
2016/11/18 Javascript
关于jQuery中fade(),show()起始位置的一点小发现
2017/04/25 jQuery
BootstrapTable加载按钮功能实例代码详解
2017/09/22 Javascript
AngularJS实现注册表单验证功能
2017/10/16 Javascript
详解React Native 采用Fetch方式发送跨域POST请求
2017/11/15 Javascript
cdn模式下vue的基本用法详解
2018/10/07 Javascript
微信小程序的mpvue框架快速上手指南
2019/05/15 Javascript
Vue状态模式实现窗口停靠功能(灵动、自由, 管理后台Admin界面)
2020/03/06 Javascript
[56:47]Ti4 循环赛第三日 iG vs Liquid
2014/07/12 DOTA
[02:19]2014DOTA2国际邀请赛 专访820少年们一起去追梦吧
2014/07/14 DOTA
python实现简单socket程序在两台电脑之间传输消息的方法
2015/03/13 Python
Python随机生成数模块random使用实例
2015/04/13 Python
Python3+Pycharm+PyQt5环境搭建步骤图文详解
2019/05/29 Python
解决webdriver.Chrome()报错:Message:'chromedriver' executable needs to be in Path
2019/06/12 Python
Opencv图像处理:如何判断图片里某个颜色值占的比例
2020/06/03 Python
一文彻底解决HTML5页面中长按保存图片功能
2019/06/10 HTML / CSS
Laura Geller官网:美国彩妆品牌
2018/12/29 全球购物
农村婚礼证婚词
2014/01/08 职场文书
马丁路德金演讲稿
2014/05/19 职场文书
小学生感恩父母演讲稿
2014/08/28 职场文书
大学本科生职业生涯规划书范文
2014/09/14 职场文书
绍兴鲁迅故居导游词
2015/02/09 职场文书
走进毛泽东观后感
2015/06/04 职场文书
2015初中团支部工作总结
2015/07/21 职场文书