laravel+redis实现的抢购(待优化)

<?php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Log;
use Mockery\Exception;
use \Redis;
use App\Jobs\SeckillGoods;


class SeckillController extends Controller
{

    protected $uid;  // 用户id
    protected $goodsId = 10086;   // 秒抢商品
    protected $goodsNum = 100;     // 数量限制
    protected $redis;             // redis实例
    protected $lockPrefix = 'lock:';
    protected $lockExpireTime = 10;
    protected $goodsCacheKeyTag = [
        'goodsNumCacheKey' => 'goods_num',
        'isSetGoods' => 'is_set_goods_status',
        'hadGetGoodsMembers' => 'had_get_goods_members',
        'limitUserNumKey' => 'limit_user_num'
    ];

    public function __construct()
    {
        $redis = new Redis;
        $redis->connect('127.0.0.1');
        $redis->select(5);
        $this->redis = $redis;
        $this->uid = intval( request('uid'));
    }
    
    /**
     * 加锁, 并发下的原子性操作
     */
    public function limitAccessFrequency()
    {
        return $this->redis->set($this->lockPrefix . $this->uid , true , array('nx', 'ex' => $this->lockExpireTime));
    }

    /**
     *  销毁锁
     */
    public function __destruct()
    {
        $this->redis->del($this->lockPrefix . $this->uid);
    }


    /**
     *  开始抢购
     * @return array
     */
    public function seckill()
    {
        $uid = $this->uid;
        if (empty($uid)) {
            return [
                'status' => false,
                'msg' => '参数错误'
            ];
        }

       // 加锁,限制用户访问速率
        if( empty($this->limitAccessFrequency())){
            return [
                'status' => false,
                'msg' => '请求频繁~~~~~~~~~~~'
            ];
        }

        // 进入队列抢购
        return $this->interQueue($uid);

    }


    /**
     *  进入抢购队列
     * @param $uid
     * @return array
     */
    public function interQueue($uid)
    {
        $redis = $this->redis;
        $goodsNumCacheKey = $this->goodsCacheKeyTag['goodsNumCacheKey'];
        $hadGetGoodsMembers = $this->goodsCacheKeyTag['hadGetGoodsMembers'];

        // 限制进入队列的人数
        $limitStatus = $this->limitUser($uid, $this->goodsNum);
        if (!$limitStatus['status']) {
            return $limitStatus;
        }

        try {
            // 开始redis事务: 抢购到的商品数量加1 ; 事务可能失败
            $redis->watch($goodsNumCacheKey);
            $res = $redis->multi()
                ->incr($goodsNumCacheKey)
                ->exec();

            if ($res[0] <= $this->goodsNum) {
                $redis->sAdd($hadGetGoodsMembers, $uid);  // 抢购成功写入用户名单
            } else {
                return [
                    'status' => false,
                    'msg' => $uid . '来晚一步啦, 商品已经抢空3'
                ];
            }
        } catch (Exception $e) {
            return [
                'status' => false,
                'msg' => $uid . '系统繁忙,请稍后再试'
            ];
        }
        // 异步写数据
        $data = [
            'goods_id' => $this->goodsId,
            'uid' => $uid,
            'goods_num' => 1,
            'created_at' => intval(LARAVEL_START)
        ];
        SeckillGoods::dispatch($data);

        return ['status' => true, 'msg' => $uid . '抢购成功'];
    }


    /**
     *  限制用户流
     * @param $uid
     * @param int $num
     * @return array
     */
    public function limitUser($uid, $goodsN = 100)
    {
        // 限定人数+50进入
        $num = $goodsN + 50;
        $limitUserNumKey = $this->goodsCacheKeyTag['limitUserNumKey'];
        $hadGetGoodsMembers = $this->goodsCacheKeyTag['hadGetGoodsMembers'];
        $goodsNumCacheKey = $this->goodsCacheKeyTag['goodsNumCacheKey'];
        $redis = $this->redis;


        // 每个用户最多抢1件商品
        $hGGM = $redis->sIsMember($hadGetGoodsMembers, $uid);
        if ($hGGM) {
            return [
                'status' => false,
                'msg' => $uid . '每个人只能抢购1件商品'
            ];
        }

        // 超出限定人数说明已经抢购完
        $res = $redis->incr($limitUserNumKey);
        if ($res > $num) {
            return [
                'status' => false,
                'msg' => $uid . '来晚一步啦, 商品已经抢空1'
            ];
        }

        // 商品已抢完
        $goodsNum = $redis->get($goodsNumCacheKey);
        if ($goodsNum >= $goodsN) {
            return [
                'status' => false,
                'msg' => $uid . '来晚一步啦, 商品已经抢空2'
            ];
        }

        // TODO 查数据库验证用户身份

        return ['status' => true];
    }


}

猜你喜欢

转载自blog.csdn.net/mathphp/article/details/84336650