互联网大厂 Java 面试实战:基于 Spring Boot + Kafka + Redis + MySQL 的电商大促订单系统设计与实现

一、为什么这个题目适合大厂面试

在互联网大厂 Java 面试中,面试官通常不会只问你“会不会 Spring Boot”,而是会结合真实业务场景,追问你:

  • 高并发下如何防止重复下单?
  • 如何保证消息队列和数据库最终一致性?
  • Redis 缓存和数据库如何设计?
  • Kafka 消费失败如何处理?
  • 如何做限流、熔断和降级?
  • 如何设计幂等性?
  • 如何监控系统的 SLA?

这些问题看似分散,实则都围绕一个核心:你是否具备从业务到架构、从代码到稳定性的全链路设计能力

因此,本文以电商大促订单系统为例,围绕如下技术栈展开:

  • Java 11 / 17
  • Spring Boot / Spring MVC
  • MyBatis / JPA
  • Redis / Caffeine
  • Kafka
  • Spring Security / JWT
  • Resilience4j
  • Micrometer / Prometheus / Grafana
  • Docker / Kubernetes
  • JUnit 5 / Mockito

二、业务场景:大促秒杀下单

假设我们要设计一个电商系统中的秒杀下单流程:

  1. 用户登录后进入商品详情页
  2. 系统展示剩余库存
  3. 用户点击“立即抢购”
  4. 系统校验登录态、库存、重复下单
  5. 创建订单
  6. 发送消息到 Kafka,异步完成扣库存、发券、通知等操作
  7. 前端轮询或 WebSocket 获取订单状态

核心挑战

  • 高并发下库存不能超卖
  • 同一用户不能重复下单
  • 订单创建要快,避免同步调用链过长
  • 下游服务失败不能影响主流程
  • 系统要可观测、可恢复、可扩展

三、总体架构设计

1)系统分层

  • Controller 层:接收请求,做参数校验
  • Service 层:执行业务逻辑
  • Repository / Mapper 层:操作数据库
  • Cache 层:Redis + Caffeine 做热点数据缓存
  • MQ 层:Kafka 做异步事件流转
  • Security 层:JWT 鉴权
  • Resilience 层:熔断、限流、降级
  • Observability 层:Micrometer + Prometheus 采集指标

2)核心链路

用户请求
  -> Spring Security 鉴权
  -> Redis 校验库存与重复购买
  -> 本地事务创建订单
  -> 发送 Kafka 订单创建事件
  -> 异步扣减库存/发通知
  -> 返回“下单成功,待支付”

四、关键设计:如何防止超卖和重复下单

1)Redis 预扣库存 + Lua 脚本

秒杀场景中,不能直接打 MySQL 库存,否则数据库扛不住。常见做法是:

  • 将库存预热到 Redis
  • 用户下单时先通过 Redis 原子扣减
  • 成功后再异步落库

Redis 原子扣减建议使用 Lua 脚本,避免并发条件竞争。

Lua 脚本示例
local stockKey = KEYS[1]
local userKey = KEYS[2]

if redis.call("SISMEMBER", userKey, ARGV[1]) == 1 then
    return -2
end

local stock = tonumber(redis.call("GET", stockKey))
if stock <= 0 then
    return -1
end

redis.call("DECR", stockKey)
redis.call("SADD", userKey, ARGV[1])
return 1

2)Spring Boot 中调用 Lua

@Service
public class SeckillService {

    private final StringRedisTemplate redisTemplate;

    public SeckillService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public boolean trySeckill(Long userId, Long productId) {
        String stockKey = "seckill:stock:" + productId;
        String userKey = "seckill:users:" + productId;

        String lua = """
            local stockKey = KEYS[1]
            local userKey = KEYS[2]
            if redis.call("SISMEMBER", userKey, ARGV[1]) == 1 then
                return -2
            end
            local stock = tonumber(redis.call("GET", stockKey))
            if stock <= 0 then
                return -1
            end
            redis.call("DECR", stockKey)
            redis.call("SADD", userKey, ARGV[1])
            return 1
        """;

        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(lua);
        script.setResultType(Long.class);

        Long result = redisTemplate.execute(
                script,
                Arrays.asList(stockKey, userKey),
                userId.toString()
        );

        return Long.valueOf(1).equals(result);
    }
}

