这场面试对话生动展现了大厂技术面试的典型场景,从电商秒杀系统设计到微服务治理,再到生产环境问题排查,覆盖了Java高级工程师需要掌握的核心技术栈。下面我将从技术角度深度解析每个问题的考察点和最佳实践。
设计一个能承受10万+QPS的秒杀系统,需要考虑以下几个关键层面:
典型架构分层如下:
code复制客户端层 → 接入层(Nginx) → 网关层(Spring Cloud Gateway) → 服务层 → 缓存层(Redis) → 消息队列(Kafka) → 数据层(MySQL)
在面试中提到的Lua脚本方案确实是标准做法,但在生产环境中还需要考虑更多细节:
java复制// 增强版的库存扣减Lua脚本
String script =
"local stockKey = KEYS[1]\n" +
"local stockChange = tonumber(ARGV[1])\n" +
"local versionKey = KEYS[2]\n" +
"local currentVersion = tonumber(redis.call('GET', versionKey))\n" +
"local expectedVersion = tonumber(ARGV[2])\n" +
// 版本号校验
"if currentVersion ~= expectedVersion then\n" +
" return {err='VERSION_MISMATCH'}\n" +
"end\n" +
// 库存校验
"local currentStock = tonumber(redis.call('GET', stockKey))\n" +
"if currentStock < stockChange then\n" +
" return {err='INSUFFICIENT_STOCK'}\n" +
"end\n" +
// 执行扣减
"redis.call('DECRBY', stockKey, stockChange)\n" +
"redis.call('INCRBY', versionKey, 1)\n" +
"return {ok=currentStock - stockChange}";
这个脚本增加了版本号控制,可以有效防止ABA问题。在实际项目中,我建议:
当面试官问到主从切换时的超卖问题,这考察的是对Redis持久化和复制机制的深入理解。我的生产环境解决方案是:
java复制// RedLock实现示例
public boolean safeDeductStock(String productId, int quantity) {
// 获取所有Redis节点的锁
RLock[] locks = new RLock[3];
for (int i = 0; i < 3; i++) {
locks[i] = redissonClient.getLock("lock:" + productId + ":" + i);
}
RedissonRedLock lock = new RedissonRedLock(locks);
try {
// 尝试获取锁,等待时间5秒,锁有效期30秒
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
// 执行库存扣减
boolean success = deductStock(productId, quantity);
if (success) {
// 异步记录扣减日志
mqProducer.send(new StockDeductMessage(productId, quantity));
return true;
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
return false;
}
在秒杀场景下,保证订单消息不丢失至关重要。除了面试中提到的配置外,还需要注意:
java复制// 增强版Kafka生产者配置
@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka1:9092,kafka2:9092");
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
// 高可靠性配置
config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.RETRIES_CONFIG, 5);
config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
// 消息压缩
config.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");
return new DefaultKafkaProducerFactory<>(config);
}
面试中提到的优雅下线方案基本正确,但在生产环境中还需要考虑:
yaml复制# Spring Boot优雅下线增强配置
spring:
lifecycle:
timeout-per-shutdown-phase: 60s # 延长等待时间
management:
endpoint:
health:
probes:
enabled: true
show-details: always
endpoints:
web:
exposure:
include: health,info,metrics
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
一个完整的微服务监控体系应该包括:
java复制// 自定义业务指标监控示例
@RestController
public class OrderController {
private final Counter orderCounter;
private final Timer orderTimer;
public OrderController(MeterRegistry registry) {
this.orderCounter = Counter.builder("order.requests")
.description("Total order requests")
.tag("type", "create")
.register(registry);
this.orderTimer = Timer.builder("order.process.time")
.description("Order processing time")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
}
@PostMapping("/orders")
public ResponseEntity<?> createOrder(@RequestBody Order order) {
return orderTimer.record(() -> {
orderCounter.increment();
// 业务逻辑处理
return ResponseEntity.ok().build();
});
}
}
当出现GC时间变长时,我的标准排查流程是:
收集基础信息:
bash复制# 查看JVM参数
jinfo <pid>
# 查看GC日志
cat gc.log | grep -i 'full gc'
分析内存使用:
bash复制# 查看堆内存分布
jmap -histo <pid> | head -20
# 生成堆转储文件
jmap -dump:live,format=b,file=heap.hprof <pid>
使用MAT分析:
常见问题处理:
除了Seata AT模式,其他主流方案对比:
| 方案 | 一致性 | 性能影响 | 适用场景 | 实现复杂度 |
|---|---|---|---|---|
| 2PC | 强一致 | 高 | 跨库事务 | 中 |
| TCC | 最终一致 | 中 | 资金交易 | 高 |
| Saga | 最终一致 | 低 | 长事务 | 中 |
| 本地消息表 | 最终一致 | 中 | 异步场景 | 中 |
| Seata AT | 最终一致 | 中 | 常规业务 | 低 |
java复制// TCC模式实现示例
public interface OrderTccService {
@TwoPhaseBusinessAction(name = "createOrder", commitMethod = "commit", rollbackMethod = "rollback")
boolean prepare(BusinessActionContext actionContext,
@BusinessActionContextParameter(paramName = "orderId") Long orderId);
boolean commit(BusinessActionContext actionContext);
boolean rollback(BusinessActionContext actionContext);
}
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Override
public boolean prepare(BusinessActionContext actionContext, Long orderId) {
// 预留资源
orderDao.insertTemporary(orderId);
return true;
}
@Override
public boolean commit(BusinessActionContext actionContext) {
// 确认提交
Long orderId = (Long) actionContext.getActionContext("orderId");
return orderDao.confirm(orderId);
}
@Override
public boolean rollback(BusinessActionContext actionContext) {
// 取消预留
Long orderId = (Long) actionContext.getActionContext("orderId");
return orderDao.cancel(orderId);
}
}
当出现大量超时告警时,系统化的排查思路:
确定影响范围:
检查依赖服务:
bash复制# 查看数据库连接池状态
curl http://localhost:8080/actuator/hikari
# 检查Redis响应时间
redis-cli --latency
分析线程状态:
bash复制# 使用Arthas查看线程堆栈
thread -n 5
# 查看阻塞线程
thread -b
网络诊断:
bash复制# 检查网络延迟
ping <target>
# 检查TCP连接状态
netstat -antp | grep <port>
遇到数据库慢查询时的紧急处理步骤:
紧急止血:
sql复制-- 杀死问题会话
KILL <process_id>;
-- 临时增加索引
CREATE INDEX idx_temp ON problem_table(problem_column);
SQL优化:
sql复制-- 使用EXPLAIN分析
EXPLAIN ANALYZE SELECT * FROM large_table WHERE condition;
-- 重写复杂查询
SELECT * FROM orders WHERE id IN (
SELECT order_id FROM order_items WHERE product_id = 123
);
-- 改为JOIN:
SELECT o.* FROM orders o JOIN order_items oi ON o.id = oi.order_id
WHERE oi.product_id = 123;
长期方案:
在生产环境进行JVM调优的黄金法则:
调优原则:
G1GC推荐配置:
bash复制-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
-XX:G1ReservePercent=10
-XX:ConcGCThreads=4
内存设置公式:
code复制推荐堆大小 = 容器内存限制 × 70%
新生代大小 = 堆大小 × 40%
Metaspace = 256MB (根据实际情况调整)
关键监控指标:
构建高可用系统的核心原则:
冗余设计:
故障隔离:
自动化恢复:
监控告警:
mermaid复制graph TD
A[用户] --> B{DNS解析}
B -->|北京区域| C[北京接入层]
B -->|上海区域| D[上海接入层]
C --> E[北京可用区A]
C --> F[北京可用区B]
D --> G[上海可用区A]
D --> H[上海可用区B]
E --> I[服务集群]
F --> I
G --> I
H --> I
I --> J[分布式缓存]
I --> K[消息队列]
I --> L[数据库集群]
L --> M[主库]
L --> N[从库]
L --> O[灾备库]
关键组件说明:
流量调度层:
数据同步层:
容灾切换:
为了验证系统的高可用性,建议定期进行混沌实验:
实验类型:
工具推荐:
bash复制# 网络延迟注入
tc qdisc add dev eth0 root netem delay 100ms
# 模拟CPU满载
stress -c 2 -t 300
# 随机kill进程
while true; do kill -9 $(ps aux | grep java | awk '{print $2}' | shuf -n 1); sleep 30; done
实验流程:
核心原理:
框架源码:
中间件:
使用STAR法则描述项目:
示例回答:
"在电商秒杀系统项目中(S),我负责库存服务的设计(T)。通过引入Redis+Lua脚本实现原子扣减,采用本地缓存降低Redis压力,最终(A)将峰值QPS从1万提升到10万,超卖率降至0.001%以下(R)。"
面对系统设计题时:
当被问到线上问题时:
记住:面试官更关注你的思考过程,而非完美答案。展示你的系统化思维能力和学习能力同样重要。