在现代分布式系统中,随着服务数量的增加,系统的复杂性也呈指数级增长。为了快速定位问题、分析性能瓶颈,全链路追踪成为一项必不可少的能力。本文将详细介绍如何利用 ELK(Elasticsearch + Logstash + Kibana) 实现全链路追踪,并结合实际代码和 UML 图帮助您更好地理解。
全链路追踪是一种技术手段,用于跟踪一个请求在整个分布式系统中的流转过程。它可以帮助开发者:
常见的全链路追踪工具有 Jaeger、Zipkin 和 SkyWalking,但 ELK 同样可以胜任这一任务,尤其是在日志驱动的场景下。
ELK 是一个强大的日志处理工具栈,包括以下组件:
通过 ELK,我们可以实现:
首先,我们设计一个简单的分布式系统架构,包含以下几个服务:
以下是系统的架构图(UML 部署图):
每个服务都会生成日志,并通过唯一的 Trace ID
关联整个请求链路。
为了实现全链路追踪,我们需要为每个请求分配一个全局唯一的 Trace ID
,并在服务间传递。以下是具体实现步骤:
import java.util.UUID;
public class TraceIdContext {
private static final ThreadLocal<String> traceId = new ThreadLocal<>();
public static String getTraceId() {
return traceId.get();
}
public static void setTraceId(String id) {
traceId.set(id);
}
public static String generateTraceId() {
return UUID.randomUUID().toString();
}
}
在网关服务中,为每个请求生成一个 Trace ID
并将其存储到上下文中:
@RestController
public class GatewayController {
@GetMapping("/submitOrder")
public String submitOrder() {
// 生成 Trace ID
String traceId = TraceIdContext.generateTraceId();
TraceIdContext.setTraceId(traceId);
// 调用下游服务
callOrderService();
return "Order submitted with Trace ID: " + traceId;
}
private void callOrderService() {
// 模拟调用订单服务
System.out.println("Calling Order Service with Trace ID: " + TraceIdContext.getTraceId());
}
}
在 HTTP 请求头中传递 Trace ID
,确保下游服务能够获取到相同的 Trace ID
。
@RestController
public class OrderController {
@PostMapping("/processOrder")
public String processOrder(@RequestHeader("X-Trace-ID") String traceId) {
TraceIdContext.setTraceId(traceId);
// 处理订单逻辑
callInventoryService();
return "Order processed with Trace ID: " + traceId;
}
private void callInventoryService() {
// 模拟调用库存服务
System.out.println("Calling Inventory Service with Trace ID: " + TraceIdContext.getTraceId());
}
}
为了方便后续的日志分析,我们需要对日志进行格式化,并确保每条日志都包含 Trace ID
。
使用 Logback 配置日志格式:
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - TraceID: %X{traceId} - %msg%npattern>
encoder>
appender>
<root level="info">
<appender-ref ref="STDOUT" />
root>
configuration>
在代码中设置 MDC(Mapped Diagnostic Context)来记录 Trace ID
:
import org.slf4j.MDC;
public class LoggingUtil {
public static void setTraceId(String traceId) {
MDC.put("traceId", traceId);
}
public static void clearTraceId() {
MDC.clear();
}
}
在每个服务的入口处调用 LoggingUtil.setTraceId()
,确保日志中包含 Trace ID
。
Logstash 负责从各个服务中收集日志,并将其发送到 Elasticsearch。
创建一个 logstash.conf
文件,定义输入、过滤器和输出:
input {
file {
path => "/path/to/logs/*.log"
start_position => "beginning"
}
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:thread}\] %{LOGLEVEL:level} %{DATA:logger} - TraceID: %{DATA:traceId} - %{GREEDYDATA:message}" }
}
}
output {
elasticsearch {
hosts => ["http://localhost:9200"]
index => "logs-%{+YYYY.MM.dd}"
}
}
在 Kibana 中,我们可以创建仪表盘来展示全链路追踪信息。
logs-*
。Trace ID
过滤日志,查看某个请求的完整链路。通过以上步骤,我们成功实现了基于 ELK 的全链路追踪。以下是关键点总结:
希望本文能帮助您更好地理解和实现全链路追踪!如果有任何问题,欢迎留言讨论。