引言

在电商领域,商品秒杀是一种常见的促销活动,它要求系统能够在短时间内处理大量的并发请求。本文将深入探讨高并发的概念、秒杀系统面临的挑战以及相应的解决方案,并提供基于Redis和MySQL的实现示例。

目录

并发与高并发基础

什么是并发

并发是指在同一时间段内,多个任务同时执行的能力。在计算机系统中,这通常通过时间片轮转或多核处理来实现。

举个生活中的例子来理解并发和并行的区别:

  • 单线程处理:你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这说明你不支持并发也不支持并行。
  • 并发处理:你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
  • 并行处理:你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

什么是高并发

高并发是指在同一时间内,有大量的用户请求同时访问系统,系统需要能够处理这些请求并返回结果。在电商秒杀、抢购、直播带货等场景中,高并发尤为常见。

高并发的特点

  1. 请求量大:短时间内涌入大量请求
  2. 请求集中:请求在时间和对象上高度集中
  3. 峰值明显:流量呈现明显的峰谷特征
  4. 持续时间短:高峰期通常持续时间较短
  5. 用户体验敏感:响应速度直接影响用户体验和转化率

秒杀系统架构设计

系统架构图

一个典型的秒杀系统架构通常包括以下几个层次:

1
2
3
4
5
用户 -> CDN -> 负载均衡 -> 应用服务器集群 -> 缓存层 -> 数据库层

消息队列

异步处理服务

核心流程

  1. 秒杀前:商品数据预热到缓存,准备好限流策略
  2. 秒杀中
    • 前端限流和防重复提交
    • 验证用户资格
    • 检查库存(缓存层)
    • 扣减库存(缓存层原子操作)
    • 生成订单(可异步处理)
    • 支付确认
  3. 秒杀后:数据一致性处理,清理缓存等

关键技术点

  • 动静分离
  • 热点数据缓存
  • 削峰填谷
  • 限流熔断
  • 异步处理
  • 分布式锁
  • 幂等性设计

高并发的挑战

高并发系统面临的主要挑战包括:

  • 系统的性能瓶颈:CPU、内存、网络IO等资源限制
  • 数据库的压力:连接数限制、锁竞争、磁盘IO瓶颈
  • 缓存的失效:缓存穿透、缓存击穿、缓存雪崩
  • 网络的延迟:网络带宽限制、网络拥塞
  • 数据一致性:分布式系统中的数据一致性难题
  • 系统稳定性:单点故障、级联失败风险

高并发的解决方案

前端优化

  1. 页面静态化:将秒杀页面静态化并部署到CDN
  2. 资源压缩:压缩CSS、JavaScript文件减少传输量
  3. 请求合并:合并多个HTTP请求减少连接开销
  4. 客户端限流:前端按钮控制、倒计时等防止重复提交
  5. 页面预加载:提前加载页面资源

系统架构优化

  1. 系统拆分:按业务领域或功能拆分为微服务
  2. 集群部署:应用服务器水平扩展
  3. 负载均衡:使用Nginx、LVS等实现负载均衡
  4. 异步处理:使用消息队列实现异步处理
  5. 服务降级:非核心功能在高峰期自动降级

数据库优化

  1. 分库分表:水平拆分和垂直拆分减轻单库压力
  2. 读写分离:主库写入,从库读取
  3. 索引优化:合理设计索引提高查询效率
  4. SQL优化:优化SQL语句,避免全表扫描
  5. 连接池:复用数据库连接减少建立连接的开销

缓存策略

  1. 多级缓存:浏览器缓存、CDN缓存、应用缓存、分布式缓存
  2. 热点数据缓存:将秒杀商品信息、库存等热点数据放入缓存
  3. 缓存预热:活动开始前提前将数据加载到缓存
  4. 缓存更新策略:设置合理的缓存过期策略,采用更新缓存和删除缓存策略
  5. 防止缓存问题:解决缓存穿透、缓存击穿、缓存雪崩问题

