本文目录导读:

我来为您提供一个全面的Java复盘案例,包含典型面试问题和实际项目经验总结。
项目复盘案例:电商订单系统
项目背景
技术栈:Spring Boot + Spring Cloud + MyBatis + Redis + RabbitMQ
负责模块:订单管理、库存管理、支付回调处理
核心功能实现复盘
订单创建(含分布式事务)
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockService stockService;
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 复盘点:如何保证订单创建和库存扣减的一致性
* 方案:最终一致性方案(本地消息表 + MQ)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Order createOrder(OrderCreateDTO dto) {
// 1. 校验参数
validateParams(dto);
// 2. 生成订单号
String orderNo = generateOrderNo();
// 3. 创建订单
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(dto.getUserId());
order.setTotalAmount(dto.getTotalAmount());
order.setStatus(OrderStatus.CREATED);
orderMapper.insert(order);
// 4. 发送消息扣减库存(异步)
StockDeductMessage message = new StockDeductMessage();
message.setOrderNo(orderNo);
message.setProductId(dto.getProductId());
message.setQuantity(dto.getQuantity());
rabbitTemplate.convertAndSend(
"stock.exchange",
"stock.deduct",
message
);
return order;
}
/**
* 复盘点:幂等性处理
*/
@Override
public void handlePaymentCallback(PaymentCallbackDTO callback) {
// 使用Redis防止重复支付
String payLockKey = "pay:lock:" + callback.getOrderNo();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(payLockKey, "1", 10, TimeUnit.MINUTES);
if (!locked) {
log.warn("重复支付回调:{}", callback.getOrderNo());
return;
}
try {
// 处理支付成功逻辑
updateOrderStatus(callback.getOrderNo(),
OrderStatus.PAID);
// 发送通知消息
sendPaymentSuccessNotification(callback.getOrderNo());
} finally {
// 支付回调处理完成10分钟后才释放锁
// redisTemplate.delete(payLockKey);
}
}
}
性能优化复盘
缓存优化
@Service
@Slf4j
public class ProductService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ProductMapper productMapper;
/**
* 复盘点:缓存穿透、击穿、雪崩的解决方案
*/
public Product getProductById(Long productId) {
// 1. 查询缓存
String key = "product:" + productId;
String cacheValue = redisTemplate.opsForValue()
.get(key);
if (StringUtils.hasText(cacheValue)) {
// 防止缓存穿透:缓存空值
if ("EMPTY".equals(cacheValue)) {
return null;
}
return JSON.parseObject(cacheValue, Product.class);
}
// 2. 使用互斥锁防止缓存击穿
String lockKey = "lock:product:" + productId;
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
// 没获取到锁,暂时返回(可重试或返回默认值)
Thread.sleep(100);
return getProductById(productId);
}
// 3. 查询数据库
Product product = productMapper.selectById(productId);
// 4. 设置缓存(包含过期时间和随机值防止雪崩)
if (product != null) {
redisTemplate.opsForValue().set(
key,
JSON.toJSONString(product),
30 + new Random().nextInt(60),
TimeUnit.MINUTES
);
} else {
// 缓存空值
redisTemplate.opsForValue().set(
key, "EMPTY", 3, TimeUnit.MINUTES);
}
return product;
} catch (InterruptedException e) {
log.error("获取产品信息出现异常", e);
Thread.currentThread().interrupt();
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
return null;
}
}
并发处理复盘
@Service
@Slf4j
public class StockServiceImpl implements StockService {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 复盘点:秒杀场景下的超卖问题
* 解决方案:Redis预减库存 + Lua脚本原子操作
*/
@Override
public Boolean deductStock(StockDeductMessage message) {
// 1. 使用Lua脚本原子性扣减库存
String luaScript =
"local current = redis.call('get', KEYS[1]) " +
"if not current then " +
" return 0 " +
"end " +
"local quantity = tonumber(current) " +
"if quantity >= tonumber(ARGV[1]) then " +
" redis.call('decrby', KEYS[1], ARGV[1]) " +
" return 1 " +
"end " +
"return 0";
DefaultRedisScript<Long> script =
new DefaultRedisScript<>(luaScript, Long.class);
List<String> keys = Collections.singletonList(
"stock:" + message.getProductId());
Long result = redisTemplate.execute(
script, keys, message.getQuantity().toString());
if (result != null && result == 1) {
log.info("扣减库存成功:{}", message.getProductId());
// 发送延迟消息,用于超时关单回滚库存
sendStockTimeoutMessage(message);
return true;
}
log.warn("库存不足:{}", message.getProductId());
return false;
}
}
高频面试题复盘
HashSet为什么不安全
/**
* 复盘点:HashSet的线程安全性分析
*/
public class HashSetAnalysis {
public static void main(String[] args) {
// 1. HashSet底层是HashMap
// 2. 多个线程同时put可能导致数据丢失
// 3. 扩容时可能导致链表环
// 线程不安全示例
Set<String> set = new HashSet<>();
// 问题复现:并发添加
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 100; j++) {
set.add(Thread.currentThread().getName()
+ j);
}
}).start();
}
// 解决方案
// 1. 使用Collections.synchronizedSet()
Set<String> syncSet =
Collections.synchronizedSet(new HashSet<>());
// 2. 使用CopyOnWriteArraySet
Set<String> copyOnWriteSet =
new CopyOnWriteArraySet<>();
// 3. 使用ConcurrentHashMap.newKeySet()
Set<String> concurrentSet =
ConcurrentHashMap.newKeySet();
}
}
数据库索引优化复盘
-- 复盘点:索引失效的典型场景 -- 1. 联合索引不满足最左前缀 CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time); -- 失效:跳过了第一列 SELECT * FROM orders WHERE status = 'PAID' AND create_time > '2024-01-01'; -- 正确:满足最左前缀 SELECT * FROM orders WHERE user_id = 1001 AND status = 'PAID'; -- 2. 对索引列使用函数 -- 失效 SELECT * FROM orders WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2024-01-01'; -- 优化 SELECT * FROM orders WHERE create_time BETWEEN '2024-01-01 00:00:00' AND '2024-01-01 23:59:59'; -- 3. 隐式类型转换 -- 失效 SELECT * FROM orders WHERE phone = 13800138000; -- phone是varchar -- 正确 SELECT * FROM orders WHERE phone = '13800138000';
JVM调优复盘
/**
* 复盘点:JVM内存模型和调优策略
*/
public class JVMOptimization {
public static void main(String[] args) {
// JVM启动参数示例
/*
-Xms2g 初始堆大小
-Xmx2g 最大堆大小
-Xmn512m 年轻代大小
-XX:MetaspaceSize=256m 元空间大小
-XX:MaxMetaspaceSize=512m
-XX:+UseG1GC 使用G1垃圾回收器
-XX:MaxGCPauseMillis=200 目标停顿时间
-XX:SurvivorRatio=8 Eden与Survivor比例
// GC日志分析
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:/path/to/gc.log
*/
// 常见OOM场景及处理
// 1. 堆溢出(Heap Space):
// - 增加堆大小
// - 优化对象生命周期
// - 检查内存泄漏
// 2. 元空间溢出(Metaspace):
// - 增加Metaspace大小
// - 检查动态生成类是否过多
// 3. 栈溢出(StackOverflow):
// - 递归调用过深
// - 检查递归是否有终止条件
// 4. 直接内存溢出:
// - 检查NIO和Netty使用
// - 及时释放DirectBuffer
}
}
经验教训总结
代码设计要点
// 1. 使用策略模式替代大量if-else
public interface StrategyInterface {
void execute(Object params);
}
// 2. 使用Builder模式构建复杂对象
Order order = Order.builder()
.id(1L)
.orderNo("20240101001")
.userId(1001L)
.totalAmount(new BigDecimal("99.90"))
.build();
// 3. 避免用自动拆箱导致NPE
// 错误示例
Integer count = null;
if (count == 5) { // NPE
}
// 正确示例
if (count != null && count == 5) {
}
性能优化经验
// 1. 合理使用Stream并行流
// 大数据量时谨慎使用并行流
list.parallelStream().forEach(item -> {
// 可能会引发线程安全问题
});
// 2. 避免频繁创建大对象
// 使用对象池或复用对象
// 3. 批量操作优于循环单条操作
// 批量插入
private void batchInsert(List<Order> orders) {
int batchSize = 1000;
// 分批插入提高性能
}
排错经验
/**
* 线上问题排查步骤
*/
public class Troubleshooting {
public void troubleshootingSteps() {
// 1. 首先查看异常日志
// 2. 使用arthas等工具排查
// 3. 检查数据库慢查询
// 4. 检查Redis和MQ等中间件状态
// 5. 监控JVM情况
// 线上快速的临时处理
// 1. 降级:关闭非核心功能
// 2. 限流:实施熔断或者减少流量
// 3. 扩容:增加服务实例
// 4. 回滚:回滚到稳定版本
}
}
学习建议
项目复盘清单
- [ ] 梳理项目核心业务流程
- [ ] 标注系统的难点和解决方案
- [ ] 总结遇到的bug和排查过程
- [ ] 记录性能优化手段
- [ ] 明确个人职责和贡献点
面试准备重点
- 项目背景、规模、技术栈
- 核心功能设计思路
- 遇到的问题及解决过程
- 架构演进和优化思路
希望这份Java复盘案例对您有帮助!建议结合实际项目经验,持续总结和优化。