PHP商品秒杀计时实现(解决大流量方案)

2018年05月05日

阅读:478

PHP商品秒杀功能我们多半以整点或时间点为例子,这样对于php来说处理不复杂,但有一个问题就是如果流量大要如何来处理,下面我们一起来看看解决办法。

要求要有小时分钟秒的实时倒计时的显示,用户端修改日期时间不会影响到倒计时的正常显示(也就是以服务器时间为准)。其实这和很多的考试等系统的时间限制功能同样的要求。总不能用ajax每秒都获取服务器时间吧,所以实时倒计时一定要用javascript实现。这很简单,网上一大把的例子。

现在问题是解决用户端修改日期时间对我们的显示的影响。解决的办法是计算出用户端的时间和服务器的时间差,这样问题的完成解决了。这样只需要运行一次php,实时倒计时的时间就和服务器的时间同步了。

理论是同步的,但实际测试会有1秒的误差。(具体原因就是和网速有关,网速越快,误差就越小),但这决不会影响到我们上面的要求了。

注:秒杀时间从早上点到晚上10点。

Code 如下:

<?php
	//php的时间是以秒算。js的时间以毫秒算
	date_default_timezone_set('PRC'); 
	//date_default_timezone_set("Asia/Hong_Kong");//地区
	//配置每天的活动时间段 
	[html] view plain copy
	$starttimestr = "08:00:00";   
	$endtimestr = "22:00:00";  
	$starttime = strtotime($starttimestr);   
	$endtime = strtotime($endtimestr);   
	$nowtime = time();   
	if ($nowtime<$starttime){   
		die("活动还没开始,活动时间是:{$starttimestr}至{$endtimestr}");   
	}   
	$lefttime = $endtime-$nowtime; //实际剩下的时间(秒)   
?>
<script language="JavaScript">   
<!-- //   
var runtimes = 0;   
function GetRTime(){   
	var nMS = <?=$lefttime?>*1000-runtimes*1000;   
	var nH=Math.floor(nMS/(1000*60*60))%24;   
	var nM=Math.floor(nMS/(1000*60)) % 60;   
	var nS=Math.floor(nMS/1000) % 60;   
	document.getElementById("RemainH").innerHTML=nH;   
	document.getElementById("RemainM").innerHTML=nM;   
	document.getElementById("RemainS").innerHTML=nS;   
	if(nMS>5*59*1000&&nMS<=5*60*1000){   
		alert("还有最后五分钟!");   
	}   
	runtimes++;   
	setTimeout("GetRTime()",1000);   
}   
window.onload=GetRTime;   
// -->   
</script>
<h4><strong id="RemainH">XX</strong>:<strong id="RemainM">XX</strong>:<strong id="RemainS">XX</strong></h4>

上面看上没有问题但碰到流量大会出现一些数量不对的问题,如:大流量并发入库导致的库存负数的问题,我们知道数据库处理sql是一条条处理的,假设购买商品的流程是这样的:

sql1:查询商品库存

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

当没有并发时,上面的流程看起来是如此完美,假设同时两个人下单,而库存只有1个了,在sql1阶段两个人查询到的库存都是>0的,于是最终都执行了sql2,库存最后变为-1,超售了,要么补库存,要么等用户投诉吧。

解决这个问题比较流行的思路:

1.用额外的单进程处理一个队列,下单请求放到队列里,一个个处理,就不会有并发的问题了,但是要额外的后台进程以及延迟问题,不予考虑。
2.数据库乐观锁,大致的意思是先查询库存,然后立马将库存+1,然后订单生成后,在更新库存前再查询一次库存,看看跟预期的库存数量是否保持一致,不一致就回滚,提示用户库存不足。
3.根据update结果来判断,我们可以在sql2的时候加一个判断条件update ... where 库存>0,如果返回false,则说明库存不足,并回滚事务。
4.借助文件排他锁,在处理下单请求的时候,用flock锁定一个文件,如果锁定失败说明有其他订单正在处理,此时要么等待要么直接提示用户"服务器繁忙"

本文要说的是第4种方案,大致代码如下:

阻塞(等待)模式
[html] view plain copy
<?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);   
?>
关于悲观锁和乐观锁:

悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

别逗了好么

努力每一天,奋斗为明天。

文章 981 作品 25,341

热门作品

文章推荐

猜你喜欢

榜上有名

广告