文章目录
-
- 一、故障现象
- 二、初步排查
-
- 2.1 系统资源监控
- 2.2 日志文件分析
- 三、Java进程深入分析
-
- 3.1 线程dump分析
- 3.2 CPU热点分析
- 四、日志配置检查
-
- 4.1 查看日志配置文件
- 4.2 发现问题
- 五、代码审查
-
- 5.1 定位高频率日志代码
- 5.2 日志性能测试
- 六、优化方案
-
- 6.1 调整日志级别
- 6.2 优化日志格式
- 6.3 启用异步日志
- 6.4 代码优化
- 七、优化效果验证
-
- 7.1 系统资源监控
- 7.2 应用性能测试
- 7.3 日志文件大小
- 八、预防措施
-
- 8.1 日志规范制定
- 8.2 定期日志审计
- 8.3 日志性能监控
- 总结
一、故障现象
某电商平台的订单服务突然出现响应缓慢,吞吐量下降的情况。监控系统显示:
二、初步排查
2.1 系统资源监控
使用top命令查看系统资源使用情况:
top -c
发现Java进程(PID=12345)占用CPU高达150%(多核系统),且RES内存使用持续增长。
使用iostat查看磁盘I/O:
iostat -x 1
结果显示磁盘%util接近100%,wr/s(写操作每秒)达到500MB以上。
2.2 日志文件分析
检查Java应用日志目录,发现order-service.log文件每小时增长约5GB,且日志滚动频繁。
三、Java进程深入分析
3.1 线程dump分析
生成Java进程的线程dump:
jstack 12345 > thread_dump.txt
分析线程dump文件,发现大量线程处于RUNNABLE状态,且集中在日志相关类:
"http-nio-8080-exec-10" #25 daemon prio=5 os_prio=0 tid=0x00007f9c000c5800 nid=0x303b runnable [0x00007f9bfd1fd000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
at org.apache.logging.log4j.core.appender.OutputStreamManager.flush(OutputStreamManager.java:156)
at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:177)
…
3.2 CPU热点分析
使用async-profiler分析CPU热点:
# 安装async-profiler
wget https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.8.3/async-profiler-2.8.3-linux-x64.tar.gz
tar -zxvf async-profiler-2.8.3-linux-x64.tar.gz
# 采集CPU热点数据(持续30秒)
./profiler.sh -d 30 -f cpu_profile.html 12345
打开生成的cpu_profile.html,发现日志相关方法占用了约40%的CPU时间:
四、日志配置检查
4.1 查看日志配置文件
检查应用的log4j2.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L – %m%n"/>
</Console>
<RollingFile name="File" fileName="order-service.log"
filePattern="order-service.%d{yyyy-MM-dd}.log.gz">
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1}:%L – %m%n</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy/>
<SizeBasedTriggeringPolicy size="100 MB"/>
</Policies>
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
<!– 业务日志配置 –>
<Logger name="com.example.order" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Logger>
</Loggers>
</Configuration>
4.2 发现问题
五、代码审查
5.1 定位高频率日志代码
使用grep查找高频日志代码:
grep -r "logger.debug" src/main/java/com/example/order/
发现以下典型问题代码:
// 订单处理服务
public class OrderService {
private static final Logger logger = LogManager.getLogger(OrderService.class);
public Order processOrder(Order order) {
// 问题1:循环内打印debug日志
for (OrderItem item : order.getItems()) {
logger.debug("处理订单项: {}", item); // 每次循环都打印
// 业务逻辑
processItem(item);
}
// 问题2:复杂对象直接打印
logger.debug("订单处理完成: {}", order); // 包含大量字段的复杂对象
// 问题3:未检查日志级别直接构建参数
logger.debug("计算订单总金额: {}", calculateTotal(order)); // 无论日志是否启用都会执行计算
return order;
}
private BigDecimal calculateTotal(Order order) {
// 复杂计算逻辑
return order.getItems().stream()
.map(item -> item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
5.2 日志性能测试
编写简单的性能测试代码:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class LoggingPerformanceTest {
private static final Logger logger = LogManager.getLogger(LoggingPerformanceTest.class);
public static void main(String[] args) {
int count = 100000;
// 测试未检查日志级别
long start1 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
logger.debug("未检查日志级别: {}", i);
}
long end1 = System.currentTimeMillis();
// 测试检查日志级别
long start2 = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
if (logger.isDebugEnabled()) {
logger.debug("检查日志级别: {}", i);
}
}
long end2 = System.currentTimeMillis();
System.out.println("未检查日志级别耗时: " + (end1 – start1) + "ms");
System.out.println("检查日志级别耗时: " + (end2 – start2) + "ms");
}
}
测试结果(DEBUG级别开启时):
未检查日志级别耗时: 123ms
检查日志级别耗时: 118ms
测试结果(DEBUG级别关闭时):
未检查日志级别耗时: 98ms
检查日志级别耗时: 2ms
六、优化方案
6.1 调整日志级别
将业务包日志级别从debug调整为info:
<Logger name="com.example.order" level="info" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Logger>
6.2 优化日志格式
移除行号和类名,减少日志处理开销:
<PatternLayout>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p – %m%n</pattern>
</PatternLayout>
6.3 启用异步日志
配置异步日志Appender:
<Appenders>
<!– 其他Appender保持不变 –>
<Async name="AsyncFile">
<AppenderRef ref="File"/>
</Async>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="AsyncFile"/> <!– 使用异步Appender –>
</Root>
</Loggers>
6.4 代码优化
修改问题代码:
public class OrderService {
private static final Logger logger = LogManager.getLogger(OrderService.class);
public Order processOrder(Order order) {
// 只在必要时打印概要信息
if (logger.isDebugEnabled()) {
logger.debug("开始处理订单,包含{}个订单项", order.getItems().size());
}
for (OrderItem item : order.getItems()) {
processItem(item);
}
// 避免打印完整对象
if (logger.isDebugEnabled()) {
logger.debug("订单处理完成,订单ID: {}", order.getId());
}
// 检查日志级别后再执行复杂计算
if (logger.isDebugEnabled()) {
logger.debug("订单总金额: {}", calculateTotal(order));
}
return order;
}
}
七、优化效果验证
7.1 系统资源监控
优化后再次使用top和iostat监控系统资源:
- Java进程CPU使用率从150%降至30%
- 磁盘wr/s从500MB/s降至50MB/s
- 系统负载平均从8.0降至1.2
7.2 应用性能测试
使用JMeter进行压测,结果如下:
平均响应时间 | 500ms | 45ms | 91% |
最大响应时间 | 1800ms | 120ms | 93% |
吞吐量(QPS) | 200 | 1800 | 800% |
错误率 | 5.2% | 0.1% | 98% |
7.3 日志文件大小
优化后日志文件每小时生成量从5GB降至约200MB,减少了96%。
八、预防措施
8.1 日志规范制定
8.2 定期日志审计
编写脚本定期检查日志配置和代码中的日志使用情况:
#!/bin/bash
# 检查项目中所有debug日志
grep -r "logger.debug" src/main/java/ | grep -v "@Test" > debug_logs.txt
# 检查循环内的日志
grep -r -E "for \\(.+\\) \\{.*logger\\.(debug|info|warn|error)" src/main/java/ > loop_logs.txt
echo "日志审计完成,请查看debug_logs.txt和loop_logs.txt"
8.3 日志性能监控
在监控系统中添加日志相关监控指标:
总结
通过本次故障排查,我们发现日志打印过多对系统性能的影响非常显著。主要原因包括:日志级别配置不合理、低效的日志格式、未优化的日志代码以及同步日志带来的I/O瓶颈。通过调整日志级别、优化日志格式、启用异步日志和改进日志代码,系统性能得到了显著提升。在日常开发和运维中,我们应该重视日志管理,制定合理的日志规范,并定期进行日志审计,以避免类似问题的发生。"
评论前必须登录!
注册