laravel redis秒杀功能

1.初始化:
秒杀商品,将商品以list数据类型存入redis(每个数量为一个元素);

2.购买:
1)购买用户入队列,如果用户队列长度超过指定的排队长度,则返回排队数过多
2)如果用户队列长度小于指定的排队长度,然后生成订单,减去库存。下单完成

<?php

namespace App\Http\Controllers;

use App\Models\Goods;
use App\Models\Orders;
use App\Services\RedisLock;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;

class MiaoshaController extends Controller
{
	//排队人数
    private $listNumber = 50;

    /**
     * 实现秒杀
     */
    public function secondsKill(Request $request)
    {
        try {
            $user_data = $request->only(['user_id', 'goods_id']);
            if ( !$user_data ) {
                return $this->failed('数据不能为空');
            }
            $goodsId = $user_data['goods_id'];
            $userId = $user_data['user_id'];
            #访问用户入队接口
            $user_list = $this->requestUser($userId);

            if ( !$user_list ) {
                return $this->failed('排队数大于商品总数:'.Redis::llen('user_list'));
            }
            #消费商品,从队列中取出商品
            $count = Redis::lpop('goods_store:' . $goodsId);
            if(!$count) {
                return $this->failed('商品抢光了');
            }
            #进入redis锁
            $redisLock = new RedisLock($userId);
            $lock = $redisLock->lock();
            if ($lock) {
                #最后进入数据库操作(每次固定消费1个)
                $mysql_data = $this->storeOrder($userId, $count, '1');
                if ( !$mysql_data ) {
                    $redisLock->unlock();
                    return $this->failed('生成订单失败');
                } else {
                    #关闭锁
                    $redisLock->unlock();
                    return $this->success('抢购成功');
                }
            }
            return $this->success('生成订单失败');

        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 将商品加入redis
     */
    public function storageGoods(Request $request)
    {
        try {
            #查询商品
            $resutl = Goods::where('id', $request->goods_id)->select(['store','id'])->first();

            $store = $resutl->store;
            $res = Redis::llen('goods_store:' . $resutl->id);
            $count = $store - $res;
            for($i = 0;$i < $count; $i++){
                Redis::lpush('goods_store:' . $resutl->id, $resutl->id);
            }
            return $this->success('加入成功'.$count);
        } catch (Exception $e) {
            throw $e;
        }
    }

    /**
     * 将用户也存入队列中(就是将访问请求数据)(此处没有进行用户过滤,同一个用户进行多次请求也会进入队列)
     */
    private function requestUser($userId)
    {
        $res = Redis::llen('user_list');
        #判断排队数
        if ($res = Redis::llen('user_list') > $this->listNumber) {
            // return '排队数大于商品总数';
            return false;
        }
        #添加数据
        Redis::lpush('user_list', $userId);
        return true;
    }



    /*
     *下单
     */
    private function storeOrder($user_id, $goods_id, $number)
    {
        try {
            #开启事务
            DB::beginTransaction();
            #查询库存sharedLock()共享锁,可以读取到数据,事务未提交不能修改,直到事务提交
            #lockForUpdate()不能读取到数据
            $resutl = Goods::where(['id'=>$goods_id])->lockForUpdate()->first();
            #添加订单
            if ( $resutl ) {
                $resutl_order = Orders::create([
                    'user_id' => $user_id,
                    'goods_id' => $goods_id,
                    'goods_number' => $number,
                    'ordersn' => $this->buildOrderNo(),
                    'price' => $resutl->price,
                ]);
                #减少库存
                $resutl_update = Goods::where('id',$goods_id)->where('store', '>', 0)->decrement('store');
                #将用户从队列里面弹出,允许下一个用户进来
                Redis::rpop('user_list');
                if ($resutl_order->id > 0 && $resutl_update > 0) {
                    DB::commit();
                    return true;
                }
            }

            DB::rollBack();
            return false;

        } catch(Exception $e) {
            throw $e;
        }
    }

    /**
     * 生成唯一订单号
     */
    private function buildOrderNo(){
        return date('ymd').substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
    }
}

  转载:https://blog.csdn.net/xiayu204575/article/details/106719845

posted @ 2022-05-19 16:20  /折磨  阅读(192)  评论(0编辑  收藏  举报