Spring Boot微服务架构下的电商秒杀系统设计与实现

1. 场景背景

在大型电商平台(如双11)中,秒杀活动是典型的高并发、低延迟、强一致性的业务场景。用户瞬时流量可达百万QPS,而商品库存仅数百件,极易引发超卖、数据库雪崩、服务宕机等问题。

本文以某一线大厂真实面试题为蓝本,结合Java技术栈(Spring Boot 3.x + JDK 17 + Jakarta EE),从面试提问 → 技术剖析 → 代码落地 → 架构演进全流程展开,适合中高级Java工程师深度学习。

2. 面试三轮递进式提问(附答案详解)

🔹 第一轮:基础建模与单体瓶颈

Q1:请用Spring Boot写一个最简秒杀接口,要求校验库存、扣减库存、生成订单。注意线程安全。

@RestController
public class SeckillController {
    @Autowired private SeckillService seckillService;

    @PostMapping("/seckill/{itemId}")
    public ResponseEntity<String> doSeckill(@PathVariable Long itemId) {
        try {
            boolean success = seckillService.trySeckill(itemId);
            return success ? ResponseEntity.ok("抢购成功") : ResponseEntity.badRequest().body("库存不足");
        } catch (Exception e) {
            return ResponseEntity.status(500).body("系统繁忙");
        }
    }
}

@Service
public class SeckillService {
    @Autowired private ItemRepository itemRepo;
    @Autowired private OrderRepository orderRepo;

    // ❌ 错误示范:DB层面无并发控制 → 超卖!
    public boolean trySeckill(Long itemId) {
        Item item = itemRepo.findById(itemId).orElseThrow();
        if (item.getStock() > 0) {
            item.setStock(item.getStock() - 1);
            itemRepo.save(item);
            orderRepo.save(new Order(itemId));
            return true;
        }
        return false;
    }
}

正确解法(第一层防御):数据库乐观锁 + 原子更新

UPDATE item SET stock = stock - 1 WHERE id = ? AND stock > 0;
@Modifying
count = itemRepo.decreaseStock(itemId); // 返回影响行数
if (count == 0) throw new IllegalStateException("库存已抢光");

🔹 第二轮:性能优化与分布式挑战

Q2:当QPS达10万+,数据库扛不住怎么办?请设计缓存+消息队列异步化方案,并说明Redis如何防止缓存击穿?

架构升级:Redis预减库存 + Kafka异步落库

  • 使用 Redis Lua脚本 原子性执行:GET stock, DECR stock, LPUSH seckill_queue
  • DECR 返回负数 → 拒绝请求(前端友好提示);
  • 成功后发Kafka消息 {"itemId":1001,"userId":123456}seckill-order-topic
  • 消费者组异步创建订单,失败则重试+死信队列告警。

🔒 防击穿方案

  • 热点Key永不过期 + 后台定时刷新;
  • SETNX key_lock 1 EX 30 获取锁,未命中则回源DB并重建缓存;
  • 结合 Caffeine 本地缓存(Guava Cache已弃用),降低Redis压力。

🔹 第三轮:终极一致性与可观测性

Q3:若Kafka消费失败导致订单丢失,或Redis与DB库存不一致,如何保障最终一致性?请给出监控埋点与补偿方案。

分布式事务三板斧

  1. TCC模式(Try-Confirm-Cancel)
    • Try:Redis预占库存(SECKILL:ITEM:1001:PRE)、冻结用户账户余额;
    • Confirm:DB扣库存+生成订单+扣余额(全部成功才提交);
    • Cancel:任一失败则释放预占资源;
  2. Saga模式(推荐)
    • 步骤1:发MQ → 创建订单(状态=PROCESSING);
    • 步骤2:监听MQ → 扣减库存(失败则发逆向MQ:取消订单);
    • 补偿任务:Quartz扫描超时PROCESSING订单,自动回滚;
  3. 监控闭环
    • Micrometer + Prometheus采集指标:seckill_success_rate{env="prod"}
    • ELK收集Kafka消费延迟日志;
    • Grafana看板联动告警(如:rate(kafka_consumergroup_lag{group=~"seckill.*"}[5m]) > 1000)。

3. 技术栈全景图(严格对齐JD要求)

| 类别 | 技术选型 | 说明 | |--------|-----------|------| | 核心平台 | JDK 17 + Spring Boot 3.2 | 支持虚拟线程(spring.threads.virtual.enabled=true)提升吞吐 | | Web框架 | Spring WebFlux | 替代Servlet容器,响应式编程处理长连接推送 | | 缓存 | Redis 7 + Lettuce + Redisson | 分布式锁、延时队列(RDelayedQueue) | | 消息 | Kafka 3.5 + Spring Kafka | Exactly-Once语义 + 事务性生产者 | | ORM | JPA 3.1 + Hibernate 6.2 | @DynamicUpdate 减少SQL冗余 | | 安全 | Spring Security 6 + JWT + OAuth2 Resource Server | 秒杀接口需SCOPE_SECKILL权限 | | 可观测 | Micrometer + Prometheus + Grafana + Jaeger | 全链路TraceId透传 | | CI/CD | GitHub Actions + Docker + K8s Helm | 多环境灰度发布(canary标签) |

4. 小白也能懂的总结

⚡️ 记住三句话

  1. 流量要削峰 → 前端答题/验证码 → Nginx限流 → Redis预减 → Kafka缓冲;
  2. 数据要兜底 → DB主键唯一约束防重复下单 → 定时对账任务校验Redis/DB库存差;
  3. 故障要可见 → 所有关键步骤打log.info("SECKILL_STEP_1", kv("itemId", itemId)) → Micrometer记录Timer.builder("seckill.latency").register(meterRegistry)

📌 延伸思考(面试官最后话术)

“今天的问题覆盖了高并发系统的核心矛盾——一致性 vs 可用性 vs 分区容错性。你展现了扎实的工程能力,但分布式系统的复杂性远不止于此。比如:如何用Resilience4j实现熔断降级?WebSocket如何实时推送抢购结果?欢迎你回家后,用Quarkus重构这个服务,我们下次聊聊云原生下的极致性能优化。祝你拿到心仪的offer!”


© 2024 CSDN技术专栏|转载请注明出处

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