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

一、业务场景

电商大促期间(如双11),热门商品(如限量手机)面临瞬时百万级QPS请求。传统同步下单流程(查库存→扣库存→生成订单→支付)极易因数据库行锁、连接池耗尽、响应延迟导致系统雪崩。

二、技术选型与架构设计

  • 核心框架:Spring Boot 3.2(基于Java 17 + Jakarta EE 9)
  • API网关:Spring Cloud Gateway(路由+全局限流)
  • 库存管理服务:独立微服务 + Redis原子操作(DECR + EXPIRE
  • 订单服务:响应式编程(Spring WebFlux) + R2DBC异步写入MySQL
  • 消息解耦:Kafka作为削峰填谷中枢,解耦下单与订单落库
  • 高可用保障:Resilience4j熔断降级 + Sentinel热点参数限流

三、关键代码实现

1. 秒杀接口(带令牌桶限流)

@RestController
@RequestMapping("/api/seckill")
public class SeckillController {
    
    @Autowired private SeckillService seckillService;
    
    @PostMapping("/{skuId}")
    public Mono<ResponseEntity<String>> doSeckill(
            @PathVariable String skuId,
            @RequestHeader("X-User-ID") String userId) {
        return Mono.fromCallable(() -> seckillService.trySeckill(skuId, userId))
                .onErrorResume(e -> Mono.just(ResponseEntity.badRequest().body("秒杀失败: " + e.getMessage())))
                .map(result -> ResponseEntity.ok(result));
    }
}

2. 原子化库存扣减(Redis Lua脚本)

// Lua script for atomic inventory check & decrement
String script = "if redis.call('exists', KEYS[1]) == 1 then " +
                "  local stock = tonumber(redis.call('get', KEYS[1])); " +
                "  if stock > 0 then " +
                "    redis.call('decr', KEYS[1]); " +
                "    return 1; " +
                "  else " +
                "    return -1; " +
                "  end " +
                "else " +
                "  return 0; " +
                "end";

Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), 
        Collections.singletonList("seckill:stock:" + skuId));

3. 异步下单(Kafka生产者)

@Service
public class KafkaOrderProducer {
    @Autowired private KafkaTemplate<String, SeckillOrderEvent> kafkaTemplate;
    
    public void sendOrderEvent(SeckillOrderEvent event) {
        kafkaTemplate.send("seckill-order-topic", event.getUserId(), event);
    }
}

四、面试官追问(模拟真实技术深挖)

  1. Redis单线程模型为何能支撑高并发秒杀?Lua脚本执行期间是否阻塞其他命令?
  2. 若Kafka消费者积压,如何保证订单不丢失且最终一致性?请结合事务消息或死信队列设计。
  3. 如何防止黄牛使用脚本刷单?请从网关层(设备指纹+行为分析)、服务层(滑动窗口限频)给出方案。

五、答案详解(小白友好)

问题1答案:Redis单线程靠避免上下文切换+纯内存操作+IO多路复用(epoll)实现高性能;Lua脚本是原子执行的,期间会阻塞其他命令——这正是我们依赖它做库存强一致的原因。但要注意脚本执行时间不能过长(<10ms),否则影响吞吐。

问题2答案:采用Kafka事务消息(initTransactions() + sendOffsetsToTransaction())确保“消息发送”与“本地数据库更新”原子性;若消费失败,配置max.poll.interval.msenable.auto.commit=false,手动提交offset,并将失败消息转入DLQ主题,由定时任务重试+人工介入。

问题3答案:网关层集成OpenResty+Lua生成设备唯一指纹(CPU核数+屏幕分辨率+字体列表Hash),并记录用户IP+UA的请求速率(每分钟≤5次);服务层用Redis+ZSET实现滑动窗口限频:ZREMRANGEBYSCORE key -inf (now-60) + ZCARD统计当前窗口请求数。


本文覆盖Spring Boot、Redis、Kafka、R2DBC、Resilience4j等12+技术栈,适合作为Java高级工程师面试准备材料。欢迎在评论区讨论优化方案!

Logo

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

更多推荐