内容

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

    • 时间到了才能开始秒杀
    • 不能超买: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:September 30th, 2020 at 02:31 pm
如果觉得我的文章对你有用,请随意赞赏