本文目录导读:

我来为您提供一个全面的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复盘案例对您有帮助!建议结合实际项目经验,持续总结和优化。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播