PHP+Redis解决高并发下的秒杀(乐观锁思路)

抢购、秒杀是平常很常见的场景,面试的时候面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等。

抢购、秒杀实现很简单,但是有些问题需要解决,主要针对两个问题:
一、高并发对数据库产生的压力
二、竞争状态下如何解决库存的正确减少("超卖"问题)
第一个问题,对于PHP来说很简单,用缓存技术就可以缓解数据库压力,比如memcache,redis等缓存技术。
第二个问题就比较复杂点:

常规写法:
查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问题,导致库存量出现负数(超卖)。
例如
(1)A购买商品,查询库存为1,可以购买
(2)减少库存,提交订单

(1)和(2)之间执行是有时间的,这中间B也来购买这个商品,他查询出的库存也是1,之后提交订单,库存就会变为-1

例如
(1)A购买商品,查询库存为1,可以购买
(2)B购买商品,查询库存为1,可以购买(出现脏读)
(3)A提交订单,减少库存,库存减至0
(4)B提交订单,减少库存,库存减至-1

<?php
	$conn = mysql_connect("localhost","root","root");
	if(!$conn)
	{
	    echo "数据库连接失败";
	    exit;
	}
	mysql_select_db("test",$conn);
	$price = 10;
	$user_id = 1;
	$goods_id = 1;
	$sku_id = 11;
	$number = 1;
  
	//生成唯一订单号
	function build_order_no()
	{
	  return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
	}
	//记录日志
	function add_num_log($event,$type=0)
	{
	    global $conn;
	    $sql = "insert into ih_log(event,type)
	    values('$event','$type')";
	    mysql_query($sql,$conn);
	}
  
	//模拟下单操作
	//库存是否大于0
	$sql = "select number from ih_store where goods_id='$goods_id' and sku_id='$sku_id'";
	$rs = mysql_query($sql,$conn);
	$row = mysql_fetch_assoc($rs);
	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 = mysql_query($sql,$conn);
	      
	    //库存减少
	    $sql = "update ih_store set number=number-{$number} where sku_id='$sku_id'";
	    $store_rs = mysql_query($sql,$conn);
	    if(mysql_affected_rows())
	    {
	        add_num_log('库存减少成功');
	    }
	    else
	    {
	        add_num_log('库存减少失败');
	    }
	}
	else
	{
	    exit('库存不够');
	}

下面是redis的乐观锁思路
例如
(1)A购买商品,获取库存为2(版本1)
(2)B购买商品,获取库存为2(版本1)
(3)A提交订单,减少库存,比较版本,一致则执行,库存减至1(更新后的版本为2)
(4)B提交订单,减少库存,比较版本,不一致会阻止执行

<?php
	$redis = new redis();
	$redis->connect('127.0.0.1',6379);

	$redis->watch('number');//在提交时检测会不会被多人修改,版本是否一致
	//从redis中获得库存
	$number = $redis->get('number');
	if($number <= 0)
	{
		exit('库存不足');
	}

	$redis->multi();//标记一个事务块的开始
	$redis->decr('number');//redis内库存减少

	$result = $redis->exec();//结束,检测版本号,不一致会阻止执行
	if($result)
	(
		//mysql操作
	)

至于为什么不使用悲观锁和队列
1、不使用悲观锁,因为等待时间非常长,响应慢
2、不使用队列,因为并发量会让队列内存瞬间升高

以上就是php+redis实现秒杀的思路,实际的业务处理会更复杂,酌情参考。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值