面试题答案
一键面试设计思路
- 统一日志接口:使用SLF4J作为统一的日志接口,因为它是一个简单的外观模式的日志抽象层,能兼容多种日志实现框架,如Logback、Log4j 2等。这样各个微服务在代码层面通过SLF4J进行日志记录,无需关心具体的日志实现。
- 日志格式定义:定义统一的日志格式,包含时间戳、服务名、线程名、日志级别、请求链路ID(用于追踪跨服务请求链路)、日志消息等关键信息。例如:
[yyyy-MM-dd HH:mm:ss.SSS] [serviceName] [threadName] [level] [traceId] message
。 - 请求链路追踪:在每个微服务的入口处生成或获取请求链路ID(如使用分布式追踪系统的ID,或自定义生成唯一ID),并将其传递到后续的服务调用中,在日志记录时包含该ID,以便追踪整个请求的处理路径。
- 兼容性处理:对于已经使用不同日志框架的微服务,通过SLF4J的桥接器进行适配。如对于Log4j 1.x,使用log4j-over-slf4j桥接器;对于其他日志框架,也有相应的桥接方案,使得它们能通过SLF4J接口输出日志。
- 性能优化:
- 合理配置日志级别,在生产环境尽量使用较高日志级别(如INFO、WARN、ERROR),减少不必要的日志输出。
- 异步日志记录,使用异步日志库(如Logback的AsyncAppender或Log4j 2的AsyncLogger),将日志记录操作放到异步线程中执行,减少对业务线程的性能影响。
关键实现步骤
- 引入依赖:在每个微服务的
pom.xml
文件中引入SLF4J相关依赖,以及对应的日志实现依赖(如Logback或Log4j 2)。如果需要桥接旧的日志框架,引入相应的桥接器依赖。例如,对于使用Log4j 1.x的微服务:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.32</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.6</version>
</dependency>
- 配置日志格式:在日志实现框架的配置文件(如
logback.xml
或log4j2.xml
)中定义统一的日志格式。以Logback为例:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%X{serviceName}] [%thread] [%level] [%X{traceId}] %msg%n</pattern>
</encoder>
</appender>
- 请求链路ID传递:可以使用过滤器(如Spring Web的
Filter
)在每个微服务的入口处生成或获取请求链路ID,并将其存储在MDC
(Mapped Diagnostic Context,SLF4J提供的用于传递上下文信息的工具)中,以便在日志记录时使用。例如:
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.util.UUID;
@Component
@WebFilter(filterName = "TraceIdFilter", urlPatterns = "/*")
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);
try {
chain.doFilter(request, response);
} finally {
MDC.remove("traceId");
}
}
}
- 服务名设置:同样可以在过滤器或启动类中设置服务名到
MDC
中。例如在Spring Boot的启动类中:
import org.slf4j.MDC;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class YourServiceApplication implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
MDC.put("serviceName", "your-service-name");
}
public static void main(String[] args) {
SpringApplication.run(YourServiceApplication.class, args);
}
}
- 异步日志配置:在日志配置文件中配置异步日志。以Logback为例,添加
AsyncAppender
:
<appender name="ASYNC_STDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
然后在根<logger>
中使用ASYNC_STDOUT
:
<root level="info">
<appender-ref ref="ASYNC_STDOUT" />
</root>