
php 高并发解决(商城抢购问题2)
可以基于例如MemcacheQ等这样的消息队列,具体的实现方案这么表述吧
比如有100张票可供用户抢,那么就可以把这100张票放到缓存中,读写时不要加锁。 当并发量大的时候,可能有500人左右抢票成功,这样对于500后面的请求可以直接转到活动结束的静态页面。进去的500个人中有400个人是不可能获得商品的。所以可以根据进入队列的先后顺序只能前100个人购买成功。后面400个人就直接转到活动结束页面。当然进去500个人只是举个例子,至于多少可以自己调整。而活动结束页面一定要用静态页面,不要用数据库。这样就减轻了数据库的压力。
方案二:当有多台服务器时,可以采用分流的形式实现
假设有m张票, 有n台产品服务器接收请求,有x个请求路由服务器随机转发
直接给每台产品服务器分配 m/n张票
每台产品服务器内存做计数器,比如允许m/n*(1+0.1)个人进来。
当内存计数器已满:
后面进的人, 直接跳到到转到活动结束的静态页面,
通知路由服务器,不在路由到这台服务器(这个值得商讨)。
所有产品服务器进来的m/n*(1+0.1)个人再全部转发到一台付款服务器上,进入付款环节,看谁手快了,这时候人少,加锁什么的就简单的。
方案三、如果是单服务器,可以使用Memcache锁来实现
product_key 为票的key
product_lock_key 为票锁key
当product_key存在于memcached中时,所有用户都可以进入下单流程。
当进入支付流程时,首先往memcached存放add(product_lock_key, “1″),
如果返回成功,进入支付流程。
如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行add操作。
方案四、借助文件排他锁
在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"
本文要说的是第4种方案,大致代码如下
阻塞(等待)模式:
方案五、管道锁与库存控制
入口我们看做一个管道(tube),假定每一个购票者给大概5分钟可以完成购票,那么超过5分钟后或者已经买到票,就会让出位置让后面的人进入售票大厅,当然这是理想化想法,但至少我们把压力控制在我们可以处理的能力之内,要不然服务器宕机,欲哭无泪。
创建管道:
实现管道锁
提前创建好管道,在并发流程中:
到此为止,如我们所愿,我们已经控制了并发,我们已经把我们能力范围内可以控制的人放进来了,其它人只能继续等待...
剩下就是库存问题了,库存控制原则:
库存数不能<0,否则问题大了,商品超卖了,用户下了单,但没货发给卖家,那你就等着投诉吧。
请看下面一个sql(数据库是InnoDB):
重点看sqlPart部分,这里巧妙和利用mysql的行级锁,把库存不可能为0的控制权交给了mysql来处理,此乃点晴之笔。
再加一个高并发参考文档:
发布日期:2021-05-10 08:32:25
浏览次数:25
分类:原创文章
本文共 3031 字,大约阅读时间需要 10 分钟。
方案一:使用消息队列来实现可以基于例如MemcacheQ等这样的消息队列,具体的实现方案这么表述吧
比如有100张票可供用户抢,那么就可以把这100张票放到缓存中,读写时不要加锁。 当并发量大的时候,可能有500人左右抢票成功,这样对于500后面的请求可以直接转到活动结束的静态页面。进去的500个人中有400个人是不可能获得商品的。所以可以根据进入队列的先后顺序只能前100个人购买成功。后面400个人就直接转到活动结束页面。当然进去500个人只是举个例子,至于多少可以自己调整。而活动结束页面一定要用静态页面,不要用数据库。这样就减轻了数据库的压力。
方案二:当有多台服务器时,可以采用分流的形式实现
假设有m张票, 有n台产品服务器接收请求,有x个请求路由服务器随机转发
直接给每台产品服务器分配 m/n张票
每台产品服务器内存做计数器,比如允许m/n*(1+0.1)个人进来。
当内存计数器已满:
后面进的人, 直接跳到到转到活动结束的静态页面,
通知路由服务器,不在路由到这台服务器(这个值得商讨)。
所有产品服务器进来的m/n*(1+0.1)个人再全部转发到一台付款服务器上,进入付款环节,看谁手快了,这时候人少,加锁什么的就简单的。
方案三、如果是单服务器,可以使用Memcache锁来实现
product_key 为票的key
product_lock_key 为票锁key
当product_key存在于memcached中时,所有用户都可以进入下单流程。
当进入支付流程时,首先往memcached存放add(product_lock_key, “1″),
如果返回成功,进入支付流程。
如果不成,则说明已经有人进入支付流程,则线程等待N秒,递归执行add操作。
方案四、借助文件排他锁
在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"
本文要说的是第4种方案,大致代码如下
阻塞(等待)模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?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 ); ?> |
方案五、管道锁与库存控制
入口我们看做一个管道(tube),假定每一个购票者给大概5分钟可以完成购票,那么超过5分钟后或者已经买到票,就会让出位置让后面的人进入售票大厅,当然这是理想化想法,但至少我们把压力控制在我们可以处理的能力之内,要不然服务器宕机,欲哭无泪。
创建管道:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * 创建管道 默认30个入口 * @param string $tubeName * @param int $max * @return bool */ static public function createTube( $tubeName , $max = 30) { if (! $beanstalk = Common::getBeanstalkHandle()) return false; $beanstalk ->use_tube( $tubeName ); $i = 0; while ( $i < $max ) { $beanstalk ->put(1); $i ++; } return true; } |
实现管道锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /** * 采用管道方式进行加锁,以限制同时进行某个流程的并发数 * @param string $name 管道名称 * @param int $delay 出管理延迟时间默认为5秒 * @return bool */ public function tubeLock( $name , $delay = 5) { $beanstalk = Common::getBeanstalkHandle(); $beanstalk ->watch( $name ); $return = false; $job = $beanstalk ->reserve_with_timeout(); if ( $job [ 'id' ] > 0) $return = $beanstalk ->release( $job [ 'id' ], 1024, $delay ); $beanstalk ->ignore( $name ); return $return ; } |
提前创建好管道,在并发流程中:
1 2 | $tubeName = 'tube_' . $goods [ 'id' ]; if (Common::getLockHandle()->tubeLock( $tubeName ) == false) return false; //如果管理已经加锁,直接返回 |
到此为止,如我们所愿,我们已经控制了并发,我们已经把我们能力范围内可以控制的人放进来了,其它人只能继续等待...
剩下就是库存问题了,库存控制原则:
库存数不能<0,否则问题大了,商品超卖了,用户下了单,但没货发给卖家,那你就等着投诉吧。
请看下面一个sql(数据库是InnoDB):
1 2 3 4 5 6 7 8 9 10 11 | /** * * 更新库存 * @param int $num * @param int $id */ public function updateQuantity( $num , $<span style= "background-color: rgb(255, 255, 255); " >goodsid</span>) { $sqlPart = $num < 0 ? ' AND quantity >= ' . abs ( $num ) : '' ; $sql = 'UPDATE %s SET quantity = quantity + ?, sale_num = sale_num - ? WHERE id = ?' . $sqlPart ; return $this ->_update( $sql , array ( intval ( $num ), intval ( $num ), intval ( $goodsid )); } |
重点看sqlPart部分,这里巧妙和利用mysql的行级锁,把库存不可能为0的控制权交给了mysql来处理,此乃点晴之笔。
再加一个高并发参考文档:
发表评论
最新留言
不错!
[***.144.177.141]2025年04月07日 05时23分06秒
关于作者

喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
JAVA变量和运算符
2021-05-10
常见状态码
2021-05-10
重定向
2021-05-10
08-springmvc-异常解析器
2021-05-10
杂谈: 记一次深夜发版经历
2021-05-10
在select后面嵌套子查询
2021-05-10
表的复制和批量插入
2021-05-10
MYISAM存储引擎
2021-05-10
练习题第一道
2021-05-10
什么情况必须使用 statement
2021-05-10
账号转账演示事务
2021-05-10
HDML BS结构和CS结构介绍
2021-05-10
Object类:jDK类库的根类
2021-05-10
java中的集合回顾-collections工具类进行一个集合排序
2021-05-10
maven maven知识点回顾
2021-05-10
VS VS导入opencv的配置文件到Debug文件后还是无法导入库函数
2021-05-10
idea创建工程时错误提醒的是architectCatalog=internal
2021-05-10
E - Another Postman Problem FZU - 2038
2021-05-10
力扣 1658. 将 x 减到 0 的最小操作数
2021-05-10
图解redis(二)
2021-05-10