一、秒杀系统架构设计
本教程将使用ThinkPHP 6.0构建一个能够支撑高并发的秒杀系统,解决瞬时高流量带来的系统压力问题。
核心技术栈:
- 核心框架:ThinkPHP 6.0
- 缓存系统:Redis 5.0+
- 消息队列:Redis List/Stream
- 前端技术:Vue.js + Element UI
- 压力测试:JMeter
系统架构图:
用户请求 → 负载均衡 → Web服务器 → [限流] → [缓存验证] → [队列处理] → 数据库 │ │ │ ↓ ↓ ↓ Nginx Redis缓存 Redis队列
核心解决方案:
- 页面静态化减少服务器压力
- Redis原子操作控制库存
- 消息队列异步处理订单
- 分布式锁防止超卖
- 接口限流保护系统
二、项目初始化与配置
1. 创建ThinkPHP项目
composer create-project topthink/think tp6-seckill
cd tp6-seckill
2. 安装扩展依赖
composer require predis/predis
composer require topthink/think-multi-app
composer require topthink/think-queue
3. 项目目录结构
tp6-seckill/
├── app/
│ ├── controller/ # 控制器
│ ├── model/ # 模型
│ ├── service/ # 服务层
│ ├── job/ # 队列任务
│ ├── middleware/ # 中间件
│ └── view/ # 视图文件
├── config/
│ ├── cache.php # 缓存配置
│ ├── queue.php # 队列配置
│ └── redis.php # Redis配置
├── public/ # 静态资源
├── extend/ # 扩展类库
└── route/ # 路由定义
4. 数据库设计
创建秒杀活动表:
CREATE TABLE `seckill_activity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '活动名称',
`product_id` int(11) NOT NULL COMMENT '商品ID',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`stock` int(11) NOT NULL COMMENT '库存数量',
`seckill_price` decimal(10,2) NOT NULL COMMENT '秒杀价格',
`status` tinyint(1) DEFAULT '0' COMMENT '状态:0未开始 1进行中 2已结束',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `seckill_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`activity_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`order_no` varchar(50) NOT NULL COMMENT '订单编号',
`create_time` datetime NOT NULL,
`status` tinyint(1) DEFAULT '0' COMMENT '0未支付 1已支付 2已取消',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_order_no` (`order_no`),
KEY `idx_user_activity` (`user_id`,`activity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、核心功能实现
1. Redis库存预热
创建服务类 app/service/SeckillService.php:
<?php
namespace appservice;
use thinkfacadeCache;
use appmodelSeckillActivity;
class SeckillService
{
// 预热活动库存到Redis
public static function warmUpStock($activityId)
{
$activity = SeckillActivity::find($activityId);
if (!$activity) {
throw new Exception('活动不存在');
}
$key = self::getStockKey($activityId);
Cache::store('redis')->set($key, $activity->stock);
return true;
}
// 获取库存键名
public static function getStockKey($activityId)
{
return 'seckill_stock:' . $activityId;
}
// 获取活动锁键名
public static function getLockKey($activityId, $userId)
{
return 'seckill_lock:' . $activityId . ':' . $userId;
}
}
2. 秒杀核心逻辑
创建控制器 app/controller/Seckill.php:
<?php
namespace appcontroller;
use thinkfacadeCache;
use thinkfacadeQueue;
use appserviceSeckillService;
use appjobCreateSeckillOrder;
class Seckill
{
// 秒杀接口
public function seckill($activityId)
{
$userId = session('user_id'); // 实际项目中从token获取
// 1. 验证活动状态
$activity = appmodelSeckillActivity::where('id', $activityId)
->where('status', 1)
->where('start_time', 'where('end_time', '>=', date('Y-m-d H:i:s'))
->find();
if (!$activity) {
return json(['code' => 400, 'msg' => '活动不存在或已结束']);
}
// 2. 获取分布式锁 (防止用户重复提交)
$lockKey = SeckillService::getLockKey($activityId, $userId);
$lock = Cache::store('redis')->set($lockKey, 1, ['nx', 'ex' => 60]);
if (!$lock) {
return json(['code' => 400, 'msg' => '请勿重复提交']);
}
try {
// 3. Redis原子操作减库存
$stockKey = SeckillService::getStockKey($activityId);
$leftStock = Cache::store('redis')->lua('
local stock = tonumber(redis.call("GET", KEYS[1]))
if stock > 0 then
redis.call("DECR", KEYS[1])
return stock - 1
else
return -1
end
', [$stockKey], 1);
if ($leftStock < 0) {
return json(['code' => 400, 'msg' => '商品已售罄']);
}
// 4. 加入订单队列
$data = [
'activity_id' => $activityId,
'user_id' => $userId,
'seckill_price' => $activity->seckill_price
];
Queue::push(CreateSeckillOrder::class, $data);
return json(['code' => 200, 'msg' => '秒杀成功,请尽快支付']);
} finally {
// 释放锁
Cache::store('redis')->delete($lockKey);
}
}
}
3. 异步订单处理
创建队列任务 app/job/CreateSeckillOrder.php:
<?php
namespace appjob;
use thinkqueueJob;
use appmodelSeckillOrder;
use appmodelSeckillActivity;
class CreateSeckillOrder
{
public function fire(Job $job, $data)
{
// 1. 检查活动是否还有库存
$activity = SeckillActivity::find($data['activity_id']);
if (!$activity || $activity->status != 1 || $activity->stock <= 0) {
$job->delete();
return;
}
// 2. 检查用户是否已有订单
$existOrder = SeckillOrder::where('user_id', $data['user_id'])
->where('activity_id', $data['activity_id'])
->find();
if ($existOrder) {
$job->delete();
return;
}
// 3. 创建订单
$order = new SeckillOrder();
$order->activity_id = $data['activity_id'];
$order->user_id = $data['user_id'];
$order->order_no = 'SK' . date('YmdHis') . mt_rand(1000, 9999);
$order->create_time = date('Y-m-d H:i:s');
$order->save();
// 4. 扣减数据库库存
SeckillActivity::where('id', $data['activity_id'])
->where('stock', '>', 0)
->dec('stock')
->update();
$job->delete();
}
}
四、高并发优化方案
1. 页面静态化
使用定时任务生成静态页面:
<?php
namespace appcommand;
use thinkconsoleCommand;
use thinkconsoleInput;
use thinkconsoleOutput;
use thinkfacadeView;
class GenerateStatic extends Command
{
protected function configure()
{
$this->setName('generate:static')
->setDescription('生成静态页面');
}
protected function execute(Input $input, Output $output)
{
// 获取所有秒杀活动
$activities = appmodelSeckillActivity::where('status', 1)
->where('start_time', '>', date('Y-m-d H:i:s'))
->select();
foreach ($activities as $activity) {
$html = View::fetch('seckill/detail', ['activity' => $activity]);
file_put_contents(
public_path('static/seckill/' . $activity->id . '.html'),
$html
);
}
$output->writeln('静态页面生成完成');
}
}
2. 接口限流中间件
创建 app/middleware/RateLimit.php:
<?php
namespace appmiddleware;
use thinkfacadeCache;
class RateLimit
{
public function handle($request, Closure $next, $limit = 100, $expire = 60)
{
$key = 'rate_limit:' . $request->pathinfo() . ':' . $request->ip();
$count = Cache::store('redis')->inc($key);
if ($count === 1) {
Cache::store('redis')->expire($key, $expire);
}
if ($count > $limit) {
return json(['code' => 429, 'msg' => '请求过于频繁']);
}
return $next($request);
}
}
3. 库存预热命令
创建 app/command/WarmUpStock.php:
<?php
namespace appcommand;
use thinkconsoleCommand;
use thinkconsoleInput;
use thinkconsoleOutput;
use appserviceSeckillService;
class WarmUpStock extends Command
{
protected function configure()
{
$this->setName('warmup:stock')
->setDescription('预热秒杀库存到Redis');
}
protected function execute(Input $input, Output $output)
{
$activities = appmodelSeckillActivity::where('status', 1)
->where('start_time', '<=', date('Y-m-d H:i:s', time() + 3600)) // 1小时内开始的活动
->where('end_time', '>=', date('Y-m-d H:i:s'))
->select();
foreach ($activities as $activity) {
SeckillService::warmUpStock($activity->id);
$output->writeln("活动{$activity->id}库存预热完成");
}
$output->writeln('所有活动库存预热完成');
}
}
五、系统测试与监控
1. JMeter压力测试
创建测试计划:
- 线程组设置:1000线程,10秒内启动
- HTTP请求:秒杀接口地址
- 添加Header:用户token
- 添加结果树和聚合报告
2. Redis监控命令
# 监控Redis内存使用
redis-cli info memory
# 监控Redis命令统计
redis-cli info stats
# 监控Redis键空间
redis-cli info keyspace
3. 系统监控指标
- QPS(每秒查询率)
- 平均响应时间
- 错误率
- Redis内存使用率
- MySQL活跃连接数
六、生产环境部署
1. Nginx配置优化
# 限制单个IP连接数
limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn perip 100;
# 静态文件缓存
location ~* .(html)$ {
expires 1h;
add_header Cache-Control public;
}
# PHP请求转发
location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# 限制请求体大小
client_max_body_size 10m;
# 限制超时时间
fastcgi_read_timeout 300;
}
2. 定时任务配置
# 每分钟检查活动状态
* * * * * cd /path/to/project && php think check:activity
# 每小时生成静态页面
0 * * * * cd /path/to/project && php think generate:static
# 秒杀开始前预热库存
*/5 * * * * cd /path/to/project && php think warmup:stock
七、总结与扩展
本教程详细介绍了使用ThinkPHP 6.0开发高并发秒杀系统的完整流程:
- 基于Redis实现库存原子操作
- 使用消息队列异步处理订单
- 分布式锁防止重复提交
- 页面静态化减少服务器压力
- 接口限流保护系统稳定
进一步扩展方向:
- 增加秒杀结果轮询接口
- 实现秒杀商品预热页面
- 接入支付系统完成订单
- 开发后台管理系统
- 实现分布式部署方案
完整项目代码已上传GitHub:https://github.com/example/tp6-seckill