互联网大厂 Java 面试实战:基于 Spring Boot + Kafka + Redis + MySQL 的电商大促订单系统设计与实现
互联网大厂 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
二、业务场景:大促秒杀下单
假设我们要设计一个电商系统中的秒杀下单流程:
- 用户登录后进入商品详情页
- 系统展示剩余库存
- 用户点击“立即抢购”
- 系统校验登录态、库存、重复下单
- 创建订单
- 发送消息到 Kafka,异步完成扣库存、发券、通知等操作
- 前端轮询或 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 后端工程师所需的系统设计能力。
如果你愿意,我还可以继续帮你生成以下任意一种版本:
- 更像面试回答稿的版本
- 更像公众号技术文章的版本
- 以“高并发秒杀系统”为主题的完整版架构文章
- 换成“AIGC/音视频/金融风控/在线教育”等场景重新生成
更多推荐

所有评论(0)