我教女票做秒杀:从零到一构建高并发电商系统

一、项目背景与目标拆解

当女友提出想开发一个秒杀功能时,我首先明确了两点核心需求:支撑每秒千级并发请求保证库存扣减的准确性。通过需求分析会,我们拆解出三个关键模块:前端抢购按钮、后端库存服务、数据库设计。

技术选型阶段,我们对比了三种方案:

  1. 单体架构:开发简单但无法应对高并发
  2. 微服务架构:扩展性强但学习成本高
  3. 轻量级服务化:结合Spring Boot+Redis的中间方案

最终选择第三种方案,既保证开发效率,又能通过Redis缓存和消息队列实现核心功能。工具链选定IntelliJ IDEA+Postman+JMeter的组合,确保全流程可测试。

二、核心功能实现详解

1. 前端抢购按钮优化

采用”三段式”交互设计:

  • 预加载阶段:提前获取商品信息和用户令牌
  • 请求阶段:按钮置灰+loading动画防止重复提交
  • 结果反馈:即时显示抢购结果或排队序号

关键代码示例(Vue.js):

  1. methods: {
  2. async seckill() {
  3. if (this.isSubmitting) return;
  4. this.isSubmitting = true;
  5. try {
  6. const res = await axios.post('/api/seckill', {
  7. productId: this.productId,
  8. token: localStorage.getItem('token')
  9. });
  10. this.showResult(res.data);
  11. } catch (e) {
  12. this.handleError(e);
  13. } finally {
  14. this.isSubmitting = false;
  15. }
  16. }
  17. }

2. 后端库存服务设计

库存扣减采用”三级缓存”策略:

  1. Redis原子操作:使用DECR命令保证计数准确
  2. 本地内存缓存:减少数据库访问
  3. 异步消息队列:处理最终库存更新

Spring Boot实现示例:

  1. @RestController
  2. public class SeckillController {
  3. @Autowired
  4. private RedisTemplate<String, Integer> redisTemplate;
  5. @PostMapping("/seckill")
  6. public Result seckill(@RequestBody SeckillRequest request) {
  7. String key = "seckill:" + request.getProductId();
  8. Integer stock = redisTemplate.opsForValue().decrement(key);
  9. if (stock == null || stock < 0) {
  10. redisTemplate.opsForValue().increment(key); // 回滚
  11. return Result.fail("库存不足");
  12. }
  13. // 发送消息到MQ进行异步处理
  14. rabbitTemplate.convertAndSend("seckill.exchange",
  15. "seckill.route", request);
  16. return Result.success("抢购成功");
  17. }
  18. }

3. 数据库优化方案

采用”读写分离+分库分表”架构:

  • 主库:处理订单写入(按用户ID分表)
  • 从库:处理查询请求
  • 热点数据:使用Redis缓存商品详情

MySQL表结构设计要点:

  1. CREATE TABLE seckill_order (
  2. id BIGINT PRIMARY KEY AUTO_INCREMENT,
  3. user_id BIGINT NOT NULL,
  4. product_id BIGINT NOT NULL,
  5. status TINYINT DEFAULT 0 COMMENT '0-待支付 1-已支付 2-已取消',
  6. create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
  7. UNIQUE KEY uk_user_product (user_id, product_id)
  8. ) PARTITION BY HASH(user_id) PARTITIONS 8;

三、高并发挑战与解决方案

1. 超卖问题防治

实施”三重校验”机制:

  1. Redis预减库存:过滤90%的无效请求
  2. 数据库唯一索引:防止重复下单
  3. 分布式锁:对热点商品加锁(Redisson实现)

Redisson分布式锁示例:

  1. RLock lock = redissonClient.getLock("seckill_lock:" + productId);
  2. try {
  3. boolean isLocked = lock.tryLock(3, 10, TimeUnit.SECONDS);
  4. if (isLocked) {
  5. // 执行业务逻辑
  6. }
  7. } finally {
  8. if (lock.isHeldByCurrentThread()) {
  9. lock.unlock();
  10. }
  11. }

2. 流量削峰策略

采用”漏斗+令牌桶”算法:

  • 前端限流:每个用户每秒最多2次请求
  • 网关层限流:Nginx配置limit_req_zone
  • 服务层限流:Guava RateLimiter实现

Nginx限流配置示例:

  1. http {
  2. limit_req_zone $binary_remote_addr zone=seckill:10m rate=20r/s;
  3. server {
  4. location /api/seckill {
  5. limit_req zone=seckill burst=50 nodelay;
  6. proxy_pass http://backend;
  7. }
  8. }
  9. }

3. 异步处理架构

构建”同步转异步”的处理流程:

  1. 用户请求→2. 内存队列缓冲→3. 消息队列持久化→4. 消费者处理

RabbitMQ消费者示例:

  1. @RabbitListener(queues = "seckill.queue")
  2. public void processSeckill(SeckillMessage message) {
  3. // 查询用户信息
  4. // 校验库存(二次确认)
  5. // 创建订单
  6. // 发送通知
  7. }

四、测试与优化实践

1. 全链路压力测试

使用JMeter构建测试场景:

  • 阶梯式加压:从100并发逐步增加到2000
  • 混合场景:80%秒杀请求+20%常规请求
  • 监控指标:QPS、错误率、响应时间

测试报告关键发现:

  • Redis操作平均耗时0.8ms
  • 消息队列处理延迟<50ms
  • 系统在1500并发时出现瓶颈

2. 性能优化方案

实施三项关键优化:

  1. 连接池调优:HikariCP配置maximumPoolSize=50
  2. JVM参数优化-Xms2g -Xmx2g -XX:+UseG1GC
  3. 网络优化:启用HTTP长连接和压缩

优化后性能对比:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|———————|————|————|—————|
| 平均响应时间 | 120ms | 65ms | 46% |
| 错误率 | 3.2% | 0.8% | 75% |
| 最大QPS | 1800 | 3200 | 78% |

五、项目总结与经验沉淀

经过两周的开发和一周的优化,系统最终达到以下指标:

  • 支持3000+并发请求
  • 库存准确性100%
  • 平均响应时间<80ms
  • 系统可用性99.95%

关键经验总结:

  1. 渐进式架构:从简单到复杂逐步演进
  2. 防御性编程:所有接口都要考虑异常情况
  3. 数据驱动优化:通过监控指标指导优化方向
  4. 文档先行:开发前编写API文档和时序图

对开发者的建议:

  1. 先实现核心功能,再考虑扩展性
  2. 使用成熟的中间件而非重复造轮子
  3. 重视全链路压测,提前发现瓶颈
  4. 建立完善的监控告警体系

这个项目不仅让女友掌握了系统开发的全流程,也让我重新思考了高并发系统的设计原则。后续计划将系统升级为Service Mesh架构,进一步提升服务的可观测性和弹性。