首页 » 网站建设 » qpsphp代码优化技巧_秒杀抢购思路以及高并发下数据安然

qpsphp代码优化技巧_秒杀抢购思路以及高并发下数据安然

访客 2024-11-19 0

扫一扫用手机浏览

文章目录 [+]

那么,我们的Web系统的理论峰值QPS为(空想化的打算办法):

20500/0.1 = 100000 (10万QPS)

qpsphp代码优化技巧_秒杀抢购思路以及高并发下数据安然

在高并发的实际场景下,机器都处于高负载的状态,在这个时候均匀相应韶光会被大大增加。

qpsphp代码优化技巧_秒杀抢购思路以及高并发下数据安然
(图片来自网络侵删)

就Web做事器而言,他打开了越多的连接进程,CPU须要处理的高下文切换也越多,额外增加了CPU的花费,然后就直接导致均匀相应韶光增加。
因此上述的MaxClient数目,要根据CPU、内存等硬件成分综合考虑,绝对不是越多越好。
可以通过Apache自带的ab来测试一下,取一个得当的值。
然后,我们选择内存操作级别的存储的Redis,在高并发的状态下,存储的相应韶光至关主要。
网络带宽虽然也是一个成分,不过,这种要求数据包一样平常比较小,一样平常很少成为要求的瓶颈。
负载均衡成为系统瓶颈的情形比较少,在这里不做谈论哈。

那么问题来了,假设我们的系统,在5w/s的高并发状态下,均匀相应韶光从100ms变为250ms(实际情形,乃至更多):

20500/0.25 = 40000 (4万QPS)

于是,我们的系统剩下了4w的QPS,面对5w每秒的要求,中间相差了1w。

举个例子,高速路口,1秒钟来5部车,每秒通过5部车,高速路口运作正常。
溘然,这个路口1秒钟只能通过4部车,车流量仍旧依旧,结果必定涌现大塞车。
(5条车道忽然变成4条车道的觉得)

同理,某一个秒内,20500个可用连接进程都在满负荷事情中,却仍旧有1万个新来要求,没有连接进程可用,系统陷入到非常状态也是预期之内。

其实在正常的非高并发的业务场景中,也有类似的情形涌现,某个业务要求接口涌现问题,相应韶光极慢,将全体Web要求相应韶光拉得很长,逐渐将Web做事器的可用连接数占满,其他正常的业务要求,无连接进程可用。

更恐怖的问题是,是用户的行为特点,系统越是不可用,用户的点击越频繁,恶性循环终极导致“雪崩”(个中一台Web机器挂了,导致流量分散到其他正常事情的机器上,再导致正常的机器也挂,然后恶性循环),将全体Web系统拖垮。

重启与过载保护

如果系统发生“雪崩”,贸然重启做事,是无法办理问题的。
最常见的征象是,启动起来后,急速挂掉。
这个时候,最好在入口层将流量谢绝,然后再将重启。
如果是redis/memcache这种做事也挂了,重启的时候须要把稳“预热”,并且很可能须要比较长的韶光。

秒杀和抢购的场景,流量每每是超乎我们系统的准备和想象的。
这个时候,过载保护是必要的。
如果检测到系统满负载状态,谢绝要求也是一种保护方法。
在前端设置过滤是最大略的办法,但是,这种做法是被用户“千夫所指”的行为。
更得当一点的是,将过载保护设置在CGI入口层,快速将客户的直接要求返回

高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会存现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,便是线程安全的)。
如果是MySQL数据库,可以利用它自带的锁机制很好的办理问题,但是,在大规模并发的场景中,是不推举利用MySQL的。
秒杀和抢购的场景中,还有其余一个问题,便是“超发”,如果在这方面掌握不慎,会产生发送过多的情形。
我们也曾经听说过,某些电商搞抢购活动,买家成功拍下后,商家却不承认订单有效,谢绝发货。
这里的问题,大概并不一定是商家巧诈,而是系统技能层面存在超发风险导致的。

超发的缘故原由

假设某个抢购场景中,我们一共只有100个商品,在末了一刻,我们已经花费了99个商品,仅剩末了一个。
这个时候,系统发来多个并发要求,这批要求读取到的商品余量都是99个,然后都通过了这一个余量判断,终极导致超发。

在上面的这个图中,就导致了并发用户B也“抢购成功”,多让一个人得到了商品。
这种场景,在高并发的情形下非常随意马虎涌现。

优化方案1将库存字段number字段设为unsigned,当库存为0时,由于字段不能为负数,将会返回false

<?php//优化方案1:将库存字段number字段设为unsigned,当库存为0时,由于字段不能为负数,将会返回falseinclude('./mysql.php');$username = 'wang'.rand(0,1000);//天生唯一订单function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0,$username){ global $conn; $sql="insert into ih_log(event,type,usernma) values('$event','$type','$username')"; return mysqli_query($conn,$sql);}function insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number){ global $conn; $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price,username,number) values('$order_sn','$user_id','$goods_id','$sku_id','$price','$username','$number')"; return mysqli_query($conn,$sql);}//仿照下单操作//库存是否大于0$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' ";$rs=mysqli_query($conn,$sql);$row = $rs->fetch_assoc(); if($row['number']>0){//高并发下会导致超卖 if($row['number']<$number){ return insertLog('库存不足',3,$username); } $order_sn=build_order_no(); //库存减少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id' and number>0"; $store_rs=mysqli_query($conn,$sql); if($store_rs){ //天生订单 insertOrder($order_sn,$user_id,$goods_id,$sku_id,$price,$username,$number); insertLog('库存减少成功',1,$username); }else{ insertLog('库存减少失落败',2,$username); } }else{ insertLog('库存不足',3,$username); }?>优化方案2悲观锁思路

办理线程安全的思路很多,可以从“悲观锁”的方向开始谈论。

悲观锁,也便是在修正数据的时候,采取锁定状态,排斥外部要求的修正。
碰着加锁的状态,就必须等待。

虽然上述的方案的确办理了线程安全的问题,但是,别忘却,我们的场景是“高并发”。
也便是说,会很多这样的修正要求,每个要求都须要等待“锁”,某些线程可能永久都没有机会抢到这个“锁”,这种要求就会去世在那里。
同时,这种要求会很多,瞬间增大系统的均匀相应韶光,结果是可用连接数被耗尽,系统陷入非常。

利用MySQL的事务,锁住操作的行

<?php//优化方案2:利用MySQL的事务,锁住操作的行include('./mysql.php');//天生唯一订单号function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0){ global $conn; $sql="insert into ih_log(event,type) values('$event','$type')"; mysqli_query($conn,$sql);}//仿照下单操作//库存是否大于0mysqli_query($conn,"BEGIN"); //开始事务$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id' FOR UPDATE";//此时这条记录被锁住,其它事务必须等待这次事务提交后才能实行$rs=mysqli_query($conn,$sql);$row=$rs->fetch_assoc();if($row['number']>0){ //天生订单 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs=mysqli_query($conn,$sql); //库存减少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs=mysqli_query($conn,$sql); if($store_rs){ echo '库存减少成功'; insertLog('库存减少成功'); mysqli_query($conn,"COMMIT");//事务提交即解锁 }else{ echo '库存减少失落败'; insertLog('库存减少失落败'); }}else{ echo '库存不足'; insertLog('库存不足'); mysqli_query($conn,"ROLLBACK");}?>乐不雅观锁思路

乐不雅观锁( Optimistic Locking ) 相对悲观锁而言,乐不雅观锁机制采纳了更加宽松的加锁机制。
悲观锁大多数情形下依赖数据库的锁机制实现,以担保操作最大程度的独占性。
但随之而来的便是数据库性能的大量开销,特殊是对长事务而言,这样的开销每每无法承受。
而乐不雅观锁机制在一定程度上办理了这个问题。
乐不雅观锁,大多是基于数据版本( Version )记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本办理方案中,一样平常是通过为数据库表增加一个 “version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录确当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

示例

如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的根本上进行修正时(如变动用户帐户余额),如果采撤消极锁机制,也就意味着全体操作过 程中(从操作员读出数据、开始修端正至提交修正结果的全过程,乃至还包括操作 员中途去煮咖啡的韶光),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情形将导致若何的后果。

乐不雅观锁机制在一定程度上办理了这个问题。
乐不雅观锁,大多是基于数据版本 ( Version )记录机制实现。
何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本办理方案中,一样平常是通过为数据库表增加一个 “version” 字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。
此时,将提交数据的版本数据与数据库表对应记录确当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对付上面修正用户帐户信息的例子而言,假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

1 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。

2 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。

3 操作员 A 完成了修正事情,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版今年夜于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。

4 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时创造,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不知足 “ 提交版本必须大于记录当前版本才能实行更新 “ 的乐不雅观锁策略,因此,操作员 B 的提交被驳回。

这样,就避免了操作员 B 用基于 version=1 的旧数据修正的结果覆盖操作员A 的操作结果的可能。

优点

