可行,但必须放弃“全功能电商”幻想,仅实现商品预热→秒杀开关→库存原子扣减→订单生成核心链路,用gii快速生成基础结构,禁用视图渲染、启用redis三层拦截、原生sql扣库存、配置redis连接池,并关闭调试模式。

三天内用 Yii 2 搭出能扛住万级 QPS 的抢购系统,可行,但必须放弃“全功能电商”的幻想——只做核心链路:商品预热 → 秒杀开关 → 库存原子扣减 → 订单生成。其他如用户中心、支付回调、物流等全部 Mock 或延后。
用 Gii 快速生成商品与订单基础结构
别手写 CRUD,Gii 是唯一能帮你省下 6 小时的工具。重点不是生成多少代码,而是确保模型继承 yii\db\ActiveRecord 并启用事务支持。
- 在
config/web.php中启用 Gii 模块,且仅限本地 IP:'allowedIPs' => ['127.0.0.1', '::1'] - 访问
/index.php?r=gii,选择 “CRUD Generator”,填入表名goods和orders - 关键参数要改:
Model Class设为app\models\Goods,Controller Class设为app\controllers\SeckillController(不要用GoodsController,避免和常规商品逻辑混在一起) - 生成后立刻删掉
views/goods/*下所有文件——抢购页不用标准列表/详情,你只需要一个views/seckill/index.php
库存扣减必须绕开 ActiveRecord 默认 save()
$model->save() 在高并发下是性能黑洞:它会先 SELECT 再 UPDATE,中间有时间窗口;且每个请求都新建 PHP 对象,内存和 GC 压力陡增。直接写原生 SQL 才可靠。
- 在
app\models\Goods中添加方法:decreaseStock($id, $num = 1) - 方法体内用
Yii::$app->db->createCommand()执行带 WHERE 条件的 UPDATE:UPDATE goods SET stock = stock - :num WHERE id = :id AND stock >= :num - 检查
execute()返回值:等于 1 表示扣减成功,0 表示库存不足或已被抢光,不抛异常,直接返回 false - 绝对不要在事务里再嵌套
$model->save()—— 这会让锁持有时间变长,加剧数据库阻塞
用 Redis 做三层拦截,而不是只当缓存
Redis 在这里不是“加速器”,而是“流量筛子”。它的作用顺序是:开关控制 → 请求计数 → 分布式锁兜底。三者缺一不可,漏一层就可能超卖。
- 秒杀开关存在
seckill:status:{goods_id},值为 0/1,用GET判断,非原子操作但够用;开关打开前所有请求直接返回“未开始” - 请求计数用
INCRBY seckill:counter:{goods_id} 1,配合GET seckill:total:{goods_id}实时比对,超量立即RETURN,不进 DB - 真正扣库存前加锁:
SET seckill:lock:{goods_id} 1 NX EX 3,过期时间设为 3 秒(足够执行 SQL),失败则返回“请重试” - 注意:
DEL锁必须放在finally块,但 Yii 2 没有原生 finally 支持,得用register_shutdown_function()或 try/catch + 显式 del
忽略视图渲染,直接输出 JSON
抢购接口不是给浏览器看的,前端应由 Vue/React 独立部署。Yii 2 的 $this->render() 会触发完整视图栈解析,实测比 json_encode() 慢 3–5 倍。
- 控制器动作结尾统一用:
return json_encode(['code' => 0, 'msg' => 'success', 'data' => $orderInfo], JSON_UNESCAPED_UNICODE); - 禁用 layout:
Yii::$app->layout = false;在 action 开头就加 - 关掉调试模式:
YII_DEBUG = false和YII_ENV = 'prod'必须写死在web/index.php顶部,否则日志写入会拖慢响应 - 不要用
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON—— 它会额外走 formatter 流程,纯echo+exit更快
最易被忽略的是 Redis 连接池配置。默认 yii\redis\Connection 没设 maxConnects,高并发下会频繁建连断连,导致 "Could not get a resource from the pool" 错误。上线前必须在 config/web.php 的 redis 组件里加上 'maxConnects' => 20。