面试官可能追问

  • 为什么 Lua 比 Java 代码 + Redis 多次操作更安全?
  • Redis 预扣失败后如何补偿?
  • 如何防止 Redis 和数据库库存不一致?

你的回答应强调:Redis 负责高并发削峰,数据库负责最终一致性,系统通过 MQ 和补偿机制收敛一致性

五、订单创建:本地事务 + 事务消息思想

1)为什么不能直接同步调用库存服务

如果订单服务调用库存服务、优惠券服务、积分服务,都用同步 RPC:

  • 链路过长
  • 任一服务超时会拖慢主流程
  • 容易出现级联故障

因此采用:

  • 主流程只负责创建订单
  • 异步通过 Kafka 传播订单事件
  • 下游服务消费事件后更新状态

2)订单表设计

CREATE TABLE t_order (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(64) NOT NULL UNIQUE,
    user_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    amount DECIMAL(18,2) NOT NULL,
    status VARCHAR(32) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);

3)订单服务代码

@Service
public class OrderService {

    private final OrderMapper orderMapper;
    private final KafkaTemplate<String, String> kafkaTemplate;

    public OrderService(OrderMapper orderMapper, KafkaTemplate<String, String> kafkaTemplate) {
        this.orderMapper = orderMapper;
        this.kafkaTemplate = kafkaTemplate;
    }

    @Transactional
    public String createOrder(Long userId, Long productId, BigDecimal amount) {
        String orderNo = generateOrderNo(userId, productId);

        Order order = new Order();
        order.setOrderNo(orderNo);
        order.setUserId(userId);
        order.setProductId(productId);
        order.setAmount(amount);
        order.setStatus("PENDING_PAY");

        orderMapper.insert(order);

        OrderCreatedEvent event = new OrderCreatedEvent(orderNo, userId, productId, amount);
        kafkaTemplate.send("order-created-topic", orderNo, JsonUtils.toJson(event));

        return orderNo;
    }

    private String generateOrderNo(Long userId, Long productId) {
        return "O" + System.currentTimeMillis() + userId + productId;
    }
}

六、Kafka 消费者设计:幂等性与重试

Kafka 消费是至少一次语义,因此可能重复投递。消费端必须保证幂等。

1)消费端幂等策略

可用以下方式:

  • 数据库唯一键
  • Redis 去重
  • 状态机校验
  • 消费日志表

推荐:数据库唯一键 + 状态机

2)Kafka Consumer 示例

@Service
public class OrderEventConsumer {

    private final OrderMapper orderMapper;
    private final StockService stockService;

    @KafkaListener(topics = "order-created-topic", groupId = "stock-group")
    public void onMessage(String message) {
        OrderCreatedEvent event = JsonUtils.fromJson(message, OrderCreatedEvent.class);

        Order order = orderMapper.selectByOrderNo(event.getOrderNo());
        if (order == null) {
            return;
        }

        if (!"PENDING_PAY".equals(order.getStatus())) {
            return; // 幂等保护
        }

        stockService.deductStock(event.getProductId(), 1);

        orderMapper.updateStatus(event.getOrderNo(), "STOCK_DEDUCTED");
    }
}

七、Resilience4j:限流、熔断、降级

大厂面试里,稳定性是重点。对于商品详情、库存查询等接口,需要做限流和熔断。

1)限流示例

@RestController
@RequestMapping("/api")
public class ProductController {

    private final RateLimiter rateLimiter = RateLimiter.of(
            "productLimiter",
            RateLimiterConfig.custom()
                    .limitForPeriod(100)
                    .limitRefreshPeriod(Duration.ofSeconds(1))
                    .timeoutDuration(Duration.ofMillis(50))
                    .build()
    );

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable Long id) {
        return RateLimiter.decorateSupplier(rateLimiter, () -> "product-" + id)
                .get();
    }
}

