JCSprout项目:秒杀系统架构设计与优化实战
秒杀系统核心挑战
秒杀系统是电商领域最具挑战性的场景之一,它面临的核心问题可以概括为"三高":
- 高并发:短时间内大量用户同时抢购
- 高可用:系统必须稳定可靠,不能崩溃
- 高性能:响应速度要快,用户体验要好
基础架构设计
系统分层架构
典型的秒杀系统采用分层架构设计:
- Web层:处理HTTP请求,使用Spring MVC框架
- Service层:业务逻辑处理,通过Dubbo实现分布式服务
- 数据层:MySQL数据库持久化存储
基础数据库设计
系统使用两张核心表:
-- 库存表
CREATE TABLE `stock` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
`count` int(11) NOT NULL COMMENT '库存',
`sale` int(11) NOT NULL COMMENT '已售',
`version` int(11) NOT NULL COMMENT '乐观锁,版本号',
PRIMARY KEY (`id`)
);
-- 订单表
CREATE TABLE `stock_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`sid` int(11) NOT NULL COMMENT '库存ID',
`name` varchar(30) NOT NULL DEFAULT '' COMMENT '商品名称',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
);
问题发现:超卖现象
在最初的实现中,系统出现了典型的超卖问题。通过JMeter进行300并发测试时:
- 库存正确扣减为0
- 但订单生成了124条记录,远超库存量
原因分析
这是由于在高并发环境下,多个线程同时读取库存,都认为还有库存可卖,导致超卖。
优化方案一:乐观锁机制
实现原理
通过版本号机制实现乐观锁:
- 读取数据时获取版本号
- 更新时检查版本号是否变化
- 如果版本号一致则更新,否则重试或失败
核心代码
@Override
public int createOptimisticOrder(int sid) throws Exception {
// 校验库存
Stock stock = checkStock(sid);
// 乐观锁更新库存
saleStockOptimistic(stock);
// 创建订单
return createOrder(stock);
}
private void saleStockOptimistic(Stock stock) {
int count = stockService.updateStockByOptimistic(stock);
if (count == 0) {
throw new RuntimeException("并发更新库存失败");
}
}
XML实现
<update id="updateByOptimistic" parameterType="com.crossoverJie.seconds.kill.pojo.Stock">
update stock
<set>
sale = sale + 1,
version = version + 1,
</set>
WHERE id = #{id} AND version = #{version}
</update>
效果验证
优化后测试结果显示:
- 库存和订单数量严格一致
- 大量并发请求会快速失败
- 系统稳定性显著提升
优化方案二:水平扩展
为提高系统吞吐量,实施水平扩展策略:
- Web层扩展:使用Nginx进行负载均衡
- Service层扩展:部署多个服务实例
- 自动化部署:编写Shell脚本实现CI/CD
自动化部署脚本示例
#!/bin/bash
# 构建web消费者
appname="consumer"
# 停止现有进程
PID=$(ps -ef | grep $appname | grep -v grep | awk '{print $2}')
for var in ${PID[@]}; do
kill -9 $var
done
# 代码更新与构建
cd ..
git pull
mvn -Dmaven.test.skip=true clean package
# 部署到多个Tomcat实例
cp target/*.war /path/to/tomcat1/webapps/
cp target/*.war /path/to/tomcat2/webapps/
# 启动服务
/path/to/tomcat1/bin/startup.sh
/path/to/tomcat2/bin/startup.sh
优化方案三:分布式限流
限流必要性
即使库存只有10个,也可能有数百万请求涌入,其中99%都是无效请求,会压垮数据库。
Redis限流实现
使用Redis+Lua脚本实现分布式限流:
public boolean limit() {
Object connection = getConnection();
Object result = limitRequest(connection);
return (FAIL_CODE != (Long) result);
}
private Object limitRequest(Object connection) {
String key = String.valueOf(System.currentTimeMillis() / 1000);
if (connection instanceof Jedis) {
return ((Jedis)connection).eval(script,
Collections.singletonList(key),
Collections.singletonList(String.valueOf(limit)));
} else {
return ((JedisCluster) connection).eval(script,
Collections.singletonList(key),
Collections.singletonList(String.valueOf(limit)));
}
}
效果验证
- 数据库连接数显著下降
- 系统资源利用率提高
- 无效请求被快速拒绝
优化方案四:Redis缓存
缓存策略
- 库存信息缓存在Redis中
- 查询操作走缓存
- 更新时同步缓存和数据库
核心代码
@Override
public int createOptimisticOrderUseRedis(int sid) throws Exception {
// 从Redis校验库存
Stock stock = checkStockByRedis(sid);
// 乐观锁更新库存和Redis
saleStockOptimisticByRedis(stock);
// 创建订单
return createOrder(stock);
}
private Stock checkStockByRedis(int sid) throws Exception {
Integer count = Integer.parseInt(redisTemplate.opsForValue()
.get(RedisKeysConstant.STOCK_COUNT + sid));
Integer sale = Integer.parseInt(redisTemplate.opsForValue()
.get(RedisKeysConstant.STOCK_SALE + sid));
if (count.equals(sale)) {
throw new RuntimeException("库存不足");
}
// 构造Stock对象返回
// ...
}
优化方案五:异步处理
架构设计
引入Kafka消息队列实现异步处理:
- 请求通过校验后发送订单信息到Kafka
- 立即返回响应给用户
- 消费者服务处理消息,完成订单创建
优势
- 请求响应时间大幅缩短
- 系统吞吐量显著提升
- 实现业务解耦
终极架构设计
经过上述优化后,系统最终架构包含以下组件:
- Nginx负载均衡层
- Web应用集群
- 分布式限流组件
- Redis缓存集群
- Kafka消息队列
- 订单消费者服务
- 数据库集群
性能优化总结
- 拦截无效请求:尽早拦截,减少系统压力
- 减少数据库访问:利用缓存,降低DB负载
- 快速失败:Fail Fast原则,保护系统资源
- 异步处理:提高系统吞吐量
- 水平扩展:增强系统处理能力
这套优化方案不仅适用于秒杀场景,对于其他高并发系统也有很好的参考价值。开发者可以根据实际业务需求和系统规模,选择合适的优化策略组合。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考