redis商品秒杀源码php,php结合redis 秒杀商品的详解

这篇文章主要介绍了关于php结合redis 秒杀商品的详解,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下

1 首先,一点点准备工作。

1.1建立商品表,订单表,并初始化数据

18f2915d511d3ea971609c3108a2df1a.png

订单表。

abaf165e92ea195cca0edafb444a94be.png

1.2 将商品数据写入到redis 队列中去。

例如编号1 商品有100件。 就往 goods_1 队列里写100个1 进去。例用pop 操作的原子性(扛并发) 后面购买时,买一个就pop 一个。//代码使用yii 框架,重点在思路,其它框架做少量调整即可。

$redis = self::createRedisObj(); //创建redis 对象,后面提供详细代码

$sql = "select * from sec_goods";

$rows = Yii::app()->db->createCommand($sql)->queryAll();

foreach( $rows as $key => $row ):

$goods_id = $row["id"];

$stock_avail = $row["stock_avail"];

$redis_key = "goods_".$goods_id;

for($i =0 ; $i< $stock_avail; $i++){

$redis->lpush($redis_key , 1);

}

echo $goods_id."llen is ".$redis->lLen($redis_key)."
";

endforeach;

建好后如下图。(真实情况下,后台可能出现库调整,需要对应的去同步redis 中的数据,实际项目中请留意,此处暂且不表)

0ba688dc29716fbd0185743a3d580c08.png

2 无redis时,常规的购买代码。//用随机值模拟客户,商品,单次购买份数

$uid = rand(1,10);

$amount = rand(1,5);

$goods_id = rand(1,6);

$time = time();

$this->buy($uid , $goods_id , $amount);

public function buy($uid , $goods_id , $amount){

//使用行锁.

try {

$trans = Yii::app()->db->beginTransaction();

$sql = "select stock_avail from sec_goods where id = $goods_id for update"; //

$stock_avail = Yii::app()->db->createCommand($sql)->queryScalar();

if( $stock_avail >= $amount ){ //份额足够。

$sn = date("YmdHis")."-".$uid."-".$goods_id.rand(1000,9999);

$sql = "insert into sec_order set sn = '$sn',user_id = $uid, goods_id = $goods_id, create_at = $time,num = $amount";

$bool = Yii::app()->db->createCommand($sql)->execute();

if( !$bool ){ throw new Exception("执行失败".$sql); }

$sql = "update sec_goods set stock_avail = stock_avail - $amount where id= $goods_id";

$bool = Yii::app()->db->createCommand($sql)->execute();

if( !$bool ){ throw new Exception("执行失败".$sql); }

}

$trans->commit();

return true;

} catch (Exception $e) {

//日志记录

$trans->rollback();

return false;

}

}

然后使用 apache 的 ab 小工具进行测试。

-n 代表请求次数 -c 代表单次并发多少个请求。

ab -n 1000 -c 100 http://xxx

(把上述代码中的事务去掉,再ab 跑时会出现爆单,超卖问题 详情点击)

由于使用了行锁 for update。 基于事务的隔离性,一定是顺序的执行,所以上述代码,也不会出现超卖爆单问题。(10件库存卖出11件),但这样的代码,有个性能问题,就是有多少次并发请求,就会往数据库请求多少次。脆弱的mysql 很快就崩掉了。

3 结redis的秒杀代码。

终于上正菜了。。。。//code 3.1

//用随机值模拟客户,商品,单次购买份数

$uid = rand(1,10);

$amount = rand(1,5);

$goods_id = rand(1,6);

$time = time();

//用redis 校验,此次用户是否可以买。(库存是否充足)

$redis = self::createRedisObj();

$redis_key = "goods_".$goods_id;

$len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。

if( $len == 0 ){ exit("抢光了!"); }

else if( $len < $amount){ exit("库存不足!"); }

//验证通过,开始pop 出队列。 pop 一个,相当于买一个。

for( $i =0 ; $i< $amount;$i++){

$redis->rPop( $redis_key );

}

$bool = $this->buy($uid , $goods_id , $amount);

if( !$bool ) { //如果购买失败,则把取出的redis 队列的数据,再压回去。(回充库存)

for( $i =0 ; $i< $amount;$i++){

$redis->lPush( $redis_key , 1);

}

}

//创建redis 对象的。

private static $_redis = null;

/**

* 创建一个redis 对象.

* @return Redis

*/

public static function createRedisObj(){ //2017-11-29 改为单例创建模式.

if( ! self::$_redis){

$redis = new Redis();

$host = “192.168.1.xx”;

$port = "6379";

$redis->connect($host,$port);

self::$_redis = $redis;

}

return self::$_redis;

}

注意一个小细节,通常redis 会结合框架做缓存。 上述例子中,请注意在创redis 对象时,再单独指定一个库。(redis 一般有9个可选),避免服务器清缓存时将数据清空。

致此,上述代码完成了一个基础版本。

------------------------------------------------------------------------------------------------

然而在现象中,会随运营需求不断的产生变化。

随便举一例。

1 产品希望单用户3秒内仅能买一次。 无商品

2 产品希望单用户单个商品最多限买5 件。

碰到这种情况

问题1 处理如下//用随机值模拟客户,商品,单次购买份数

$uid = rand(1,10);

$amount = rand(1,5);

$goods_id = rand(1,6);

$time = time();

$redis = self::createRedisObj();

单用户限3秒内仅允许请求一次///

$lock_key = "uTimeLimit_".$uid;

//按用户名编即可。 如果限用户针对指定商品,则lock_key 按uid+ goods_id 进行唯一编码

if( $redis->get($lock_key)){

$left_time = $redis->ttl($lock_key);

exit($expire ."秒内只允许 $tag 一次!请".$left_time."之后再尝试");

}else {

$redis->setEx($lock_key , $expire , "1" );

}

//

//用redis 校验,此次用户是否可以买。(库存是否充足)

$len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。

if( $len == 0 ){

exit("抢光了!");

}else if( $len < $amount){

exit("库存不足!");

}

// 后序购买流程。。。。。

问题2 修改如下。//用随机值模拟客户,商品,单次购买份数

$uid = rand(1,10);

$amount = rand(1,5);

$goods_id = rand(1,6);

$time = time();

$redis = self::createRedisObj();

单用户限5个处理///

//设一个hash 表, "user_buy" 然后 $uid . "_" . $goods_id 来记录购买的份数。

$already_num = $redis->hGet("user_buy",$uid."_".$goods_id)? $redis->hGet("user_buy",$uid."_".$goods_id)

:0; //求出已购买份额

if( $already_num +$amount > 5){ exit("单用户单个商品限购买5个");}

else{

$new_num = $already_num +$amount ;

$redis->hSet("user_buy",$uid."_".$goods_id , $new_num);

}

用redis 校验,此次用户是否可以买。(库存是否充足)

$len = $redis->lLen($redis_key); //求队列的长度,也就是商品的库存。

if( $len == 0 ){ exit("抢光了!"); }

else if( $len < $amount){ exit("库存不足!"); }// 后序购买流程。。。。。

//如果购买失败

$redis->hSet(

"user_buy",$uid."_".$goods_id ,

$redis->hGet("user_buy",$uid."_".$goods_id ) - $amount //失败时,则回复hash 表的数值

);

这两个问题处理完毕,其它类似问题见招拆招。 有心的读者可以发现,这类修改有共同之处,可以适当封装下代码,更好的复用。

相关推荐:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值