限流与熔断

  1. 接口限流:令牌桶、漏桶算法限制接口访问频率
  2. 分布式限流:使用Redis、Sentinel等实现分布式限流
  3. 熔断机制:当服务不可用时快速失败而不是持续等待
  4. 降级策略:返回兜底数据而不是错误
  5. 黑白名单:针对特定IP或用户实施不同的限流策略

秒杀系统实现示例

基于Redis的实现

List 队列实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php

namespace app\controller;

use support\Request;
use app\model\Goods;
use app\model\Order;
use support\Db;
use support\Redis;

class ScoreKill
{
/**
* 初始化商品库存
* @param int $goodsId 商品ID
* @return void
*/
public function setGoods($goodsId = 1)
{
// 获取商品信息
$goods = Goods::find($goodsId);
if (!$goods) {
return json([
'code' => 400,
'message' => '商品不存在'
]);
}

$stock = $goods->stock;
// 清空之前的库存队列
Redis::del("goodsId:{$goodsId}");
// 将商品放入Redis List队列中,每个元素代表一个库存单位
for ($i=1; $i <= $stock; $i++) {
Redis::lpush("goodsId:{$goodsId}", json_encode($goods));
}

return json([
'code' => 200,
'message' => '库存初始化成功',
'data' => [
'goods_id' => $goodsId,
'stock' => $stock
]
]);
}

/**
* 秒杀商品 - 基于Redis List队列实现
* @param Request $request
* @return \support\Response
*/
public function killGoods(Request $request)
{
$goodsId = 1;
$userId = $request->input('user_id', rand(1, 999));

// 检查库存
$stock = Redis::lLen("goodsId:{$goodsId}");
if ($stock <= 0) {
return json([
'code' => 400,
'message' => '商品库存不足'
]);
}

// 检查用户是否购买过
$purchased = Redis::get("user_purchased:{$userId}:{$goodsId}");
if ($purchased) {
return json([
'code' => 400,
'message' => '用户已购买过该商品'
]);
}

// 从队列中取出商品(原子操作,保证了并发安全)
$goods = Redis::rPop("goodsId:{$goodsId}");
if (!$goods) {
return json([
'code' => 400,
'message' => '商品库存不足'
]);
}

$goods = json_decode($goods, true);

// 标记用户已购买
Redis::set("user_purchased:{$userId}:{$goodsId}", 1, ['EX' => 86400]); // 24小时过期

try {
// 创建订单(实际项目中应考虑异步处理)
$order = new Order();
$order->user_id = $userId;
$order->goods_id = $goodsId;
$order->save();

return json([
'code' => 200,
'message' => '秒杀成功',
'data' => [
'order_id' => $order->id
]
]);
} catch (\Exception $e) {
// 发生异常时,将商品放回队列
Redis::lpush("goodsId:{$goodsId}", json_encode($goods));
// 移除购买标记
Redis::del("user_purchased:{$userId}:{$goodsId}");

return json([
'code' => 500,
'message' => '系统异常:' . $e->getMessage()
]);
}
}
}

Watch 乐观锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php

namespace app\controller;

use support\Request;
use app\model\Goods;
use app\model\Order;
use support\Db;
use support\Redis;

