发布日期:2023年11月21日 | 作者:后端架构师
一、项目背景与技术挑战
在电商业务中,秒杀活动是最考验系统性能的场景之一。本文将通过ThinkPHP 8.0框架,从零构建一个能够支撑万级并发的高性能秒杀系统。我们将重点解决库存超卖、系统瓶颈、数据一致性等核心技术难题。
核心技术栈:
- 核心框架:ThinkPHP 8.0 + PHP 8.1
- 缓存层:Redis 6.0 + 分布式锁
- 消息队列:RabbitMQ 异步处理
- 数据库:MySQL 8.0 + 读写分离
- 监控系统:Prometheus + Grafana
二、系统架构设计
1. 整体架构图
用户请求 → Nginx负载均衡 → ThinkPHP应用层 → Redis预减库存 → RabbitMQ异步下单 → MySQL数据持久化 ↓ ↓ ↓ ↓ ↓ ↓ CDN IP限流模块 令牌桶限流 Lua原子操作 死信队列处理 分库分表
2. 目录结构规划
seckill-system/
├── app/
│ ├── controller/ # 控制器层
│ │ ├── Seckill.php # 秒杀核心控制器
│ │ └── Order.php # 订单控制器
│ ├── service/ # 服务层
│ │ ├── SeckillService.php
│ │ └── OrderService.php
│ ├── model/ # 模型层
│ │ ├── SeckillGoods.php
│ │ └── SeckillOrder.php
│ └── middleware/ # 中间件
│ ├── RateLimit.php # 限流中间件
│ └── Auth.php # 认证中间件
├── config/ # 配置文件
│ ├── redis.php # Redis配置
│ ├── queue.php # 队列配置
│ └── database.php # 数据库配置
└── extend/ # 扩展类库
├── util/ # 工具类
└── lock/ # 分布式锁
三、核心业务实现
1. 数据库设计
// 秒杀商品表
CREATE TABLE `seckill_goods` (
`id` bigint(20) UNSIGNED NOT NULL,
`goods_name` varchar(255) NOT NULL COMMENT '商品名称',
`stock_count` int(11) NOT NULL COMMENT '库存数量',
`start_time` datetime NOT NULL COMMENT '开始时间',
`end_time` datetime NOT NULL COMMENT '结束时间',
`version` int(11) DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
// 秒杀订单表
CREATE TABLE `seckill_order` (
`id` bigint(20) UNSIGNED NOT NULL,
`user_id` bigint(20) NOT NULL,
`goods_id` bigint(20) NOT NULL,
`order_code` varchar(32) NOT NULL COMMENT '订单号',
`status` tinyint(1) DEFAULT '0' COMMENT '状态',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_user_goods` (`user_id`,`goods_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 秒杀核心控制器
<?php
namespace appcontroller;
use appBaseController;
use appserviceSeckillService;
use thinkfacadeCache;
use thinkfacadeLog;
class Seckill extends BaseController
{
protected $seckillService;
public function __construct(SeckillService $seckillService)
{
$this->seckillService = $seckillService;
}
/**
* 秒杀入口
*/
public function seckill()
{
$goodsId = $this->request->param('goods_id');
$userId = $this->request->user_id; // 从中间件获取
// 1. 验证活动状态
if (!$this->seckillService->checkSeckillTime($goodsId)) {
return json(['code' => 400, 'msg' => '不在秒杀时间内']);
}
// 2. 验证重复购买
if ($this->seckillService->checkUserBought($userId, $goodsId)) {
return json(['code' => 400, 'msg' => '请勿重复参与']);
}
// 3. 获取秒杀资格(令牌桶限流)
if (!$this->seckillService->acquireToken($goodsId)) {
return json(['code' => 400, 'msg' => '秒杀太火爆,请稍后重试']);
}
// 4. Redis预减库存
$stock = $this->seckillService->preReduceStock($goodsId);
if ($stock 400, 'msg' => '商品已售罄']);
}
// 5. 发送消息队列
$result = $this->seckillService->sendSeckillMessage($userId, $goodsId);
if ($result) {
return json(['code' => 200, 'msg' => '秒杀请求已受理']);
} else {
return json(['code' => 500, 'msg' => '系统繁忙,请重试']);
}
}
/**
* 查询秒杀结果
*/
public function checkResult()
{
$goodsId = $this->request->param('goods_id');
$userId = $this->request->user_id;
$result = $this->seckillService->getSeckillResult($userId, $goodsId);
return json($result);
}
}
3. 秒杀服务层实现
<?php
namespace appservice;
use thinkfacadeDb;
use thinkfacadeCache;
use thinkfacadeQueue;
class SeckillService
{
/**
* Redis预减库存
*/
public function preReduceStock($goodsId)
{
$key = "seckill_stock:{$goodsId}";
// 使用Lua脚本保证原子性
$lua = <<<LUA
local stock = redis.call('get', KEYS[1])
if not stock then
return -2
end
if tonumber(stock) eval($lua, [$key], 1);
return $result;
}
/**
* 令牌桶限流
*/
public function acquireToken($goodsId)
{
$key = "seckill_token:{$goodsId}";
$now = microtime(true);
// 令牌桶容量和生成速率
$capacity = 1000; // 桶容量
$rate = 100; // 每秒生成令牌数
$data = Cache::store('redis')->hGetAll($key);
if (empty($data)) {
// 初始化令牌桶
$data = [
'tokens' => $capacity - 1,
'timestamp' => $now
];
Cache::store('redis')->hMSet($key, $data);
return true;
}
// 计算新增令牌
$newTokens = ($now - $data['timestamp']) * $rate;
$tokens = min($capacity, $data['tokens'] + $newTokens);
if ($tokens hMSet($key, $data);
return true;
}
/**
* 发送秒杀消息到队列
*/
public function sendSeckillMessage($userId, $goodsId)
{
$data = [
'user_id' => $userId,
'goods_id' => $goodsId,
'create_time' => time()
];
return Queue::push('SeckillJob', $data, 'seckill');
}
/**
* 数据库最终扣减库存(乐观锁)
*/
public function reduceStockInDb($goodsId)
{
return Db::name('seckill_goods')
->where('id', $goodsId)
->where('stock_count', '>', 0)
->dec('stock_count')
->update();
}
}
四、高并发优化策略
1. 队列处理器实现
<?php
namespace appjob;
use thinkqueueJob;
use appserviceOrderService;
class SeckillJob
{
protected $orderService;
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}
public function fire(Job $job, $data)
{
$userId = $data['user_id'];
$goodsId = $data['goods_id'];
try {
// 创建订单
$result = $this->orderService->createSeckillOrder($userId, $goodsId);
if ($result) {
$job->delete();
// 发送成功通知
$this->sendSuccessNotification($userId, $goodsId);
} else {
// 库存不足,重试3次后放弃
if ($job->attempts() > 3) {
$job->delete();
Log::error("秒杀订单创建失败: user_id:{$userId}, goods_id:{$goodsId}");
} else {
$job->release(10); // 10秒后重试
}
}
} catch (Exception $e) {
Log::error("秒杀队列处理异常: " . $e->getMessage());
$job->release(10);
}
}
}
2. 限流中间件
<?php
namespace appmiddleware;
use thinkfacadeCache;
class RateLimit
{
public function handle($request, Closure $next)
{
$ip = $request->ip();
$path = $request->pathinfo();
$key = "rate_limit:{$ip}:{$path}";
// 滑动窗口限流:每分钟最多100次请求
$now = time();
$windowSize = 60; // 60秒窗口
$maxRequests = 100;
$requests = Cache::store('redis')->lRange($key, 0, -1);
$requests = array_filter($requests, function($time) use ($now, $windowSize) {
return $time > $now - $windowSize;
});
if (count($requests) >= $maxRequests) {
return json(['code' => 429, 'msg' => '请求过于频繁']);
}
// 记录本次请求
Cache::store('redis')->lPush($key, $now);
Cache::store('redis')->expire($key, $windowSize);
return $next($request);
}
}
五、系统部署与监控
1. Nginx配置优化
# nginx.conf 关键配置
http {
# 限制请求频率
limit_req_zone $binary_remote_addr zone=seckill:10m rate=10r/s;
upstream thinkphp_backend {
server 127.0.0.1:8001 weight=5;
server 127.0.0.1:8002 weight=5;
keepalive 32;
}
server {
location /seckill {
limit_req zone=seckill burst=20 nodelay;
proxy_pass http://thinkphp_backend;
proxy_set_header Host $host;
}
}
}
2. 性能监控指标
- QPS监控:实时监测系统吞吐量
- Redis命中率:确保缓存有效性
- 队列积压:监控消息队列处理情况
- 数据库连接数:预防连接池耗尽
六、压测结果与总结
通过JMeter对系统进行压力测试,在4核8G服务器配置下:
- 单机QPS:最高支持 5,000+ 请求/秒
- 订单处理:3,000+ 订单/秒
- 响应时间:95%请求在100ms内完成
- 错误率:< 0.1%
核心技术要点总结:
- 分层架构:控制器→服务层→数据层,职责清晰
- 缓存策略:Redis预减库存 + 多级缓存
- 异步处理:消息队列削峰填谷
- 限流防护:多层次限流保护系统
- 数据一致性:最终一致性保证
本系统展示了ThinkPHP在现代高并发场景下的强大能力,通过合理的架构设计和优化策略,完全可以支撑大规模的秒杀业务。读者可以基于此框架扩展更多电商功能,如优惠券、积分、物流跟踪等模块。