从上面的例子可以看出,乐不雅观锁机制避免了长事务中的数据库加锁开销(操作员 A和操作员 B 操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。

缺陷

须要把稳的是,乐不雅观锁机制每每基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐不雅观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的掌握,因此可能会造成脏数据被更新到数据库中。
在系统设计阶段,我们该当充分考虑到这些情形涌现的可能性,并进行相应调度(如将乐不雅观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新路子,而不是将数据库表直接对外公开)。

但是,综合来说,这是一个比较好的办理方案。

有很多软件和做事都“乐不雅观锁”功能的支持,例如Redis中的watch便是个中之一。
通过这个实现,我们担保了数据的安全。

Redis中的watch

<?php$redis = new redis(); $result = $redis->connect('127.0.0.1', 6379); echo $mywatchkey = $redis->get("mywatchkey");$rob_total = 100; //抢购数量if($mywatchkey<=$rob_total){ $redis->watch("mywatchkey"); $redis->multi(); //在当前连接上启动一个新的事务。
//插入抢购数据 $redis->set("mywatchkey",$mywatchkey+1); $rob_result = $redis->exec(); if($rob_result){ $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),$mywatchkey); $mywatchlist = $redis->hGetAll("watchkeylist"); echo "抢购成功!
<br/>"; echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>"; echo "用户列表:<pre>"; var_dump($mywatchlist); }else{ $redis->hSet("watchkeylist","user_".mt_rand(1, 9999),'meiqiangdao'); echo "手气不好,再抢购!
";exit; }}?>
优化方案3FIFO行列步队思路

那好,那么我们轻微修正一下上面的场景,我们直接将要求放入行列步队中的,采取FIFO(First Input First Output,前辈先出),这样的话,我们就不会导致某些要求永久获取不到锁。
看到这里,是不是有点强行将多线程变成单线程的觉得哈。

然后,我们现在办理了锁的问题,全部要求采取“前辈先出”的行列步队办法来处理。
那么新的问题来了,高并发的场景下,由于要求很多,很可能一瞬间将行列步队内存“撑爆”,然后系统又陷入到了非常状态。
或者设计一个极大的内存行列步队,也是一种方案,但是,系统处理完一个行列步队内要求的速率根本无法和猖獗涌入行列步队中的数目比较。
也便是说,行列步队内的要求会越积累越多,终极Web系统均匀相应时候还是会大幅低落,系统还是陷入非常。

附:RabbitMQ行列步队利用 -> https://my.oschina.net/wangjie404/blog/819141

优化方案4文件锁的思路

对付日IP不高或者说并发数不是很大的运用,一样平常不用考虑这些!
用一样平常的文件操作方法完备没有问题。
但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就随意马虎造成数据丢失

利用非壅塞的文件排他锁

<?php//优化方案4:利用非壅塞的文件排他锁include ('./mysql.php');//天生唯一订单号function build_order_no(){ return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);}//记录日志function insertLog($event,$type=0){ global $conn; $sql="insert into ih_log(event,type) values('$event','$type')"; mysqli_query($conn,$sql);}$fp = fopen("lock.txt", "w+");if(!flock($fp,LOCK_EX | LOCK_NB)){ echo "系统繁忙,请稍后再试"; return;}//下单$sql="select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";$rs = mysqli_query($conn,$sql);$row = $rs->fetch_assoc();if($row['number']>0){//库存是否大于0 //仿照下单操作 $order_sn=build_order_no(); $sql="insert into ih_order(order_sn,user_id,goods_id,sku_id,price) values('$order_sn','$user_id','$goods_id','$sku_id','$price')"; $order_rs = mysqli_query($conn,$sql); //库存减少 $sql="update ih_store set number=number-{$number} where sku_id='$sku_id'"; $store_rs = mysqli_query($conn,$sql); if($store_rs){ echo '库存减少成功'; insertLog('库存减少成功'); flock($fp,LOCK_UN);//开释锁 }else{ echo '库存减少失落败'; insertLog('库存减少失落败'); }}else{ echo '库存不足'; insertLog('库存不足');}fclose($fp); ?>

标签:

相关文章

哇嘎兼容协议,构建万物互联的未来生态

随着信息技术的飞速发展,万物互联已经成为未来社会发展的趋势。在这个背景下,哇嘎兼容协议作为一种新型的通信协议,应运而生。本文将围绕...

网站建设 2024-12-27 阅读0 评论0

新疆IT企业蓬勃发展,引领西部经济新潮流

随着我国西部大开发的深入推进,新疆作为丝绸之路经济带核心区,其经济发展日新月异。在众多产业中,新疆IT企业如雨后春笋般涌现,成为推...

网站建设 2024-12-27 阅读0 评论0

旅游IT人才,引领智慧旅游新风尚

随着互联网技术的飞速发展,旅游业也迎来了前所未有的变革。旅游IT人才作为连接旅游产业与信息技术的桥梁,扮演着至关重要的角色。本文将...

网站建设 2024-12-27 阅读0 评论0

发电机CMS,智慧能源管理的新篇章

随着我国能源需求的不断增长,发电行业面临着巨大的挑战。为了提高发电效率,降低能源消耗,我国开始大力发展智慧能源管理系统。其中,发电...

网站建设 2024-12-27 阅读0 评论0

可爱符号的魅力,温暖心灵,点亮生活

可爱语言符号作为一种独特的文化现象,逐渐渗透到人们的日常生活中。从表情包到可爱动物,从卡通形象到网络用语,这些充满童趣的符号在传递...

网站建设 2024-12-27 阅读0 评论0