class ScoreKill
{
/**
* 秒杀商品 - 基于Redis Watch乐观锁实现
* @param Request $request
* @return \support\Response
*/
public function killGoods(Request $request)
{
$goodsId = 1;
$userId = $request->input('user_id', rand(1, 999));
$stockKey = "goods_stock:{$goodsId}";
$purchasedKey = "user_purchased:{$userId}:{$goodsId}";

// 检查用户是否已购买(防止重复购买)
$hasPurchased = Redis::get($purchasedKey);
if ($hasPurchased) {
return json([
'code' => 400,
'message' => '用户已购买过该商品'
]);
}

// 初始化库存(只在第一次执行时初始化,实际项目中应在商品上架时初始化)
$exists = Redis::exists($stockKey);
if (!$exists) {
// 从数据库获取商品信息
$goods = Goods::find($goodsId);
if (!$goods) {
return json([
'code' => 400,
'message' => '商品不存在'
]);
}
Redis::set($stockKey, $goods->stock);
}

// 尝试最多3次执行秒杀操作
$maxRetries = 3;
for ($i = 0; $i < $maxRetries; $i++) {
// 监视库存键
Redis::watch($stockKey);

// 获取当前库存
$stock = Redis::get($stockKey);
if ($stock <= 0) {
Redis::unwatch(); // 解除监视
return json([
'code' => 400,
'message' => '商品库存不足'
]);
}

// 开启事务
Redis::multi();

// 减少库存
Redis::decr($stockKey);

// 记录用户购买记录
Redis::set($purchasedKey, 1, ['EX' => 86400]); // 24小时过期

// 执行事务
$result = Redis::exec();

// 判断事务是否执行成功
if ($result) {
try {
// 创建订单(实际项目中可考虑异步处理)
$order = new Order();
$order->user_id = $userId;
$order->goods_id = $goodsId;
$order->save();

return json([
'code' => 200,
'message' => '秒杀成功',
'data' => [
'order_id' => $order->id,
'retry_count' => $i
]
]);
} catch (\Exception $e) {
// 发生异常时,恢复库存
Redis::incr($stockKey);
// 移除购买标记
Redis::del($purchasedKey);

return json([
'code' => 500,
'message' => '系统异常:' . $e->getMessage()
]);
}
}

// 如果执行失败,说明有并发修改,继续重试
// Redis Watch会自动取消监视,不需要显式调用unwatch
}

// 多次重试后仍然失败
return json([
'code' => 429, // Too Many Requests
'message' => '秒杀人数过多,请稍后重试'
]);
}
}

基于MySQL的实现

乐观锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?php

namespace app\controller;

use support\Request;
use app\model\Goods;
use app\model\Order;
use support\Db;

class ScoreKill
{
/**
* 秒杀商品 - 基于MySQL乐观锁实现
* @param Request $request
* @return \support\Response
*/
public function killGoods(Request $request)
{
$goodsId = 1;
$userId = $request->input('user_id', rand(1, 999));

// 检查商品是否存在
Db::beginTransaction();
try {
// 查询商品信息
$goods = Goods::find($goodsId);
if (!$goods) {
Db::rollBack();
return json([
'code' => 400,
'message' => '商品不存在'
]);
}

// 检查库存
if ($goods->stock <= 0) {
Db::rollBack();
return json([
'code' => 400,
'message' => '商品库存不足'
]);
}

// 检查用户是否购买过
$order = Order::where('user_id', $userId)
->where('goods_id', $goodsId)
->first();
if ($order) {
Db::rollBack();
return json([
'code' => 400,
'message' => '用户已购买过该商品'
]);
}

// 使用乐观锁更新库存
// 通过version字段实现乐观锁,只有当version匹配时才更新成功
$result = Goods::where([
'id' => $goodsId,
'version' => $goods->version
])->update([
'stock' => $goods->stock - 1,
'version' => $goods->version + 1
]);

if ($result) {
// 创建订单
$order = new Order();
$order->user_id = $userId;
$order->goods_id = $goodsId;
$order->save();

Db::commit();
return json([
'code' => 200,
'message' => '秒杀成功',
'data' => [
'order_id' => $order->id
]
]);
} else {
// 更新失败,说明有并发更新,版本号已变化
Db::rollBack();
return json([
'code' => 409, // Conflict
'message' => '秒杀失败,请重试'
]);
}
} catch(\Exception $e) {
Db::rollBack();
return json([
'code' => 500,
'message' => '系统异常:' . $e->getMessage()
]);
}
}
}

