Java复盘案例

wen java案例 1

本文目录导读:

Java复盘案例

  1. 项目复盘案例:电商订单系统
  2. 高频面试题复盘
  3. 经验教训总结
  4. 学习建议

我来为您提供一个全面的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复盘案例对您有帮助!建议结合实际项目经验,持续总结和优化。

抱歉,评论功能暂时关闭!