1.相关数据通过计划任务写入redis,生成静态html前端访问页面
2.通过redis锁控制秒杀数量
3.秒杀成功通过队列处理订单
基于hyperf框架的伪代码:
<?php declare(strict_types=1); namespace App\Shop\SecKill\Controller; class IndexController extends AbstractController { /** * redis的keys * @var string[] */ private $ary_redis_key = [ 'info' => 'secKill:goods_%s:info', 'start_time' => 'secKill:goods_%s:start_time', 'end_time' => 'secKill:goods_%s:end_time', 'num' => 'secKill:goods_%s:num', 'success' => 'secKill:goods_%s:success', ]; /** * 模拟商品信息 * @var array */ public $ary_goods = [ 'name' => '测试商品', 'id' => '101', 'num' => 1, 'price' => 100.12, //'start_time' => '', //'end_time' => '', ]; /** * redis连接对象 * @var Redis */ public $obj_redis; public function __construct() { $this->obj_redis = redis(); } /** * 模拟秒杀活动数据 * 秒杀活动数据提前写入redis缓存中 * @RequestMapping(path="storeInfo", methods="get,post") */ public function storeInfo() { $ary_goods = $this->ary_goods; //测试,清除redis数据 foreach ($this->ary_redis_key as $key => $val) { $this->obj_redis->del(sprintf($val, $ary_goods['id'])); } //模拟活动开始时间 $ary_goods['start_time'] = time() + 40; $ary_goods['start_date'] = date('Y-m-d H:i:s', $ary_goods['start_time']); $ary_goods['end_time'] = $ary_goods['start_time'] + 120; $ary_goods['end_date'] = date('Y-m-d H:i:s', $ary_goods['end_time']); $redis_info_key = sprintf($this->ary_redis_key['info'], $ary_goods['id']); $redis_num_key = sprintf($this->ary_redis_key['num'], $ary_goods['id']); $redis_start_time_key = sprintf($this->ary_redis_key['start_time'], $ary_goods['id']); $redis_end_time_key = sprintf($this->ary_redis_key['end_time'], $ary_goods['id']); $this->obj_redis->set($redis_info_key, json_encode($ary_goods)); $this->obj_redis->set($redis_num_key, $ary_goods['num']); $this->obj_redis->set($redis_start_time_key, $ary_goods['start_time']); $this->obj_redis->set($redis_end_time_key, $ary_goods['end_time']); } /** * 模拟秒杀活动进行中 * @RequestMapping(path="secondKill", methods="get,post") */ public function secondKill(): array { $ary_goods = $this->ary_goods; $obj_redis = $this->obj_redis; $redis_start_time_key = sprintf($this->ary_redis_key['start_time'], $ary_goods['id']); $redis_end_time_key = sprintf($this->ary_redis_key['end_time'], $ary_goods['id']); if (!$obj_redis->exists($redis_end_time_key) || $obj_redis->get($redis_end_time_key) < time()) { return [ 'status' => 0, 'msg' => '活动已结束', 'date' => date('Y-m-d H:i:s'), ]; } if (!$obj_redis->exists($redis_start_time_key) || $obj_redis->get($redis_start_time_key) > time()) { return [ 'status' => 0, 'msg' => '活动未开始', 'date' => date('Y-m-d H:i:s'), ]; } try { $redis_key = sprintf($this->ary_redis_key['num'], $ary_goods['id']); $redis_success_key = sprintf($this->ary_redis_key['success'], $ary_goods['id']); $uid = date('Ymd_His') . '_' . uniqid(); //获取锁 $lock_id = RedisLockService::addLock($redis_key, 10); if (empty($lock_id)) { return [ 'status' => 0, 'msg' => '商品锁定中...', 'date' => date('Y-m-d H:i:s'), ]; } $num = $obj_redis->get($redis_key); if ($num > 0) { $obj_redis->zAdd($redis_success_key, ['NX'], time(), $uid);; $obj_redis->decrby($redis_key, 1); //秒杀成功处理逻辑 begin //队列处理逻辑 //1.生成订单信息 //2.付款 for ($i = 0, $sum = 0; $i < 10000; $i++) { $sum += rand(1, 999999); } //秒杀成功处理逻辑 end $res = [ 'status' => 1, 'uid' => $uid, 'msg' => '用户下单成功:' . $uid, 'date' => date('Y-m-d H:i:s'), ]; } else { $res = [ 'status' => 0, 'uid' => $uid, 'msg' => '用户下单失败,库存为:' . $num, 'date' => date('Y-m-d H:i:s'), ]; } //删除锁 RedisLockService::unLock($redis_key, $lock_id); return $res; } catch (\Exception $e) { return [ 'status' => 0, 'msg' => $e->getMessage(), 'date' => date('Y-m-d H:i:s'), ]; } } }
redis锁:
<?php namespace App\Shop\SecKill\Controller; /** * Class RedisLockService redis 锁服务 * @author code.cent123.com */ class RedisLockService { /** * redis锁key */ const REDIS_LOCK_KEY_TEMPLATE = 'LOCKING_KEY_'; /** * redis锁默认超时时间(秒) */ const REDIS_LOCK_DEFAULT_EXPIRE_TIME = 3600; /** * 加redis锁 * @param string $strLockKey * @param int $intExpireTime 锁过期时间(秒) * @return bool|string 加锁成功返回唯一锁ID,加锁失败返回false */ public static function addLock(string $strLockKey, int $intExpireTime = self::REDIS_LOCK_DEFAULT_EXPIRE_TIME) { if (empty($strLockKey) || $intExpireTime <= 0) { return false; } //获取Redis连接 $objRedisConn = redis(); //生成唯一锁ID,解锁需持有此ID $uniqueLockId = self::generateUniqueLockId(); $redisKey = self::REDIS_LOCK_KEY_TEMPLATE . $strLockKey; $bolRes = $objRedisConn->set($redisKey, $uniqueLockId, ['nx', 'ex' => $intExpireTime]); return $bolRes ? (string)$uniqueLockId : false; } /** * 解redis锁 * @param string $strLockKey * @param string $uniqueLockId * @return bool */ public static function unLock(string $strLockKey, string $uniqueLockId): bool { if (empty($strLockKey) || empty($uniqueLockId)) { return false; } $objRedisConn = redis(); $redisKey = self::REDIS_LOCK_KEY_TEMPLATE . $strLockKey;; //监听Redis key防止在比对lock_id与解锁事务执行过程中被修改或删除,提交事务后会自动取消监控,其他情况需手动解除监控 $objRedisConn->watch($redisKey); if ($uniqueLockId == $objRedisConn->get($redisKey)) { $objRedisConn->multi()->del($redisKey)->exec(); return true; } $objRedisConn->unwatch(); return false; } /** * 生成锁唯一ID * @return string */ public static function generateUniqueLockId(): string { return date('Ymd_His') . uniqid('_uniqid_'); //return redis()->incr('LOCK_UNIQUE_ID'); } }