PHP 实现积分兑换和大转盘抽奖功能,防超卖

本文介绍了如何在PHP项目中利用Redis实现悲观锁机制,确保积分兑换过程中避免商品超卖。通过预先锁定库存、使用redis队列和锁来控制并发,实现在高并发场景下的库存保护。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前情提要

前段时间帮客户做了一个线上会议网站,网站实际运营 2 个多月,正常参会用户注册量大概有1万多。

网站其中有两个模块一个是积分兑换,一个是大转盘抽奖。用户通过浏览网站指定的会议视频或赞助商信息后可获得积分,积分达到一定程度后可参与这两个模块的兑换或抽奖。

因为兑换的商品是人民币等值的京东卡(50元,100元,200元三个商品),可能会被刷,所以在程序开发前就考虑了如何不产生并发超卖的情况。

如何不发生超卖现象?

根据网站的注册数量以及实际兑换/抽奖需求进行评估之后,我们对这两个模块的开发初步制定了如下思路(因两个模块基本相同,以下仅以积分兑换模块作为说明)。

  • 将给用户兑换的商品提前锁定库存并写入 redis 队列(此处为调度任务脚本自动执行)

  • 在符合兑换要求的用户进行兑换商品时,读取 redis 队列,并在下单时(兑换可以视为下订单流程)使用 redis 锁避免 mysql 的重复写入。

代码如何实现

根据上述已明确的思路,开发工作分为两个步骤进行。

1.事先将需要兑换商品及库存写入 redis 队列

<?php
// 以下仅给出核心代码,其他自定义判断可根据需求自行编写
    
// 查找商品及库存
$date = '2022-04-01'
$goods = Goods::query()->select(['id', 'goods_store'])->where('goods_date', $date)->get();

// 将查询到的商品及库存写入redis队列
if($goods ->isNotEmpty()){
	foreach ($goods as $key =>$value){
		$store = $value['goods_store'];
		$llen = app('redis')->llen('goods_store:' . $value['id']);
		$count = $store - $llen;

        // 将每个商品的库存写入各自商品的key,写入的vaule是商品id,为了后续直接取出后使用该id来走下单流程
		for($i = 0; $i < $count; $i++){
			app('redis')->lpush('goods_store:' . $value['id'], $value['id']);
		}
	}
	return;
}

2.实现用户积分兑换的下单流程,使用 redis 锁并写入数据库

<?php
// 其他积分兑换的条件判断省略
    
// 消费商品,从队列中取出商品
$count = app('redis')->lpop('goods_store:' . $goods_id);

// 使用兑换用户的id为key,使用redis加锁下单,redis锁有很多种实现方式,其他方式可自行实现
$lock = $this->lock($user->id);
if($lock){
    // 这里是下单写表逻辑,$count变量其实就是兑换商品的id,详见上一步。下单写表代码省略。
	$mysql_data = $this->storeOrder($user, $count);
	if(!$mysql_data){
		$this->unlock($user->id);
        throw new Exception("error");
	}else{
		$this->unlock($user->id);
		return $this->success();
    }
}
Redis 锁的实现(悲观锁)
// 加锁,给锁设置过期时间, 避免锁释放失败导致死锁现象
public function lock($id){
	return Redis::set("points:lock", $id, "nx", "ex", 10);
}

// 解锁,使用lua脚本减少网络开销,原子性操作等
public function unlock($id)
{
	$script =  <<<'LUA'
if redis.call('get', KEYS[1]) == ARGV[1]
then
    return redis.call('del', KEYS[1])
else
    return 0
end
LUA;

	return Redis::eval($script, 1, "points:lock", intval($id));
}
结论

上述实现的防止商品超卖逻辑中 PHP 代码如果直接使用需在 Laravel 框架中运行, 其他框架或架构需简单修改后方可运行。

在上述逻辑实现用户兑换商品成功后,需要的做的还有很多,比如检查商品的正确性减少商品在数据库中的库存获取商品对应的奖品信息(这里是京东卡券),最后将奖券下发给用户

这段代码最终的奖券邮件发送逻辑采用的是 Laravel 中的事件系统(队列)实现的,该实现步骤可大致参考我的另一篇文章:使用 Laravel 事件系统与消息通知实现给新注册用户发送通知邮件

同时,在使用上述代码运行积分兑换的一周多的时间里,积分有被刷的情况,但没有发生过一次超卖现象,证明该逻辑在一定流量的并发下对防止商品超卖的情况相对稳定有效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吹落的树叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值