我们正常写一个购买商品:然后用ab测试下 ab -n 300 -c 100 -k http://laravel8.cn/api/Index/Ceshi
发现:库存变成了负数了。
public function ceshi(){
$info = DB::table('shop')->where("id","2")->get()->map(function ($value) {
return (array)$value;
})->toArray();
if($info[0]['shopNum'] > 0) {
sleep(1);
$result = DB::table('shop')->where("id", "2")->decrement('shopNum');
if($result){
echo '执行成功';
}
return;
}
echo '没有库存了';exit;
}
秒杀抢购时同时大量的用户同时下单并发量大才导致超卖。
解决思路:我们把商品放在redis里用redis 处理并发(官方测试redis每秒处理大概8万-10万)。把商品放在redis列表里,然后 判断 列表长度 是否大于0,如果大于0 就继续下单 。每下一单把队列 lpop出去一个 把商品信息、用户信息、订单号放在队列里。通过异步进程进行处理队列 进入mysql 表。
通过定时任务 把秒杀商品放入redis
public function insertShopRedis(){
$shopData = DB::table("shop")->get()->map(function ($value) {
return (array)$value;
})->toArray();
if(!empty($shopData)){
foreach($shopData as $k=>$v){
// 需要存入redis
for($i=1;$i<=$v['shopNum'];$i++){
Redis::lpush('shop'.$v['id'],$i);
}
Redis::setex('shopNum'.$v['id'],120,$v['shopNum']);
Redis::expire('shop'.$v['id'], 120);
}
echo '秒杀商品放入完毕';exit;
}else{
echo '没有要秒杀的商品';exit;
}
}
点击下单:
public function redisSeckill(Request $request){
$id = 2;//这里的商品id $request 拿过来的
$userId = rand(1,99); // userId 用户的id我随机生成的
$key = 'shop'.$id;
$result = Redis::exists($key);
if($result == false){
echo '秒杀时间已过';exit;
}
if(Redis::llen($key) <= 0){
echo '抱歉已无库存';exit;
}
if (Redis::llen('user_list') > Redis::get("shopNum".$id)) {
echo '抱歉队列大于商品的数量';exit;
}
// 用户信息存入队列里
$result = $this->requestUser($userId,$id);
if(!$result){
echo '抱歉你已经抢购过该商品了';exit;
}
$count = Redis::lpop($key);
if(!$count) {
echo '抱歉已无库存';exit;
}
// 测试直接更改表
$result = DB::table('shop')->where("id", "2")->decrement('shopNum');
if($result){
echo "购买成功";
}
return;
// 实际:商品、用户购买的信息进入队列
$arr['userId'] = $userId;
$arr['shopId'] = $id;
$arr['order_on'] = $this->orderNo();
$str = json_encode($arr);
Redis::lpush('order_list'.$id,$str);
echo $userId.'购买成功';exit;
}
// 需要对已经在队列的用户进行截止
private function requestUser($userId,$id)
{
$userArr = Redis::Lrange('user_list'.$id,0,-1);
if(!in_array($userId,$userArr)) {
Redis::lpush('user_list'.$id, $userId);
return true;
}
return false;
}
// 生成订单号
private function orderNo(){
return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
}