redis简单秒杀设计

方案:Redis队列

原理:pop操作是原子的,即使有很多用户同时到达,也是依次执行

1.将商品库存加入队列
    /**
     * 商品库存放入redis
     * @param string $goods_id 商品ID
     * @param int $num 商品数量
     * @param false $is_clear 是否清除旧库存
     * @return bool
     */
    public function goods_spu_redis($goods_id, $num, $is_clear=false)
    {
        $cache_name = 'cache_ms_goods_id_'.$goods_id;
        $redis = RedisClass::getRedisConn();

        //是否先清除该商品原先库存
        if($is_clear)
        {
            while ($is_clear)
            {
                $is_clear = $redis->lPop($cache_name);
            }
        }

        //库存入队
        for($i=0; $i<$num; $i++)
        {
            $redis->lPush($cache_name,1);
        }

        //库存数量
        $spu = $redis->lLen($cache_name);

        $redis->close();
        return true;
    }
2.下单判断库存数量
    /**
     * 判断秒杀库存
     * @param array $sku_data 商品数组:[{"goods_id":"1","num":"2"},{"goods_id":"2","num":"3"}]
     * @return bool
     */
    protected function is_spu_ms($sku_data)
    {
        $is_true = true;
        $redis = RedisClass::getRedisConn();

        foreach ($sku_data as $v)
        {
            $cache_name = 'cache_ms_goods_id_'.$v['goods_id'];

            $spu = $redis->lLen($cache_name);
            if($spu < $v['num'])
            {
                //库存不足
                return  false;
            }
        }

        $redis->close();
        return $is_true;
    }
3.减库存
    /**
     * 秒杀减库存
     * @param array $sku_data 商品数组:[{"goods_id":"1","num":"2"},{"goods_id":"2","num":"3"}]
     * @return array|bool
     * @throws PDOException
     */
    public function reduce_spu($sku_data)
    {
        $this->orderM->startTrans();

        $redis = RedisClass::getRedisConn();
        $pipe = $redis->multi(\Redis::PIPELINE);
        
        try
        {
            foreach ($sku_data as $v)
            {
                //redis减库存
                $cache_name = CACHE_MS_NAME.$v['goods_id'];
                for ($i=0; $i<$v['num']; $i++)
                {
                    $pipe->lPop($cache_name);
                }

                //数据库减库存
                $this->msGoodsM->where(MsGoodsM::cl_id, $v['goods_id'])->setDec(MsGoodsM::cl_spu, $v['num']);
            }

            $replies = $pipe->exec();
            if($replies == false)
            {
                $redis->close();
                $this->orderM->rollback();

                return ['code' => 1, 'msg' => '订单生成错误,库存不足'];
            }

            $this->orderM->commit();
        }catch (Exception $e)
        {
            $this->orderM->rollback();

            return ['code' => 1, 'msg' => $e->getMessage()];
        }
        
        return true;
    }
4.redis工具类
<?php
/**
 * Created by PhpStorm.
 * User: SUN
 * Date: 2020/7/15
 * Time: 14:16
 */

namespace app\common\controller;


use think\Env;

class RedisClass
{
    const REDIS_LOCK_KEY_TEMPLATE = 'order_lock_%s';
    const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 60;
    const REDIS_LOCK_UNIQUE_ID_KEY = 'lock_unique_id';
    static $handler = null;

    public static function addLock($intOrderId, $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME)
    {
        if (empty($intOrderId) || $intExpireTime <= 0) {
            return false;
        }
        $objRedisConn = self::getRedisConn();
        $intUniqueLockId = self::generateUniqueLockId();
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);
        $bolRes = $objRedisConn->set($strKey, $intUniqueLockId, ['nx', 'ex'=>$intExpireTime]);
        return $bolRes ? $intUniqueLockId : $bolRes;
    }

    public static function releaseLock($intOrderId, $intLockId)
    {
        if (empty($intOrderId) || empty($intLockId)) {
            return false;
        }
        $objRedisConn = self::getRedisConn();
        $strKey = sprintf(self::REDIS_LOCK_KEY_TEMPLATE, $intOrderId);

        $objRedisConn->watch($strKey);
        if ($intLockId == $objRedisConn->get($strKey)) {
            $objRedisConn->multi()->del($strKey)->exec();
            return true;
        }
        $objRedisConn->unwatch();
        return false;
    }

    public static function getRedisConn($select='')
    {
        $config['host']= Env::get('redis.host','');
        $config['port']= Env::get('redis.port','');
        $config['password']= Env::get('redis.password','');
        $config['select']= !empty($select)?$select:Env::get('redis.select',0);
        $objRedis = new \Redis();
        $objRedis->connect($config['host'],$config['port']);
        $objRedis->auth($config['password']);
        if (0 != $config['select']) {
            $objRedis->select($config['select']);
        }
        return $objRedis;
    }

    public static function generateUniqueLockId()
    {
        return self::getRedisConn()->incr(self::REDIS_LOCK_UNIQUE_ID_KEY);
    }
}
5.并发测试

ab -r -n 6000 -c 5000 http://bid.com/api/demo/msKill
上述只是简单模拟高并发下的秒杀,真实场景要比这复杂很多
如上面的会导致一个用户抢多个
解决思路:
需要一个排队队列和抢购结果队列及库存队列。高并发情况,先将用户进入排队队列,用一个线程循环处理从排队队列取出一个用户,判断用户是否已在抢购结果队列,如果在,则已抢购,否则未抢购,库存减1,写数据库,将用户入结果队列

参考:https://www.cnblogs.com/qqlong/p/9327844.html

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容