内容
- 实现简单的秒杀页面(显示当前秒杀活动状态)和秒杀接口,不需要考虑下订单和退货流程。
秒杀接口要求
- 时间到了才能开始秒杀
- 不能超买: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`'; } }
Comment here is closed