2)面试回答重点

  • 为什么限流放在网关和服务双层更稳?
  • 熔断如何防止故障扩散?
  • 降级数据如何设计兜底策略?

八、Spring Security + JWT:接口鉴权

1)JWT 过滤器示例

@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String token = request.getHeader("Authorization");
        if (token != null && token.startsWith("Bearer ")) {
            String jwt = token.substring(7);
            // 解析 JWT,校验签名、过期时间、用户权限
            Authentication auth = JwtUtils.parse(jwt);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

九、可观测性:Micrometer + Prometheus + Grafana

面试官很喜欢问:你的系统怎么监控?

1)自定义指标

@Service
public class OrderMetricService {

    private final Counter orderCounter;

    public OrderMetricService(MeterRegistry meterRegistry) {
        this.orderCounter = Counter.builder("order_created_total")
                .description("Total orders created")
                .register(meterRegistry);
    }

    public void markOrderCreated() {
        orderCounter.increment();
    }
}

2)你可以补充

  • QPS、P95、P99 延迟
  • Kafka 消费堆积
  • Redis 命中率
  • 订单创建成功率
  • 库存扣减失败率

十、单元测试:JUnit 5 + Mockito

测试核心逻辑非常重要,说明你不是只会写业务代码。

@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderMapper orderMapper;

    @Mock
    private KafkaTemplate<String, String> kafkaTemplate;

    @InjectMocks
    private OrderService orderService;

    @Test
    void shouldCreateOrderSuccessfully() {
        String orderNo = orderService.createOrder(1L, 100L, new BigDecimal("99.99"));

        assertThat(orderNo).isNotBlank();
        verify(orderMapper, times(1)).insert(any(Order.class));
        verify(kafkaTemplate, times(1)).send(eq("order-created-topic"), eq(orderNo), anyString());
    }
}

十一、面试答题模板:怎么讲才能更像“大厂候选人”

你可以按这个结构回答:

1)先讲业务目标

“这个系统是大促秒杀场景,核心目标是高并发下不超卖、不过载、可恢复。”

2)再讲核心架构

“我会用 Redis 做库存预热和原子预扣,MySQL 做最终落库,Kafka 做异步事件解耦,Resilience4j 做限流熔断,Micrometer 做可观测性。”

3)最后讲风险和补偿

“由于 Kafka 是至少一次投递,消费端必须幂等;Redis 和 DB 之间通过补偿任务、状态机和唯一键保证最终一致性。”

十二、面试官高频追问与回答方向

问:Redis 预扣库存失败了怎么办?

答:直接返回失败,不进入后续流程;如果 Redis 预扣成功但订单落库失败,需要通过补偿任务恢复库存。

问:Kafka 消息重复怎么办?

答:消费端幂等,依赖订单状态判断、唯一键、去重表。

问:为什么不用事务直接包住订单和消息发送?

答:数据库事务无法天然覆盖消息系统,建议使用事务消息、Outbox 模式或最终一致性方案。

问:如何保证性能?

答:热点数据缓存、本地缓存 Caffeine、异步化、批量写入、数据库索引优化、MQ 削峰。

十三、总结

这类题目在大厂 Java 面试中非常常见,因为它考察的不只是某个框架,而是:

  • Java 基础和并发能力
  • Spring Boot 工程化能力
  • Redis、Kafka、MySQL 的协同设计
  • 分布式一致性思维
  • 可观测性和稳定性建设
  • 单元测试和工程质量

如果你能把本文中的思路讲清楚,再配合代码案例和业务权衡,面试官通常会认为你具备中高级 Java 后端工程师所需的系统设计能力。


如果你愿意,我还可以继续帮你生成以下任意一种版本:

  1. 更像面试回答稿的版本
  2. 更像公众号技术文章的版本
  3. 以“高并发秒杀系统”为主题的完整版架构文章
  4. 换成“AIGC/音视频/金融风控/在线教育”等场景重新生成
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