八个服务的报错,谁在说谎
用户投诉下单要八秒。订单服务的日志说下游慢,库存服务说数据库慢,数据库说当时的慢查询早被清掉了——每个服务单看都挺正常,凑在一起就是一笔糊涂账。没有全局视角的排障,是盲人摸象:你得手动到八个容器里按时间窗 grep,还要祈祷每台机器的时钟对得够齐。链路追踪干的就是这件事:把一次调用的完整路径钉在一张图上。
三个概念搭起骨架
Trace 是一次请求的完整旅程,全局唯一 TraceId 串起全程;Span 是旅程中的一段——一次 RPC、一次 DB 查询、一次缓存读取,各自有起止时间与父子关系;TraceContext 是随请求流动的上下文,装着当前 TraceId、SpanId 与采样标记。所有 Span 按父子关系拼起来,就是一棵调用树:哪一跳慢、哪一跳失败,树上一目了然。
// 请求进入:从 Header 提取上下文,没有就新建
Span span = tracer.nextSpan()
.name("order/create")
.tag("user.id", userId)
.start();
try (SpanInScope ws = tracer.withSpanInScope(span)) {
// 调用下游:拦截器自动把上下文注入 Header
// X-B3-TraceId / X-B3-SpanId / X-B3-Sampled
inventoryClient.deduct(skuId, 1);
} finally {
span.end(); // 忘了 end,这条 Span 就丢了
}TraceId 怎么跨进程传递
跨进程靠 Header 注入与提取:入口生成或继承 TraceId,调下游时写进 Header,下游提取后继续传播。同步 HTTP 链路,主流框架的拦截器都能自动做。三个容易断链的地方:线程池——异步任务换了线程,上下文不会自动跟过去,要用装饰器包装任务;消息队列——生产者在消息头里带上上下文,消费者提取重建;自定义 HTTP 调用——绕过拦截器裸写的连接,链路就断了。断链的请求无法被追踪,但恰恰是这类请求最容易出问题。
埋点从哪来
两条路线:字节码增强——Agent 在类加载时改写字节码,对 Spring Cloud、MyBatis、Redis 客户端这些常用组件自动埋点,业务代码零改动,SkyWalking 是代表;SDK 手动埋点——OpenTelemetry API 显式创建 Span,表达力强但侵入代码。工程上通常 Agent 打底覆盖常规组件,关键业务路径再手动补充自定义 Span,两边不冲突。
采样:不是每个请求都要记
全量记录的成本高昂:万级 QPS 的系统,每个请求几十个 Span,存储一天就能吃掉几个 TB。采样是必选项:头部采样——入口决定记不记,整条链路一致,成本低但可能漏掉尾部才暴露的问题;尾部采样——看完整个 Trace 再决定,慢请求、错误请求必采,代价是中间节点要缓存数据。常见组合:常态按比例头部采样,错误与超时请求强制保留——正常流量可以少记,异常流量一个都不能丢。
概念齐了,下一篇直接上手:SkyWalking 接入、慢接口定位与采样治理。
评论 (0)