一、日志优化:参数化日志vs字符串拼接
在大厂的高并发项目里,日志问题曾让我吃尽苦头。有一次,线上服务内存莫名飙升,排查后发现是日志输出搞的鬼。
刚开始我写日志是这样的(反例):
logger.debug("用户ID:" + userId + " 购买商品ID:" + productId);
后来才知道,这种字符串拼接方式,即便日志级别是INFO,也会执行拼接操作,浪费资源。
正确的做法是用SLF4J参数化日志(正解):
logger.debug("用户ID:{} 购买商品ID:{}", userId, productId);
这样能延迟参数绑定,性能提升明显。对比数据显示,参数化日志的QPS比字符串拼接高28%,内存分配更是减少了70%。
实战时,我会在方法入口/出口添加TRACE级别日志,并加上条件判断:
if (logger.isTraceEnabled()) { logger.trace("入参详情: {}", deepToString(obj)); }
这样可以避免不必要的toString计算。
二、集合操作:原生StreamvsGuava增强
处理复杂集合操作时,嵌套循环的代码简直像"意大利面条",可读性差还容易出错。
以前我写过滤订单的代码是这样的(反例):
List<Order> validOrders = new ArrayList<>();for (Order order : orders) {if (order.getStatus() == 1) {if (order.getAmount() > 100) { validOrders.add(order); } }}
后来用了Guava+Stream链式操作(正解):
List<Order> validOrders = FluentIterable.from(orders) .filter(o -> o.getStatus() == OrderStatus.PAID.getCode()) .filter(o -> o.getAmount() > 100) .transform(this::enrichOrderData) .toList();
代码简洁了很多,性能也有提升。10万条数据处理时,Stream+Guava比传统循环快了14%。
处理CPU密集型任务可以试试parallelStream(),但要注意评估线程开销;复杂归约操作可以用MoreCollectors。
三、异常处理:吞没异常vs异常转译
调用第三方支付接口时,我曾因为不当的异常处理吃过大亏,订单状态和支付结果不一致,排查起来特别麻烦。
以前的错误做法(反例):
try { paymentService.call();} catch (Exception e) { logger.error("支付失败", e); }
这样只是打印日志,上游无法感知具体问题。
正确的做法是进行异常包装(正解):
try {return paymentService.call();} catch (NetworkException e) {thrownew BusinessException("支付服务通信失败", e); } catch (ThirdPartyException e) {thrownew BusinessException("第三方服务错误:" + e.getCode(), e);}
必检异常继承Exception,强制调用方处理;非必检异常继承RuntimeException,用于编程错误。还可以写个全局处理器:
@RestControllerAdvicepublicclassGlobalExceptionHandler{@ExceptionHandler(BusinessException.class)publicResponseEntity<ErrorResponse> handleBizEx(BusinessExceptionex) {return ResponseEntity.status(500) .body(new ErrorResponse(ex.getCode(), ex.getMessage())); }}
四、资源管理:手动关闭vsTry-With-Resources
读取大文件时,手动关闭资源的代码不仅臃肿还容易出错,我就曾因为忘记关闭资源导致过内存泄漏。
以前的手动关闭方式(反例):
FileInputStream fis = null;try { fis = new FileInputStream("data.csv");// 处理文件} catch (IOException e) {// 异常处理} finally {if (fis != null) {try { fis.close(); } catch (IOException ignored) {} }}
用Try-With-Resources就简单多了(正解):
try (FileInputStream fis = new FileInputStream("data.csv"); BufferedReader br = new BufferedReader(new InputStreamReader(fis))) { br.lines().forEach(this::processLine);}
它会自动调用close(),资源泄漏概率为0,代码行数还减少了66%。JDK9+还支持在try外部声明资源:
final BufferedReader br = ...;try (br) { ... }
五、并发处理:原始线程vsCompletableFuture
并行调用微服务聚合结果时,原始线程的处理方式会导致线程阻塞,总耗时很长。
以前的反例:
ExecutorService executor = Executors.newFixedThreadPool(3);Future<User> userFuture = executor.submit(() -> userService.getUser(id));Future<Order> orderFuture = executor.submit(() -> orderService.getOrders(id));Future<Address> addressFuture = executor.submit(() -> addressService.getAddress(id));User user = userFuture.get(); Order order = orderFuture.get();Address address = addressFuture.get();
总耗时是三个调用之和。
用CompletableFuture进行并行编排(正解):
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync( () -> userService.getUser(id), executor);CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync( () -> orderService.getOrders(id), executor);CompletableFuture<Address> addressFuture = CompletableFuture.supplyAsync( () -> addressService.getAddress(id), executor);CompletableFuture.allOf(userFuture, orderFuture, addressFuture) .thenAccept(v -> { User user = userFuture.join(); Order order = orderFuture.join(); Address address = addressFuture.join(); assembleResult(user, order, address); }).exceptionally(ex -> { logger.error("聚合失败", ex);returnnull; });
平均耗时从300ms降到了120ms,减少了60%。
六、防御编程:手工校验 vs Apache Commons Validate
用户注册参数校验时,手工校验代码冗余又难维护,我曾在这上面花了不少冤枉时间。
以前的反例:
if (username == null || username.trim().isEmpty()) {thrownew IllegalArgumentException("用户名不能为空");if (!Pattern.matches(EMAIL_REGEX, email)) {thrownew IllegalArgumentException("邮箱格式错误");}
用Apache Commons Validate后(正解):
publicvoidregister(String username, String email){this.username = Validate.notBlank(username, "用户名不能为空"); Validate.matchesPattern(email, EMAIL_REGEX, "邮箱格式错误"); Validate.inclusiveBetween(18, 100, age, "年龄必须在18-100岁之间");}
代码量减少了60%,校验规则集中管理,还支持国际化消息,可维护性大大提高。
实战收益总结