排他锁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
<?php

namespace app\controller;

use support\Request;
use app\model\Goods;
use app\model\Order;
use support\Db;

class ScoreKill
{
/**
* 秒杀商品 - 基于MySQL排他锁实现
* @param Request $request
* @return \support\Response
*/
public function killGoods(Request $request)
{
$goodsId = 1;
$userId = $request->input('user_id', rand(1, 999));

// 开启事务
Db::beginTransaction();
try {
// 使用FOR UPDATE获取排他锁
$goods = Goods::lockForUpdate()->find($goodsId);
if (!$goods) {
Db::rollBack();
return json([
'code' => 400,
'message' => '商品不存在'
]);
}

// 检查库存
if ($goods->stock <= 0) {
Db::rollBack();
return json([
'code' => 400,
'message' => '商品库存不足'
]);
}

// 检查用户是否购买过
$order = Order::where('user_id', $userId)
->where('goods_id', $goodsId)
->first();
if ($order) {
Db::rollBack();
return json([
'code' => 400,
'message' => '用户已购买过该商品'
]);
}

// 更新库存
$result = Goods::where('id', $goodsId)->update([
'stock' => $goods->stock - 1,
]);

if ($result) {
// 创建订单
$order = new Order();
$order->user_id = $userId;
$order->goods_id = $goodsId;
$order->save();

Db::commit();
return json([
'code' => 200,
'message' => '秒杀成功',
'data' => [
'order_id' => $order->id
]
]);
} else {
Db::rollBack();
return json([
'code' => 400,
'message' => '秒杀失败'
]);
}
} catch(\Exception $e) {
Db::rollBack();
return json([
'code' => 500,
'message' => '系统异常:' . $e->getMessage()
]);
}
}
}

性能测试与优化

在实现秒杀系统后,需要进行性能测试以验证系统的并发处理能力:

  1. 压力测试工具:使用Apache JMeter、Gatling等工具模拟大量并发请求
  2. 测试指标
    • QPS(每秒查询率)
    • 响应时间
    • 成功率
    • 资源利用率(CPU、内存、网络等)
  3. 性能瓶颈分析
    • 使用监控工具定位瓶颈
    • 分析慢查询日志
    • 检查网络延迟

常见问题与解决方案

1. 超卖问题

问题:多个用户同时抢购,导致库存出现负数。

解决方案

  • 使用Redis的原子操作(如LPOP、DECR等)
  • 数据库乐观锁或悲观锁
  • 预扣库存 + 异步确认

2. 库存同步问题

问题:缓存中的库存与数据库中的库存不一致。

解决方案

  • 定时任务同步库存
  • 异步更新数据库
  • 最终一致性设计

3. 重复下单问题

问题:用户重复提交订单。

解决方案

  • 前端防重复提交
  • 接口幂等性设计
  • 唯一索引约束

4. 缓存雪崩问题

问题:大量缓存同时失效,导致请求直接访问数据库。

解决方案

  • 缓存失效时间随机化
  • 热点数据永不过期
  • 多级缓存架构

总结与最佳实践

构建高性能的秒杀系统需要综合考虑前端优化、系统架构、数据库设计、缓存策略等多个方面。以下是一些最佳实践:

  1. 系统设计

    • 秒杀系统独立部署
    • 采用微服务架构
    • 使用消息队列削峰填谷
  2. 数据层优化

    • 热点数据预加载到缓存
    • 避免使用复杂SQL
    • 合理使用锁机制
  3. 应用层优化

    • 接口限流
    • 异步处理
    • 服务降级
  4. 前端优化

    • 页面静态化
    • CDN加速
    • 客户端限流
  5. 监控与预警

    • 实时监控系统状态
    • 建立完善的预警机制
    • 制定应急预案

通过合理的架构设计和优化策略,可以构建出高性能、高可用的秒杀系统,满足大规模并发访问的需求。