面试题答案
一键面试实现自定义日志格式化模式
- 使用SLF4J和Logback:
- 在
pom.xml
中添加SLF4J和Logback依赖:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback - classic</artifactId> <version>1.2.11</version> </dependency>
- 在
src/main/resources
下创建logback.xml
文件,定义自定义格式化模式。例如:
这里<configuration> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy - MM - dd HH:mm:ss.SSS} [%thread] %X{requestId} - %msg%n</pattern> </encoder> </appender> <root level="info"> <appender - ref ref="STDOUT"/> </root> </configuration>
%d{yyyy - MM - dd HH:mm:ss.SSS}
表示请求时间,%thread
表示处理线程,%X{requestId}
表示请求ID(需在代码中设置MDC,即Mapped Diagnostic Context中的requestId
)。- 在Java代码中设置MDC:
import org.slf4j.MDC; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RequestHandler { private static final Logger logger = LoggerFactory.getLogger(RequestHandler.class); public void handleRequest(String requestId) { MDC.put("requestId", requestId); try { // 处理请求逻辑 logger.info("Handling request..."); } finally { MDC.remove("requestId"); } } }
- 在
- 使用Log4j2:
- 在
pom.xml
中添加Log4j2依赖:
<dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j - api</artifactId> <version>2.14.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j - core</artifactId> <version>2.14.1</version> </dependency>
- 在
src/main/resources
下创建log4j2.xml
文件,定义自定义格式化模式:
<Configuration status="WARN"> <Appenders> <Console name="Console" target="SYSTEM_OUT"> <PatternLayout pattern="%d{yyyy - MM - dd HH:mm:ss.SSS} [%t] %X{requestId} - %msg%n"/> </Console> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root> </Loggers> </Configuration>
- 在Java代码中设置Context Map(类似于SLF4J的MDC):
import org.apache.logging.log4j.ThreadContext; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class RequestHandler { private static final Logger logger = LogManager.getLogger(RequestHandler.class); public void handleRequest(String requestId) { ThreadContext.put("requestId", requestId); try { // 处理请求逻辑 logger.info("Handling request..."); } finally { ThreadContext.remove("requestId"); } } }
- 在
高并发场景下可能遇到的性能问题及优化方案
- 性能问题:
- I/O瓶颈:高并发下频繁的日志写入操作会导致I/O性能瓶颈,尤其是写入文件时。大量的日志写入磁盘会导致磁盘I/O负载过高,影响应用整体性能。
- MDC操作开销:在高并发环境中,频繁地设置和清除MDC(或Log4j2的ThreadContext)数据可能会带来一定的性能开销,因为这涉及到线程本地存储的操作。
- 锁竞争:如果日志框架内部使用锁来保证线程安全的日志写入,高并发时可能会出现锁竞争问题,降低系统并发性能。
- 优化方案:
- 异步日志写入:
- Logback:可以使用
AsyncAppender
。在logback.xml
中配置如下:
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"> <appender - ref ref="STDOUT"/> </appender> <root level="info"> <appender - ref ref="ASYNC"/> </root>
- Log4j2:使用
AsyncLogger
或AsyncAppender
。在log4j2.xml
中配置:
<AsyncLogger name="com.example" level="info" includeLocation="true"> <AppenderRef ref="Console"/> </AsyncLogger>
- Logback:可以使用
- 减少MDC操作频率:尽量在请求处理的入口和出口统一设置和清除MDC数据,避免在请求处理过程中频繁操作。可以使用过滤器(如Servlet Filter)在Web应用中统一处理。
- 优化日志输出级别:根据实际需求,在高并发场景下适当提高日志输出级别,如将
debug
级别的日志关闭,只保留info
及以上级别的日志,减少不必要的日志记录。 - 使用无锁数据结构:某些日志框架支持使用无锁的数据结构来避免锁竞争,例如Log4j2的
Disruptor
模式。在log4j2.xml
中配置:
这里<Appenders> <Async name="Async" bufferSize="1024" blocking="false" discardThreshold="0"> <Disruptor waitStrategy="Sleeping"/> <AppenderRef ref="Console"/> </Async> </Appenders>
Disruptor
使用Sleeping
等待策略,可减少线程竞争。 - 异步日志写入: