视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
php怎样应对高并发
2020-11-27 15:20:08 责编:小采
文档
 高并发下的数据安全

我们知道在多线程写入同一个文件的时候,会出现“线程安全”的问题(多个线程同时运行同一段代码,如果每次运行结果和单线程运行的结果是一样的,结果和预期相同,就是线程安全的)。

如果是MySQL数据库,可以使用它自带的锁机制很好的解决问题,但是在大规模并发的场景中,是不推荐使用MySQL的。秒杀和抢购的场景中,最关键的问题,就是“超发”,如果在这方面控制不慎,会导致实际产生的订单比预售商品还多的问题。

超发的原因:(推荐学习:PHP编程从入门到精通)

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

值得注意的是:记得将库存字段number字段设为unsigned,当库存为0时,因为unsigned字段不能为负数,将会返回false

优化方案

优化1:使用MySQL的事务,锁住操作的行 BEGIN ; SELECT ... FOR UPDATE ; COMMIT ; ROLLBACK

<?php
	//优化方案1:使用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);
	}
	
	mysqli_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");
	}

优化2:文件锁的思路

对于日访问量不高或者说并发数不是很大的应用,用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失。

<?php
//优化方案2:使用非阻塞的文件排他锁
	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);

下载本文
显示全文
专题