内容

实现简单的秒杀页面(显示当前秒杀活动状态)和秒杀接口,不需要考虑下订单和退货流程。

秒杀接口要求

时间到了才能开始秒杀

不能超买:1个用户只能秒杀1次

不能超卖

在缓存崩溃重启的情况也不能出现超买和超卖的情况

测试

功能正常

1个用户发起100个并发测试

随机用户(userId:rand(1, 1000000000)) 请求,100个并发秒杀,最先完成秒杀1000个商品的活动

数据表结构如下

--

-- 用户秒杀成功记录 `log`

--

CREATE TABLE `log` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `eventId` int(11) NOT NULL COMMENT '活动ID',

  `userId` int(11) NOT NULL COMMENT '用户ID',

  `createTime` int(11) NOT NULL COMMENT '创建时间',

  PRIMARY KEY (`id`),

  UNIQUE KEY `eventId` (`eventId`,`userId`)

) ENGINE=InnoDB AUTO_INCREMENT=4353 DEFAULT CHARSET=utf8;

--

-- 秒杀活动 `event`

--

CREATE TABLE `event` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

  `cnt` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '总量',

  `remainCnt` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '剩余数量',

  `startTime` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '开始时间',

  `createTime` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',

  PRIMARY KEY (`id`)

) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='活动';

代码参考(重在理解)

<?php
namespace app\helper;

class SecKill
{
    protected $userId;//用户ID
    protected $eventId;//活动ID
    protected $eventLock;//活动锁
    protected $eventCntKey;//数量缓存键
    protected $userLogCacheKey;//用户日志缓存
    protected $userLock;//用户锁缓存键
    protected $cache = null;
    protected $db = null;
    protected $config = [];
    const EXPIRATION = 20;

    public function __construct($userId, $eventId)
    {
        $this->userId = $userId;
        $this->eventId = $eventId;
        $this->eventLock = $this->eventId . '_eventLock';
        $this->eventCntKey = $this->eventId . '_eventCnt';//缓存中数量比数据库中多1个
        $this->userLogCacheKey = $this->userId . '_log';
        $this->userLock = $this->userId . '_userLock';
        $this->config = [//可以写一个配置文件引入,在这里我就简单点写
            'cache' => [
                'host' => '127.0.0.1',
                'port' => '11211'
            ],
            'db' => [
                'host' => '127.0.0.1',
                'user' => 'root',
                'password' => 123456,
                'database' => 'miaosha',
            ]
        ];
        $this->kill();
    }

    private function kill()
    {
        try {
            $this->connectMemcached();
            //给用户加锁
            if (!$this->cache->add($this->userLock, 1, self::EXPIRATION)) {
                return 'User failed to lock';
            }
            $this->connectMysql();
            $sqlTpl = "SELECT 
                          `id`
                          FROM  %s 
                          WHERE `userId` = '%d' 
                          AND `eventId` = '%d'";
            $sql = sprintf($sqlTpl, self::tableName(), $this->userId, $this->eventId);
            $res = $this->db->query($sql);
            //用户是否抢到过抢到过直接退出
            if ($res->fetch_assoc() > 0) {
                $this->cache->delete($this->userLock);
                return '不要贪心呦';
            }
            //递减1
            $cnt = $this->cache->decrement($this->eventCntKey, 1);
            if ($cnt === false) {
                if (!$this->cache->add($this->eventLock, 1, self::EXPIRATION)) {
                    $this->cache->delete($this->userLock);
                    return '加锁失败';
                }
                $res = $this->db->query("SELECT * FROM `event` WHERE `id` = {$this->eventId}");
                $event = $res->fetch_assoc();
                if (!$event) {
                    $this->cache->delete($this->eventLock);
                    $this->cache->delete($this->userLock);
                    return '活动不存在';
                }
                $cnt = $event['remainCnt'];
                //添加活动数量
                if (!$this->cache->add($this->eventCntKey, $cnt)) {
                    $this->cache->delete($this->userLock);
                    $this->cache->delete($this->eventLock);
                    return "被人捷足先登";
                }
                $this->cache->delete($this->eventLock);
            }
            if ($cnt < 1) {
                $this->cache->delete($this->userLock);
                return "抢光了";
            }
            $time = time();
            $this->db->query("BEGIN");
            $this->db->query("UPDATE `event` SET `remainCnt` = `remainCnt` - 1 WHERE `id` = {$this->eventId}");
            $this->db->query("INSERT INTO `log` (`eventId`, `userId`, `createTime`) VALUES ({$this->eventId}, {$this->userId}, {$time})");
            $this->db->query("COMMIT");
            $this->cache->delete($this->userLock);
            return "Success";
        } catch (\Exception $ex) {
            echo "<pre>";
            var_dump($ex);
        }
    }

    /**
     * 连接memcached
     * @return string
     */
    protected function connectMemcached()
    {
        $this->cache = new \Memcache();
        if (!empty($this->config['cache'])) {
            $this->cache->addServer($this->config['cache']['host'], $this->config['cache']['port']);
        } else {
            return 'Configuration does not exist';
        }
    }

    /**
     * 连接mysql
     */
    protected function connectMysql()
    {
        $this->db = new \mysqli();
        $this->db->connect($this->config['db']['host'], $this->config['db']['user'], $this->config['db']['password'], $this->config['db']['database']);
        $this->db->set_charset('utf8');
    }

    protected static function tableName()
    {
        return '`log`';
    }
}

Last modification:July 16th, 2019 at 02:08 pm
如果觉得我的文章对你有用,请随意赞赏